parent
							
								
									0bac3e55f4
								
							
						
					
					
						commit
						1e1385da84
					
				| @ -0,0 +1,28 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| from django.http import HttpResponse | ||||
| from django.conf import settings | ||||
| from django.utils.translation import ugettext as _ | ||||
| from proxy.views import proxy_view | ||||
| from rest_framework.views import APIView | ||||
| 
 | ||||
| from api.utils.auth import AdminTokenAuthentication | ||||
| 
 | ||||
| flower_url = f'{settings.CELERY_FLOWER_HOST}:{settings.CELERY_FLOWER_PORT}' | ||||
| 
 | ||||
| 
 | ||||
| class CeleryFlowerView(APIView): | ||||
|     authentication_classes = [AdminTokenAuthentication, ] | ||||
| 
 | ||||
|     def get(self, request, path): | ||||
|         remote_url = 'http://{}/flower/{}'.format(flower_url, path) | ||||
|         try: | ||||
|             response = proxy_view(request, remote_url) | ||||
|         except Exception as e: | ||||
|             msg = _("<h1>Flower service unavailable, check it</h1>") + \ | ||||
|                   '<br><br> <div>{}</div>'.format(e) | ||||
|             response = HttpResponse(msg) | ||||
|         return response | ||||
| 
 | ||||
|     def post(self, request, path): | ||||
|         return self.get(request, path) | ||||
| @ -0,0 +1,10 @@ | ||||
| from django.core.management.base import BaseCommand | ||||
| 
 | ||||
| from api.tasks import start_api_sever_do_clean | ||||
| 
 | ||||
| 
 | ||||
| class Command(BaseCommand): | ||||
|     help = 'Expire caches' | ||||
| 
 | ||||
|     def handle(self, *args, **options): | ||||
|         start_api_sever_do_clean() | ||||
| @ -0,0 +1,6 @@ | ||||
| from .services.command import BaseActionCommand, Action | ||||
| 
 | ||||
| 
 | ||||
| class Command(BaseActionCommand): | ||||
|     help = 'Restart services' | ||||
|     action = Action.restart.value | ||||
| @ -0,0 +1,146 @@ | ||||
| from django.core.management.base import BaseCommand, CommandError | ||||
| from django.db.models import TextChoices | ||||
| from .utils import ServicesUtil | ||||
| from .hands import * | ||||
| 
 | ||||
| 
 | ||||
| class Services(TextChoices): | ||||
|     # gunicorn = 'gunicorn', 'gunicorn' | ||||
|     uwsgi = 'uwsgi', 'uwsgi' | ||||
|     celery = 'celery', 'celery' | ||||
|     beat = 'beat', 'beat' | ||||
|     flower = 'flower', 'flower' | ||||
|     task = 'task', 'task' | ||||
|     all = 'all', 'all' | ||||
| 
 | ||||
|     @classmethod | ||||
|     def get_service_object_class(cls, name): | ||||
|         from . import services | ||||
|         services_map = { | ||||
|             cls.flower: services.FlowerService, | ||||
|             cls.celery: services.CeleryDefaultService, | ||||
|             cls.beat: services.BeatService, | ||||
|             cls.uwsgi: services.UwsgiService | ||||
|         } | ||||
|         return services_map.get(name) | ||||
| 
 | ||||
|     @classmethod | ||||
|     def api_services(cls): | ||||
|         return [cls.uwsgi] | ||||
| 
 | ||||
|     @classmethod | ||||
|     def flower_services(cls): | ||||
|         return [cls.flower] | ||||
| 
 | ||||
|     @classmethod | ||||
|     def beat_services(cls): | ||||
|         return [cls.beat] | ||||
| 
 | ||||
|     @classmethod | ||||
|     def celery_services(cls): | ||||
|         return [cls.celery] | ||||
| 
 | ||||
|     @classmethod | ||||
|     def task_services(cls): | ||||
|         return cls.celery_services() + cls.beat_services() + cls.flower_services() | ||||
| 
 | ||||
|     @classmethod | ||||
|     def all_services(cls): | ||||
|         return cls.task_services() + cls.api_services() | ||||
| 
 | ||||
|     @classmethod | ||||
|     def export_services_values(cls): | ||||
|         return [cls.all.value, cls.uwsgi.value, cls.task.value, cls.celery.value, cls.flower.value, cls.beat.value] | ||||
| 
 | ||||
