-
-
Notifications
You must be signed in to change notification settings - Fork 301
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1192 from LibrePhotos/feat/improved-service-manag…
…ement Improved service management
- Loading branch information
Showing
10 changed files
with
219 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
from django.core.management.base import BaseCommand | ||
from django_q.models import Schedule | ||
from django_q.tasks import schedule | ||
|
||
from api.services import SERVICES, start_service | ||
|
||
|
||
class Command(BaseCommand): | ||
help = "Start one of the services." | ||
|
||
# Define all the services that can be started | ||
def add_arguments(self, parser): | ||
parser.add_argument( | ||
"service", | ||
type=str, | ||
help="The service to start", | ||
choices=[ | ||
SERVICES.keys(), | ||
"all", | ||
], | ||
) | ||
|
||
def handle(self, *args, **kwargs): | ||
service = kwargs["service"] | ||
if service == "all": | ||
for svc in SERVICES.keys(): | ||
start_service(svc) | ||
if not Schedule.objects.filter(func="api.services.check_services").exists(): | ||
schedule( | ||
"api.services.check_services", | ||
schedule_type=Schedule.MINUTES, | ||
minutes=1, | ||
) | ||
else: | ||
start_service(service) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import subprocess | ||
|
||
import requests | ||
|
||
from api.util import logger | ||
|
||
# Define all the services that can be started, with their respective ports | ||
SERVICES = { | ||
"image_similarity": 8002, | ||
"thumbnail": 8003, | ||
"face_recognition": 8005, | ||
"clip_embeddings": 8006, | ||
"llm": 8008, | ||
"image_captioning": 8007, | ||
} | ||
|
||
running_processes = {} | ||
|
||
|
||
def check_services(): | ||
for service in running_processes.keys(): | ||
if not is_healthy(service): | ||
process = running_processes.pop(service) | ||
process.terminate() | ||
logger.info(f"Starting {service}") | ||
start_service(service) | ||
|
||
|
||
def is_healthy(service): | ||
port = SERVICES.get(service) | ||
try: | ||
res = requests.get(f"http://localhost:{port}/health") | ||
return res.status_code == 200 | ||
except BaseException as e: | ||
logger.warning(f"Error checking health of {service}: {str(e)}") | ||
return False | ||
|
||
|
||
def start_service(service): | ||
if service == "image_similarity": | ||
process = subprocess.Popen( | ||
[ | ||
"python", | ||
"image_similarity/main.py", | ||
"2>&1 | tee /logs/image_similarity.log", | ||
] | ||
) | ||
elif service in SERVICES.keys(): | ||
process = subprocess.Popen( | ||
[ | ||
"python", | ||
f"service/{service}/main.py", | ||
"2>&1 | tee /logs/{service}.log", | ||
] | ||
) | ||
else: | ||
logger.warning("Unknown service:", service) | ||
return False | ||
|
||
running_processes[service] = process | ||
logger.info(f"Service '{service}' started successfully") | ||
return True | ||
|
||
|
||
def stop_service(service): | ||
if service in running_processes: | ||
process = running_processes.pop(service) | ||
process.terminate() | ||
logger.info(f"Service '{service}' stopped successfully") | ||
return True | ||
else: | ||
logger.warning(f"Service '{service}' is not running") | ||
return False |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
from rest_framework import status, viewsets | ||
from rest_framework.decorators import action | ||
from rest_framework.permissions import IsAdminUser | ||
from rest_framework.response import Response | ||
|
||
from api.services import SERVICES, is_healthy, start_service, stop_service | ||
|
||
|
||
class ServiceViewSet(viewsets.ViewSet): | ||
permission_classes = [IsAdminUser] | ||
|
||
def list(self, request): | ||
return Response({"services": SERVICES}) | ||
|
||
def retrieve(self, request, pk=None): | ||
service_name = pk | ||
|
||
if service_name not in SERVICES: | ||
return Response( | ||
{"error": f"Service {service_name} not found"}, | ||
status=status.HTTP_404_NOT_FOUND, | ||
) | ||
|
||
healthy = is_healthy(service_name) | ||
return Response({"service_name": service_name, "healthy": healthy}) | ||
|
||
@action(detail=True, methods=["post"]) | ||
def start(self, request, pk=None): | ||
service_name = pk | ||
|
||
if service_name not in SERVICES: | ||
return Response( | ||
{"error": f"Service {service_name} not found"}, | ||
status=status.HTTP_404_NOT_FOUND, | ||
) | ||
|
||
start_result = start_service(service_name) | ||
if start_result: | ||
return Response( | ||
{"message": f"Service {service_name} started successfully"}, | ||
status=status.HTTP_200_OK, | ||
) | ||
else: | ||
return Response( | ||
{"error": f"Failed to start service {service_name}"}, | ||
status=status.HTTP_500_INTERNAL_SERVER_ERROR, | ||
) | ||
|
||
@action(detail=True, methods=["post"]) | ||
def stop(self, request, pk=None): | ||
service_name = pk | ||
|
||
if service_name not in SERVICES: | ||
return Response( | ||
{"error": f"Service {service_name} not found"}, | ||
status=status.HTTP_404_NOT_FOUND, | ||
) | ||
|
||
stop_result = stop_service(service_name) | ||
if stop_result: | ||
return Response( | ||
{"message": f"Service {service_name} stopped successfully"}, | ||
status=status.HTTP_200_OK, | ||
) | ||
else: | ||
return Response( | ||
{"error": f"Failed to stop service {service_name}"}, | ||
status=status.HTTP_500_INTERNAL_SERVER_ERROR, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters