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