|     @classmethod | ||||
|     def get_service_objects(cls, service_names, **kwargs): | ||||
|         services = set() | ||||
|         for name in service_names: | ||||
|             method_name = f'{name}_services' | ||||
|             if hasattr(cls, method_name): | ||||
|                 _services = getattr(cls, method_name)() | ||||
|             elif hasattr(cls, name): | ||||
|                 _services = [getattr(cls, name)] | ||||
|             else: | ||||
|                 continue | ||||
|             services.update(set(_services)) | ||||
| 
 | ||||
|         service_objects = [] | ||||
|         for s in services: | ||||
|             service_class = cls.get_service_object_class(s.value) | ||||
|             if not service_class: | ||||
|                 continue | ||||
|             kwargs.update({ | ||||
|                 'name': s.value | ||||
|             }) | ||||
|             service_object = service_class(**kwargs) | ||||
|             service_objects.append(service_object) | ||||
|         return service_objects | ||||
| 
 | ||||
| 
 | ||||
| class Action(TextChoices): | ||||
|     start = 'start', 'start' | ||||
|     status = 'status', 'status' | ||||
|     stop = 'stop', 'stop' | ||||
|     restart = 'restart', 'restart' | ||||
| 
 | ||||
| 
 | ||||
| class BaseActionCommand(BaseCommand): | ||||
|     help = 'Service Base Command' | ||||
| 
 | ||||
|     action = None | ||||
|     util = None | ||||
| 
 | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         super().__init__(*args, **kwargs) | ||||
| 
 | ||||
|     def add_arguments(self, parser): | ||||
|         parser.add_argument( | ||||
|             'services', nargs='+', choices=Services.export_services_values(), help='Service', | ||||
|         ) | ||||
|         parser.add_argument('-d', '--daemon', nargs="?", const=True) | ||||
|         parser.add_argument('-up', '--uwsgi_processes', type=int, nargs="?", default=4) | ||||
|         parser.add_argument('-ut', '--uwsgi_threads', type=int, nargs="?", default=2) | ||||
|         parser.add_argument('-usm', '--uwsgi_socket_mode', nargs="?", const=True, | ||||
|                             help='run to bind socket mode, default http mode, only uwsgi service') | ||||
|         parser.add_argument('-f', '--force', nargs="?", const=True) | ||||
|         parser.add_argument('-u', '--uid', nargs="?", default='root', type=str) | ||||
|         parser.add_argument('-g', '--gid', nargs="?", default='root', type=str) | ||||
| 
 | ||||
|     def initial_util(self, *args, **options): | ||||
|         service_names = options.get('services') | ||||
|         service_kwargs = { | ||||
|             'uwsgi_processes': options.get('uwsgi_processes'), | ||||
|             'uwsgi_threads': options.get('uwsgi_threads'), | ||||
|             'uwsgi_socket_mode': options.get('uwsgi_socket_mode'), | ||||
|             'uid': options.get('uid'), | ||||
|             'gid': options.get('gid'), | ||||
|         } | ||||
|         services = Services.get_service_objects(service_names=service_names, **service_kwargs) | ||||
| 
 | ||||
|         kwargs = { | ||||
|             'services': services, | ||||
|             'run_daemon': options.get('daemon', False), | ||||
|             'stop_daemon': self.action == Action.stop.value and Services.all.value in service_names, | ||||
|             'force_stop': options.get('force') or False, | ||||
|         } | ||||
|         self.util = ServicesUtil(**kwargs) | ||||
| 
 | ||||
|     def handle(self, *args, **options): | ||||
|         self.initial_util(*args, **options) | ||||
|         assert self.action in Action.values, f'The action {self.action} is not in the optional list' | ||||
|         _handle = getattr(self, f'_handle_{self.action}', lambda: None) | ||||
|         _handle() | ||||
| 
 | ||||
|     def _handle_start(self): | ||||
|         self.util.start_and_watch() | ||||
|         os._exit(0) | ||||
| 
 | ||||
|     def _handle_stop(self): | ||||
|         self.util.stop() | ||||
| 
 | ||||
|     def _handle_restart(self): | ||||
|         self.util.restart() | ||||
| 
 | ||||
|     def _handle_status(self): | ||||
|         self.util.show_status() | ||||
| @ -0,0 +1,28 @@ | ||||
| import os | ||||
| import sys | ||||
| import logging | ||||
| from django.conf import settings | ||||
| 
 | ||||
