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