#SPDX-License-Identifier: MIT """ Augur library commands for controlling the backend components """ from copy import deepcopy import os, time, atexit, subprocess, click import multiprocessing as mp import gunicorn.app.base from gunicorn.six import iteritems from gunicorn.arbiter import Arbiter from augur.housekeeper.housekeeper import Housekeeper from augur.util import logger from augur.server import Server from augur.cli.util import kill_processes import time @click.command("run") @click.option("--disable-housekeeper", is_flag=True, default=False, help="Turns off the housekeeper") @click.option("--skip-cleanup", is_flag=True, default=False, help="Disables the old process cleanup that runs before Augur starts") @click.pass_context def cli(ctx, disable_housekeeper, skip_cleanup): """ Start Augur's backend server """ if not skip_cleanup: logger.info("Cleaning up old Augur processes. Just a moment please...") ctx.invoke(kill_processes) time.sleep(2) else: logger.info("Skipping cleanup processes.") def get_process_id(name): """Return process ids found by name or command """ child = subprocess.Popen(['pgrep', '-f', name], stdout=subprocess.PIPE, shell=False) response = child.communicate()[0] return [int(pid) for pid in response.split()] app = ctx.obj mp.set_start_method('forkserver', force=True) master = None manager = None broker = None housekeeper = None logger.info("Booting broker and its manager...") manager = mp.Manager() broker = manager.dict() controller = app.read_config('Workers') worker_pids = [] worker_processes = [] if not disable_housekeeper: if not controller: return for worker in controller.keys(): if not controller[worker]['switch']: continue logger.info("Your config has the option set to automatically boot {} instances of the {}".format(controller[worker]['workers'], worker)) pids = get_process_id("/bin/sh -c cd workers/{} && {}_start".format(worker, worker)) worker_pids += pids if len(pids) > 0: worker_pids.append(pids[0] + 1) pids.append(pids[0] + 1) logger.info("Found and preparing to kill previous {} worker pids: {}".format(worker,pids)) for pid in pids: try: os.kill(pid, 9) except: logger.info("Worker process {} already killed".format(pid)) @atexit.register def exit(): try: for pid in worker_pids: os.kill(pid, 9) except: logger.info("Worker process {} already killed".format(pid)) for process in worker_processes: logger.info("Shutting down worker process with pid: {} ...".format(process.pid)) process.terminate() if master is not None: master.halt() logger.info("Shutting down housekeeper updates...") if housekeeper is not None: housekeeper.shutdown_updates() # if hasattr(manager, "shutdown"): # wait for the spawner and the worker threads to go down # if manager is not None: manager.shutdown() # check if it is still alive and kill it if necessary # if manager._process.is_alive(): manager._process.terminate() # Prevent multiprocessing's atexit from conflicting with gunicorn logger.info("Killing main augur process with PID: {}".format(os.getpid())) os.kill(os.getpid(), 9) os._exit(0) if not disable_housekeeper: logger.info("Booting housekeeper...") jobs = deepcopy(app.read_config('Housekeeper', 'jobs')) try: housekeeper = Housekeeper( jobs, broker, broker_host=app.read_config('Server', 'host'), broker_port=app.read_config('Server', 'port'), user=app.read_config('Database', 'user'), password=app.read_config('Database', 'password'), host=app.read_config('Database', 'host'), port=app.read_config('Database', 'port'), dbname=app.read_config('Database', 'name') ) except KeyboardInterrupt as e: exit() logger.info("Housekeeper has finished booting.") if controller: for worker in controller.keys(): if controller[worker]['switch']: for i in range(controller[worker]['workers']): logger.info("Booting {} #{}".format(worker, i + 1)) worker_process = mp.Process(target=worker_start, kwargs={'worker_name': worker, 'instance_number': i, 'worker_port': controller[worker]['port']}, daemon=True) worker_process.start() worker_processes.append(worker_process) host = app.read_config('Server', 'host') port = app.read_config('Server', 'port') workers = int(app.read_config('Server', 'workers')) timeout = int(app.read_config('Server', 'timeout')) options = { 'bind': '%s:%s' % (host, port), 'workers': workers, 'accesslog': '-', 'access_log_format': '%(h)s - %(t)s - %(r)s', 'timeout': timeout } logger.info('Starting server...') master = Arbiter(AugurGunicornApp(options, manager=manager, broker=broker, housekeeper=housekeeper)).run() def worker_start(worker_name=None, instance_number=0, worker_port=None): time.sleep(120 * instance_number) destination = subprocess.DEVNULL try: destination = open("workers/{}/worker_{}.log".format(worker_name, worker_port), "a+") except IOError as e: logger.error("Error opening log file for auto-started worker {}: {}".format(worker_name, e)) process = subprocess.Popen("cd workers/{} && {}_start".format(worker_name,worker_name), shell=True, stdout=destination, stderr=subprocess.STDOUT) logger.info("{} booted.".format(worker_name)) class AugurGunicornApp(gunicorn.app.base.BaseApplication): """ Loads configurations, initializes Gunicorn, loads server """ def __init__(self, options=None, manager=None, broker=None, housekeeper=None): self.options = options or {} self.manager = manager self.broker = broker self.housekeeper = housekeeper super(AugurGunicornApp, self).__init__() # self.cfg.pre_request.set(pre_request) def load_config(self): """ Sets the values for configurations """ config = dict([(key, value) for key, value in iteritems(self.options) if key in self.cfg.settings and value is not None]) for key, value in iteritems(config): self.cfg.set(key.lower(), value) def load(self): """ Returns the loaded server """ server = Server(manager=self.manager, broker=self.broker, housekeeper=self.housekeeper) return server.app