| from config import BASECONF | ||||
| 
 | ||||
| try: | ||||
|     from apps.jumpserver import const | ||||
| 
 | ||||
|     __version__ = const.VERSION | ||||
| except ImportError as e: | ||||
|     print("Not found __version__: {}".format(e)) | ||||
|     print("Python is: ") | ||||
|     logging.info(sys.executable) | ||||
|     __version__ = 'Unknown' | ||||
| 
 | ||||
| SOCKET_HOST = BASECONF.SERVER_BIND_HOST or '127.0.0.1' | ||||
| SOCKET_PORT = BASECONF.SERVER_LISTEN_PORT or 8080 | ||||
| 
 | ||||
| CELERY_FLOWER_HOST = BASECONF.CELERY_FLOWER_HOST or '127.0.0.1' | ||||
| CELERY_FLOWER_PORT = BASECONF.CELERY_FLOWER_PORT or 5555 | ||||
| 
 | ||||
| DEBUG = BASECONF.DEBUG or False | ||||
| BASE_DIR = settings.BASE_DIR | ||||
| APPS_DIR = BASE_DIR | ||||
| LOG_DIR = os.path.join(BASE_DIR, 'logs') | ||||
| TMP_DIR = os.path.join(BASE_DIR, 'tmp') | ||||
| @ -0,0 +1,5 @@ | ||||
| from .beat import * | ||||
| from .flower import * | ||||
| from .gunicorn import * | ||||
| from .celery_default import * | ||||
| from .uwsgi import * | ||||
| @ -0,0 +1,233 @@ | ||||
| import abc | ||||
| import time | ||||
| import shutil | ||||
| import psutil | ||||
| import datetime | ||||
| import threading | ||||
| import subprocess | ||||
| import pwd | ||||
| from ..hands import * | ||||
| 
 | ||||
| 
 | ||||
| def get_user_id(uid): | ||||
|     if uid.isdigit(): | ||||
|         try: | ||||
|             return pwd.getpwuid(int(uid)).pw_uid | ||||
|         except: | ||||
|             ... | ||||
|     else: | ||||
|         try: | ||||
|             return pwd.getpwnam(uid).pw_uid | ||||
|         except: | ||||
|             ... | ||||
| 
 | ||||
| 
 | ||||
| class BaseService(object): | ||||
| 
 | ||||
|     def __init__(self, **kwargs): | ||||
|         self.name = kwargs['name'] | ||||
|         self.uid = kwargs['uid'] | ||||
|         self.gid = kwargs['gid'] | ||||
|         self._process = None | ||||
|         self.STOP_TIMEOUT = 10 | ||||
|         self.max_retry = 0 | ||||
|         self.retry = 3 | ||||
|         self.LOG_KEEP_DAYS = 7 | ||||
|         self.EXIT_EVENT = threading.Event() | ||||
| 
 | ||||
|     @property | ||||
|     @abc.abstractmethod | ||||
|     def cmd(self): | ||||
|         return [] | ||||
| 
 | ||||
|     @property | ||||
|     @abc.abstractmethod | ||||
|     def cwd(self): | ||||
|         return '' | ||||
| 
 | ||||
|     @property | ||||
|     def is_running(self): | ||||
|         if self.pid == 0: | ||||
|             return False | ||||
|         try: | ||||
|             os.kill(self.pid, 0) | ||||
|         except (OSError, ProcessLookupError): | ||||
|             return False | ||||
|         else: | ||||
|             return True | ||||
| 
 | ||||
|     def show_status(self): | ||||
|         if self.is_running: | ||||
|             msg = f'{self.name} is running: {self.pid}.' | ||||
|         else: | ||||
|             msg = f'{self.name} is stopped.' | ||||
|         print(msg) | ||||
| 
 | ||||
|     # -- log -- | ||||
|     @property | ||||
|     def log_filename(self): | ||||
|         return f'{self.name}.log' | ||||
| 
 | ||||
|     @property | ||||
|     def log_filepath(self): | ||||
|         return os.path.join(LOG_DIR, self.log_filename) | ||||
| 
 | ||||
|     @property | ||||
|     def log_file(self): | ||||
|         return open(self.log_filepath, 'a') | ||||
| 
 | ||||
|     @property | ||||
|     def log_dir(self): | ||||
|         return os.path.dirname(self.log_filepath) | ||||
| 
 | ||||
|     # -- end log -- | ||||
| 
 | ||||
|     # -- pid -- | ||||
|     @property | ||||
|     def pid_filepath(self): | ||||
|         return os.path.join(TMP_DIR, f'{self.name}.pid') | ||||
| 
 | ||||
|     @property | ||||
|     def pid(self): | ||||
|         if not os.path.isfile(self.pid_filepath): | ||||
|             return 0 | ||||
|         with open(self.pid_filepath) as f: | ||||
|             try: | ||||
|                 pid = int(f.read().strip()) | ||||
|             except ValueError: | ||||
|                 pid = 0 | ||||
|         return pid | ||||
| 
 | ||||
|     def write_pid(self): | ||||
|         with open(self.pid_filepath, 'w') as f: | ||||
|             f.write(str(self.process.pid)) | ||||
| 
 | ||||
|     def remove_pid(self): | ||||
|         if os.path.isfile(self.pid_filepath): | ||||
|             os.unlink(self.pid_filepath) | ||||
| 
 | ||||
|     # -- end pid -- | ||||
| 
 | ||||
|     # -- process -- | ||||
|     @property | ||||
|     def process(self): | ||||
|         if not self._process: | ||||
|             try: | ||||
|                 self._process = psutil.Process(self.pid) | ||||
|             except: | ||||
|                 pass | ||||
|         return self._process | ||||
| 
 | ||||
|     # -- end process -- | ||||
| 
 | ||||
|     # -- action -- | ||||
|     def open_subprocess(self): | ||||
|         self.set_work_dir_owner() | ||||
|         kwargs = {'cwd': self.cwd, 'stderr': self.log_file, 'stdout': self.log_file} | ||||
|         self._process = subprocess.Popen(self.cmd, **kwargs) | ||||
| 
 | ||||
|     def start(self): | ||||
|         if self.is_running: | ||||
|             self.show_status() | ||||
|             return | ||||
|         self.remove_pid() | ||||
|         self.open_subprocess() | ||||
|         self.write_pid() | ||||
|         self.start_other() | ||||
| 
 | ||||
|     def start_other(self): | ||||
|         pass | ||||
| 
 | ||||
|     def set_work_dir_owner(self): | ||||
|         uid = get_user_id(self.uid) | ||||
|         gid = get_user_id(self.gid) | ||||
|         u_gid = uid if uid else gid | ||||
|         if u_gid: | ||||
|             for (dir_path, dir_names, filenames) in os.walk(LOG_DIR): | ||||
|                 for filename in filenames + dir_names: | ||||
|                     os.chown(uid=u_gid, gid=u_gid, path=os.path.join(dir_path, filename)) | ||||
|         else: | ||||
|             logging.error(f'uid: {self.uid} gid:{self.gid} is not exists') | ||||
| 
 | ||||
|     def stop(self, force=False): | ||||
|         if not self.is_running: | ||||
|             self.show_status() | ||||
|             # self.remove_pid() | ||||
|             return | ||||
| 
 | ||||
|         print(f'Stop service: {self.name}', end='') | ||||
|         sig = 9 if force else 15 | ||||
|         os.kill(self.pid, sig) | ||||
| 
 | ||||
|         if self.process is None: | ||||
|             print("\033[31m No process found\033[0m") | ||||
|             return | ||||
|         try: | ||||
|             self.process.wait(5) | ||||
|         except: | ||||
|             pass | ||||
| 
 | ||||
|         for i in range(self.STOP_TIMEOUT): | ||||
|             if i == self.STOP_TIMEOUT - 1: | ||||
|                 print("\033[31m Error\033[0m") | ||||
|             if not self.is_running: | ||||
|                 print("\033[32m Ok\033[0m") | ||||
|                 self.remove_pid() | ||||
|                 break | ||||
|             else: | ||||
|                 time.sleep(1) | ||||
|                 continue | ||||
| 
 | ||||
|     def watch(self): | ||||
|         self._check() | ||||
|         if not self.is_running: | ||||
|             self._restart() | ||||
|         self._rotate_log() | ||||
| 
 | ||||
|     def _check(self): | ||||
|         now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') | ||||
|         print(f"{now} Check service status: {self.name} -> ", end='') | ||||
|         if self.process: | ||||
|             try: | ||||
|                 self.process.wait(1)  # 不wait,子进程可能无法回收 | ||||
|             except: | ||||
|                 pass | ||||
| 
 | ||||
|         if self.is_running: | ||||
|             print(f'running at {self.pid}') | ||||
|         else: | ||||
|             print(f'stopped at {self.pid}') | ||||
| 
 | ||||
|     def _restart(self): | ||||
|         if self.retry > self.max_retry: | ||||
|             logging.info(f"Service {self.name} start failed, exit") | ||||
|             self.EXIT_EVENT.set() | ||||
|             return | ||||
|         self.retry += 1 | ||||
|         logging.info(f'> Find {self.name} stopped, retry {self.retry}, {self.pid}') | ||||
|         self.start() | ||||
| 
 | ||||
|     def _rotate_log(self): | ||||
|         now = datetime.datetime.now() | ||||
|         _time = now.strftime('%H:%M') | ||||
|         if _time != '23:59': | ||||
|             return | ||||
| 
 | ||||
|         backup_date = now.strftime('%Y-%m-%d') | ||||
|         backup_log_dir = os.path.join(self.log_dir, backup_date) | ||||
|         if not os.path.exists(backup_log_dir): | ||||
|             os.mkdir(backup_log_dir) | ||||
| 
 | ||||
|         backup_log_path = os.path.join(backup_log_dir, self.log_filename) | ||||
|         if os.path.isfile(self.log_filepath) and not os.path.isfile(backup_log_path): | ||||
|             logging.info(f'Rotate log file: {self.log_filepath} => {backup_log_path}') | ||||
|             shutil.copy(self.log_filepath, backup_log_path) | ||||
|             with open(self.log_filepath, 'w') as f: | ||||
|                 pass | ||||
| 
 | ||||
|         to_delete_date = now - datetime.timedelta(days=self.LOG_KEEP_DAYS) | ||||
|         to_delete_dir = os.path.join(LOG_DIR, to_delete_date.strftime('%Y-%m-%d')) | ||||
|         if os.path.exists(to_delete_dir): | ||||
|             logging.info(f'Remove old log: {to_delete_dir}') | ||||
|             shutil.rmtree(to_delete_dir, ignore_errors=True) | ||||
|     # -- end action -- | ||||
| @ -0,0 +1,31 @@ | ||||
| from ..hands import * | ||||
| from .base import BaseService | ||||
| from django.core.cache import cache | ||||
| 
 | ||||
| __all__ = ['BeatService'] | ||||
| 
 | ||||
| 
 | ||||
| class BeatService(BaseService): | ||||
| 
 | ||||
|     def __init__(self, **kwargs): | ||||
|         super().__init__(**kwargs) | ||||
|         self.lock = cache.lock('beat-distribute-start-lock', timeout=60) | ||||
| 
 | ||||
|     @property | ||||
|     def cmd(self): | ||||
|         scheduler = "django_celery_beat.schedulers:DatabaseScheduler" | ||||
|         print("\n- Start Beat as Periodic Task Scheduler") | ||||
|         cmd = [ | ||||
|             'celery', '-A', | ||||
|             'fir_ser', 'beat', | ||||
|             '-l', 'INFO', | ||||
|             '--uid', self.uid, | ||||
|             '--gid', self.gid, | ||||
|             '--scheduler', scheduler, | ||||
|             '--max-interval', '60' | ||||
|         ] | ||||
|         return cmd | ||||
| 
 | ||||
|     @property | ||||
|     def cwd(self): | ||||
|         return APPS_DIR | ||||
| @ -0,0 +1,39 @@ | ||||
| from ..hands import * | ||||
| from .base import BaseService | ||||
| 
 | ||||
| 
 | ||||
| class CeleryBaseService(BaseService): | ||||
| 
 | ||||
|     def __init__(self, queue, num=10, **kwargs): | ||||
|         super().__init__(**kwargs) | ||||
|         self.queue = queue | ||||
|         self.num = num | ||||
| 
 | ||||
|     @property | ||||
|     def cmd(self): | ||||
|         print('\n- Start Celery as Distributed Task Queue: {}'.format(self.queue.capitalize())) | ||||
| 
 | ||||
|         os.environ.setdefault('PYTHONOPTIMIZE', '1') | ||||
|         os.environ.setdefault('ANSIBLE_FORCE_COLOR', 'True') | ||||
| 
 | ||||
|         if os.getuid() == 0: | ||||
|             os.environ.setdefault('C_FORCE_ROOT', '1') | ||||
|         server_hostname = os.environ.get("SERVER_HOSTNAME") | ||||
|         if not server_hostname: | ||||
|             server_hostname = '%n' | ||||
| 
 | ||||
|         cmd = [ | ||||
|             'celery', '-A', | ||||
|             'fir_ser', 'worker', | ||||
|             '-l', 'INFO', | ||||
|             '--uid', self.uid, | ||||
|             '--gid', self.gid, | ||||
|             '-c', str(self.num), | ||||
|             '-Q', self.queue, | ||||
|             '-n', f'{self.queue}@{server_hostname}' | ||||
|         ] | ||||
|         return cmd | ||||
| 
 | ||||
|     @property | ||||
|     def cwd(self): | ||||
|         return APPS_DIR | ||||
| @ -0,0 +1,10 @@ | ||||
| from .celery_base import CeleryBaseService | ||||
| 
 | ||||
| __all__ = ['CeleryDefaultService'] | ||||
| 
 | ||||
| 
 | ||||
| class CeleryDefaultService(CeleryBaseService): | ||||
| 
 | ||||
|     def __init__(self, **kwargs): | ||||
|         kwargs['queue'] = 'celery' | ||||
|         super().__init__(**kwargs) | ||||
| @ -0,0 +1,36 @@ | ||||
| from ..hands import * | ||||
| from .base import BaseService | ||||
| 
 | ||||
| __all__ = ['FlowerService'] | ||||
| 
 | ||||
| 
 | ||||
| class FlowerService(BaseService): | ||||
| 
 | ||||
|     def __init__(self, **kwargs): | ||||
|         super().__init__(**kwargs) | ||||
| 
 | ||||
|     @property | ||||
|     def cmd(self): | ||||
|         print("\n- Start Flower as Task Monitor") | ||||
| 
 | ||||
|         if os.getuid() == 0: | ||||
|             os.environ.setdefault('C_FORCE_ROOT', '1') | ||||
|         cmd = [ | ||||
|             'celery', '-A', | ||||
|             'fir_ser', 'flower', | ||||
|             '-l', 'INFO', | ||||
|             '--uid', self.uid, | ||||
|             '--gid', self.gid, | ||||
|             '--url_prefix=/flower', | ||||
|             '--auto_refresh=False', | ||||
|             '--max_tasks=1000', | ||||
|             f'--address={CELERY_FLOWER_HOST}', | ||||
|             f'--port={CELERY_FLOWER_PORT}', | ||||
|             # '--basic_auth=flower:ninevenxxx' | ||||
|             # '--tasks_columns=uuid,name,args,state,received,started,runtime,worker' | ||||
|         ] | ||||
|         return cmd | ||||
| 
 | ||||
|     @property | ||||
|     def cwd(self): | ||||
|         return APPS_DIR | ||||
| @ -0,0 +1,35 @@ | ||||
| from ..hands import * | ||||
| from .base import BaseService | ||||
| 
 | ||||
| __all__ = ['GunicornService'] | ||||
| 
 | ||||
| 
 | ||||
| class GunicornService(BaseService): | ||||
| 
 | ||||
|     def __init__(self, **kwargs): | ||||
|         self.worker = kwargs['worker_gunicorn'] | ||||
|         super().__init__(**kwargs) | ||||
| 
 | ||||
|     @property | ||||
|     def cmd(self): | ||||
|         print("\n- Start Gunicorn WSGI HTTP Server") | ||||
| 
 | ||||
|         log_format = '%(h)s %(t)s %(L)ss "%(r)s" %(s)s %(b)s ' | ||||
|         bind = f'{HTTP_HOST}:{HTTP_PORT}' | ||||
|         cmd = [ | ||||
|             'gunicorn', 'fir_ser.wsgi', | ||||
|             '-b', bind, | ||||
|             '-k', 'gthread', | ||||
|             '--threads', '10', | ||||
|             '-w', str(self.worker), | ||||
|             '--max-requests', '4096', | ||||
|             '--access-logformat', log_format, | ||||
|             '--access-logfile', '-' | ||||
|         ] | ||||
|         if DEBUG: | ||||
|             cmd.append('--reload') | ||||
|         return cmd | ||||
| 
 | ||||
|     @property | ||||
|     def cwd(self): | ||||
|         return APPS_DIR | ||||
| @ -0,0 +1,45 @@ | ||||
| from ..hands import * | ||||
| from .base import BaseService | ||||
| 
 | ||||
| __all__ = ['UwsgiService'] | ||||
| 
 | ||||
| 
 | ||||
| class UwsgiService(BaseService): | ||||
| 
 | ||||
|     def __init__(self, **kwargs): | ||||
|         self.processes = kwargs['uwsgi_processes'] | ||||
|         self.threads = kwargs['uwsgi_threads'] | ||||
|         self.uwsgi_socket_mode = kwargs['uwsgi_socket_mode'] | ||||
|         super().__init__(**kwargs) | ||||
| 
 | ||||
|     @property | ||||
|     def cmd(self): | ||||
|         if os.getuid() == 0: | ||||
|             os.environ.setdefault('C_FORCE_ROOT', '1') | ||||
|         print("\n- Start Uwsgi WSGI HTTP Server") | ||||
|         bind = f'{SOCKET_HOST}:{SOCKET_PORT}' | ||||
|         cmd = [ | ||||
|             'uwsgi', | ||||
|             '--uid', self.uid, | ||||
|             '--gid', self.gid, | ||||
|             '--processes', f'{self.processes}', | ||||
|             '--threads', f'{self.threads}', | ||||
|             '--wsgi-file', f"{BASE_DIR}/fir_ser/wsgi.py", | ||||
|             '--listen', '512', | ||||
|             '--chdir', BASE_DIR, | ||||
|             '--buffer-size', '65536', | ||||
|             '--vacuum', | ||||
|             '--enable-threads', | ||||
|         ] | ||||
| 
 | ||||
|         if self.uwsgi_socket_mode: | ||||
|             cmd.extend(['--socket', bind]) | ||||
|         else: | ||||
|             cmd.extend(['--http', bind]) | ||||
|         if DEBUG: | ||||
|             cmd.extend(['--touch-reload', BASE_DIR]) | ||||
|         return cmd | ||||
| 
 | ||||
|     @property | ||||
|     def cwd(self): | ||||
|         return APPS_DIR | ||||
| @ -0,0 +1,141 @@ | ||||
| import threading | ||||
| import signal | ||||
| import time | ||||
| import daemon | ||||
| from daemon import pidfile | ||||
| from .hands import * | ||||
| from .hands import __version__ | ||||
| from .services.base import BaseService | ||||
| 
 | ||||
| 
 | ||||
| class ServicesUtil(object): | ||||
| 
 | ||||
|     def __init__(self, services, run_daemon=False, force_stop=False, stop_daemon=False): | ||||
|         self._services = services | ||||
|         self.run_daemon = run_daemon | ||||
|         self.force_stop = force_stop | ||||
|         self.stop_daemon = stop_daemon | ||||
|         self.EXIT_EVENT = threading.Event() | ||||
|         self.check_interval = 30 | ||||
|         self.files_preserve_map = {} | ||||
| 
 | ||||
|     def restart(self): | ||||
|         self.stop() | ||||
|         time.sleep(5) | ||||
|         self.start_and_watch() | ||||
| 
 | ||||
|     def start_and_watch(self): | ||||
|         logging.info(time.ctime()) | ||||
|         logging.info(f'FlyApps version {__version__}, more see https://flyapps.cn') | ||||
|         self.start() | ||||
|         if self.run_daemon: | ||||
|             self.show_status() | ||||
|             with self.daemon_context: | ||||
|                 self.watch() | ||||
|         else: | ||||
|             self.watch() | ||||
| 
 | ||||
|     def start(self): | ||||
|         for service in self._services: | ||||
|             service: BaseService | ||||
|             service.start() | ||||
|             self.files_preserve_map[service.name] = service.log_file | ||||
|             time.sleep(1) | ||||
| 
 | ||||
|     def stop(self): | ||||
|         for service in self._services: | ||||
|             service: BaseService | ||||
|             service.stop(force=self.force_stop) | ||||
| 
 | ||||
|         if self.stop_daemon: | ||||
|             self._stop_daemon() | ||||
| 
 | ||||
|     # -- watch -- | ||||
|     def watch(self): | ||||
|         while not self.EXIT_EVENT.is_set(): | ||||
|             try: | ||||
|                 _exit = self._watch() | ||||
|                 if _exit: | ||||
|                     break | ||||
|                 time.sleep(self.check_interval) | ||||
|             except KeyboardInterrupt: | ||||
|                 print('Start stop services') | ||||
|                 break | ||||
|         self.clean_up() | ||||
| 
 | ||||
|     def _watch(self): | ||||
|         for service in self._services: | ||||
|             service: BaseService | ||||
|             service.watch() | ||||
|             if service.EXIT_EVENT.is_set(): | ||||
|                 self.EXIT_EVENT.set() | ||||
|                 return True | ||||
|         return False | ||||
| 
 | ||||
|     # -- end watch -- | ||||
| 
 | ||||
|     def clean_up(self): | ||||
|         if not self.EXIT_EVENT.is_set(): | ||||
|             self.EXIT_EVENT.set() | ||||
| 
 | ||||
|         self.stop() | ||||
| 
 | ||||
|     def show_status(self): | ||||
|         for service in self._services: | ||||
|             service: BaseService | ||||
|             service.show_status() | ||||
| 
 | ||||
|     # -- daemon -- | ||||
|     def _stop_daemon(self): | ||||
|         if self.daemon_pid and self.daemon_is_running: | ||||
|             os.kill(self.daemon_pid, 15) | ||||
|         self.remove_daemon_pid() | ||||
| 
 | ||||
|     def remove_daemon_pid(self): | ||||
|         if os.path.isfile(self.daemon_pid_filepath): | ||||
|             os.unlink(self.daemon_pid_filepath) | ||||
| 
 | ||||
|     @property | ||||
|     def daemon_pid(self): | ||||
|         if not os.path.isfile(self.daemon_pid_filepath): | ||||
|             return 0 | ||||
|         with open(self.daemon_pid_filepath) as f: | ||||
|             try: | ||||
|                 pid = int(f.read().strip()) | ||||
|             except ValueError: | ||||
|                 pid = 0 | ||||
|         return pid | ||||
| 
 | ||||
|     @property | ||||
|     def daemon_is_running(self): | ||||
|         try: | ||||
|             os.kill(self.daemon_pid, 0) | ||||
|         except (OSError, ProcessLookupError): | ||||
|             return False | ||||
|         else: | ||||
|             return True | ||||
| 
 | ||||
|     @property | ||||
|     def daemon_pid_filepath(self): | ||||
|         return os.path.join(TMP_DIR, 'flyapps.pid') | ||||
| 
 | ||||
|     @property | ||||
|     def daemon_log_filepath(self): | ||||
|         return os.path.join(LOG_DIR, 'flyApps.log') | ||||
| 
 | ||||
|     @property | ||||
|     def daemon_context(self): | ||||
|         daemon_log_file = open(self.daemon_log_filepath, 'a') | ||||
|         context = daemon.DaemonContext( | ||||
|             pidfile=pidfile.TimeoutPIDLockFile(self.daemon_pid_filepath), | ||||
|             signal_map={ | ||||
|                 signal.SIGTERM: lambda x, y: self.clean_up(), | ||||
|                 signal.SIGHUP: 'terminate', | ||||
|             }, | ||||
|             stdout=daemon_log_file, | ||||
|             stderr=daemon_log_file, | ||||
|             files_preserve=list(self.files_preserve_map.values()), | ||||
|             detach_process=True, | ||||
|         ) | ||||
|         return context | ||||
|     # -- end daemon -- | ||||
| @ -0,0 +1,6 @@ | ||||
| from .services.command import BaseActionCommand, Action | ||||
| 
 | ||||
| 
 | ||||
| class Command(BaseActionCommand): | ||||
|     help = 'Start services' | ||||
|     action = Action.start.value | ||||
| @ -0,0 +1,6 @@ | ||||
| from .services.command import BaseActionCommand, Action | ||||
| 
 | ||||
| 
 | ||||
| class Command(BaseActionCommand): | ||||
|     help = 'Show services status' | ||||
|     action = Action.status.value | ||||
| @ -0,0 +1,6 @@ | ||||
| from .services.command import BaseActionCommand, Action | ||||
| 
 | ||||
| 
 | ||||
| class Command(BaseActionCommand): | ||||
|     help = 'Stop services' | ||||
|     action = Action.stop.value | ||||
					Loading…
					
					
				
		Reference in new issue