def __init__(self, *args, **kwargs): self.log = Logger(level=LOGLEVEL) self.processes = list() self.start_time = -1 self.stdout = '' self.stderr = '' self.trynum = 1 self.threads = list() self.stopping = False self.update(*args, **kwargs) self.log.info('task %s initialized.' % self.name)
class Main(MDApp): bot: BotThread = None parser: Parser logger: Logger def build(self): self.theme_cls.primary_palette = 'Gray' return Builder.load_file('templates/screen/screens.kv') def on_start(self): app = MDApp.get_running_app() self.parser = Parser(app) self.logger = Logger().init_terminal_instance(app) self.logger.info('App started') def go_to_server(self, screen_manager: ScreenManager): screen_manager.transition.direction = 'right' screen_manager.current = 'server' def go_to_settings(self, screen_manager: ScreenManager): screen_manager.transition.direction = 'left' screen_manager.current = 'settings' def run_bot(self): token = self.parser.get_token() bot_status = self.parser.get_bot_status() try: if self.bot is not None: self.bot = None self.logger.info('Polling stopped') bot_status.circle_color = colors.RED_COLOR elif token: self.bot = BotThread(self.parser, self.logger) self.bot.start() self.logger.info('Polling started') bot_status.circle_color = colors.GREEN_COLOR else: self.logger.alert('Empty token') bot_status.circle_color = colors.RED_COLOR except Exception as e: self.bot = None self.logger.alert(e) bot_status.circle_color = colors.RED_COLOR
def handle_process_restart_behavior(process, behavior, returncodes, callback): process.wait() Logger(level=LOGLEVEL).debug(( f'Process {process.pid} ({process.args}) exited,' f' with returncode {process.returncode}.' f' Expected returncodes: {returncodes}. Policy: autorestart {behavior}.' )) if behavior.upper() == 'ALWAYS' or ( behavior.upper() == 'UNEXPECTED' and process.returncode not in returncodes and '*' not in returncodes ): try: Logger(level=LOGLEVEL).debug('calling callback %s' % callback.__name__) callback() return except Exception: # XXX: Hack return Logger(level=LOGLEVEL).debug(f'{process.pid} killed w/signal {process.returncode}')
def __init__(self, conf_path): self.conf_path = conf_path self.log = Logger(LOGLEVEL) if not os.path.exists(conf_path): print("") self.log.error('Unknown file or directory %s.' % conf_path) sys.exit(-1) try: with open(conf_path) as conf: self.configuration = config = yaml.load(conf, Loader=Loader) self.log.debug('Configuration readed: %s' % self.configuration) except Exception: raise ParseError('Unable to read configuration file %s' % conf_path) if 'programs' not in config or len(config) > 1: raise ParseError( ('config file is invalid, ' 'bad indentation or "program" section not found.')) self.check_configuration()
import signal import sys from controllers.parser import ( ParseError, TaskmasterDaemonParser, ) from controllers.task import Task from controllers.server import Server from controllers.manager import Manager from controllers.logger import Logger LOGLEVEL = getattr(logging, os.environ.get('LOGLEVEL', 'INFO'), logging.INFO) if __name__ == '__main__': logger = Logger(level=LOGLEVEL) try: try: logger.info('Parsing input configuration...', end='') parser = TaskmasterDaemonParser.from_command_line() except ParseError as e: print("") logger.error(e) sys.exit(-1) logger.success('') logger.debug('Configuration retrieved: %s' % parser.configuration) programs = list() if parser.configuration.get('programs', {}): for program_name, program_params in parser.configuration.get(
from aiogram import Bot from aiogram.utils import executor from aiogram.dispatcher import Dispatcher import os import sys import requests from controllers.logger import Logger from controllers.keyboard import Keyboard logger = Logger() def getToken(): if 'BOT_TOKEN' in os.environ: return os.environ['BOT_TOKEN'] else: logger.error('Token does not exist') sys.exit(1) def getUrl(): if 'API_URL' in os.environ: return os.environ['API_URL'] else: logger.error('URL does not exist') sys.exit(1) url = f"http://{getUrl()}/api"
class Task: def __init__(self, *args, **kwargs): self.log = Logger(level=LOGLEVEL) self.processes = list() self.start_time = -1 self.stdout = '' self.stderr = '' self.trynum = 1 self.threads = list() self.stopping = False self.update(*args, **kwargs) self.log.info('task %s initialized.' % self.name) def update(self, name, cmd, numprocs=1, umask='666', workingdir=os.getcwd(), autostart=True, autorestart='unexpected', exitcodes=[0], startretries=2, starttime=5, stopsignal='TERM', stoptime=10, env={}, **kwargs): self.name = name self.cmd = cmd self.numprocs = numprocs self.umask = str(umask) if umask else '000' self.workingdir = workingdir self.autostart = autostart self.autorestart = autorestart self.exitcodes = exitcodes self.startretries = startretries self.starttime = starttime self.stopsignal = stopsignal self.stoptime = stoptime self.env = os.environ for key, value in env.items(): self.env[key] = str(value) self.stdout = kwargs.get('stdout', '') self.stderr = kwargs.get('stderr', '') if autostart: if self.is_running: self.restart() else: self.run() def close_fds(self): getattr(self.stdout, 'close', _nop)() getattr(self.stderr, 'close', _nop)() def reopen_stds(self): if getattr(self.stdout, 'closed', True): self.stdout = getattr(self.stdout, 'name', '') if getattr(self.stderr, 'closed', True): self.stderr = getattr(self.stderr, 'name', '') @property def stdout(self): return self._stdout @stdout.setter def stdout(self, path): if not path: self._stdout = subprocess.PIPE return self._stdout = open(path, 'w') @property def stderr(self): return self._stderr @stderr.setter def stderr(self, path): if not path: self._stderr = subprocess.PIPE return self._stderr = open(path, 'w') def _initchildproc(self): os.umask(int(self.umask, 8)) def restart(self, retry=False, from_thread=False): if from_thread and self.stopping: return self.stop(from_thread) self.run(retry=retry) def define_restart_policy(self, process, retry=False): thr = threading.Thread( target=handle_process_restart_behavior, args=( process, self.autorestart, self.exitcodes, lambda *_: self.restart(retry=retry, from_thread=True), ), daemon=True, ) thr.start() self.threads.append(thr) def run(self, retry=False): self.trynum = 1 if not retry else (self.trynum + 1) if self.trynum > self.startretries: self.log.warning('%s reached the maximum number of retries.' % self.name) return self.reopen_stds() self.log.info('Try to start {}. Retry attempt {}, max retries: {}, cmd: `{}`'.format( self.name, self.trynum, self.startretries, self.cmd, )) try: for virtual_pid in range(self.numprocs): process = subprocess.Popen( self.cmd.split(), stderr=self.stderr, stdout=self.stdout, env=self.env, cwd=self.workingdir, preexec_fn=self._initchildproc, ) self.processes.append(process) self.start_time = time.time() if process.returncode in self.exitcodes: self.define_restart_policy(process) self.log.success(( f'{self.name}: process number {virtual_pid} started.' f' Exited directly, with returncode {process.returncode}' )) self.start_time = -2 self.trynum = 1 continue else: try: process.wait(timeout=self.starttime) if process.returncode in self.exitcodes: self.define_restart_policy(process) self.log.success(( f'{self.name}: process number {virtual_pid} started.' f' Exited directly, with returncode {process.returncode}' )) self.start_time = -2 self.trynum = 1 continue except subprocess.TimeoutExpired: self.define_restart_policy(process) self.log.success(f'{self.name}: process number {virtual_pid} started.') self.trynum = 1 continue # retry self.log.info(f'Unexpected returncode {process.returncode}') self.restart(retry=True) except Exception: # retry self.log.warning('%s startup failed.' % self.name) self.restart(retry=True) def stop(self, from_thread=False): self.stopping = True self.close_fds() for process in self.processes: self.log.info(f'Send SIG{self.stopsignal} to {process.pid}.') process.send_signal(getattr(signal, 'SIG' + self.stopsignal)) try: process.wait(self.stoptime) except subprocess.TimeoutExpired: self.log.info(f'Force kill {process.pid}.') process.kill() if not from_thread: for thr in self.threads: thr.join(.1) self.processes = list() self.threads = list() self.start_time = -3 self.stopping = False @property def is_running(self): return self.start_time > 0 @property def uptime(self): for p in self.processes: if p.returncode is not None: if p.returncode in self.exitcodes: self.start_time = -2 else: self.start_time = -3 break if self.start_time == -1: return 'not started' if self.start_time == -2: return 'finished' if self.start_time == -3: return 'stopped' upt = int(time.time() - self.start_time) return '{}:{}:{}'.format(int(upt / 3600), int(upt / 60 % 60), int(upt % 60)) def send_command(self, command): self.log.info(f'task {self.name}, command received {command}.') if command.upper() == 'START': self.run() elif command.upper() == 'RESTART': self.stop() self.run() elif command.upper() == 'STOP': self.stop() else: return {'error': True, 'message': 'Unknown command %s' % command, 'task': self.name} return {'task': self.name, 'message': command.lower() + 'ed'}
class TaskmasterDaemonParser: """TaskmasterDeamonParser class. > See `supervisord`. """ def __init__(self, conf_path): self.conf_path = conf_path self.log = Logger(LOGLEVEL) if not os.path.exists(conf_path): print("") self.log.error('Unknown file or directory %s.' % conf_path) sys.exit(-1) try: with open(conf_path) as conf: self.configuration = config = yaml.load(conf, Loader=Loader) self.log.debug('Configuration readed: %s' % self.configuration) except Exception: raise ParseError('Unable to read configuration file %s' % conf_path) if 'programs' not in config or len(config) > 1: raise ParseError( ('config file is invalid, ' 'bad indentation or "program" section not found.')) self.check_configuration() @classmethod def from_command_line(cls): parser = argparse.ArgumentParser() parser.add_argument( '--config_file', '-c', required=True, type=str, ) args = parser.parse_args() return cls(args.config_file) def diff(self, parser): return diff_dict(self.configuration['programs'], parser.configuration['programs']) def refresh(self): try: parser = TaskmasterDaemonParser(self.conf_path) except ParseError as e: self.log.error(e) os.kill(os.getpid(), signal.SIGKILL) diff = self.diff(parser) self.configuration = parser.configuration return diff def check_configuration(self): programs = self.configuration.get('programs', {}) if not programs: self.configuration['programs'] = dict() self.log.warning('Any programs to be loaded') return for program_name, parameters in programs.items(): for rparam in REQUIRED_PARAMS: if rparam not in parameters: raise ParseError( f'{program_name}: Missing required parameter {rparam}') for parameter_key, pvalue in parameters.items(): if parameter_key not in CONFIGURATION_MAPPING: raise ParseError( f'Unknown parameter "{parameter_key}" in task {program_name}.' ) parameter_mapping = CONFIGURATION_MAPPING[parameter_key] expected_type = _to_list(parameter_mapping['expected_type']) handler = parameter_mapping.get('handler', _no_check) transform = parameter_mapping.get('transform') args = parameter_mapping.get('args', list()) if not isinstance(pvalue, tuple(expected_type)): actual_type = type(pvalue) raise ParseError( f'{program_name}: {parameter_key}: Incorrect type {actual_type} instead of {expected_type}.' ) handler_return, handler_msg = handler(pvalue, *args) if not handler_return: raise ParseError( f'{program_name}: {parameter_key}: {handler_msg}.') if transform is not None: self.configuration['programs'][program_name][ parameter_key] = transform(pvalue)
"""Main application file, with the program loop.""" import time from datetime import datetime, timedelta from constants.log_level import LogLevel as LogLevel from models.task_queue import TaskQueue import settings from constants import constants from models.user_list import UserList from controllers.event_list import EventList from controllers.logger import Logger from controllers.calendar_service import CalendarService from controllers.mattermost_service import MattermostService from models.task import Task logger: Logger = Logger() mattermost_service = MattermostService(logger) task_queue: TaskQueue = TaskQueue(logger, mattermost_service) last_calendar_check = None last_task_queue_check = None user_list: UserList event_list: EventList def need_check(last_check, interval_seconds): if last_check is None: return True # noinspection PyTypeChecker next_check: datetime = last_check + timedelta(seconds=interval_seconds) now = datetime.now()
def on_start(self): app = MDApp.get_running_app() self.parser = Parser(app) self.logger = Logger().init_terminal_instance(app) self.logger.info('App started')
def __init__(self, programs, parser): self.programs = programs self.parser = parser self.log = Logger(level=LOGLEVEL)
class Manager: programs = list() def __init__(self, programs, parser): self.programs = programs self.parser = parser self.log = Logger(level=LOGLEVEL) def stop_all(self): for program in self.programs: program.stop() for thr in program.threads: thr.join(.1) def _get_program_by_name(self, name): for program in self.programs: if program.name == name: return program def _remove_program_by_name(self, name): for program in self.programs: if program.name == name: self.programs.remove(program) def update(self): self.log.info('Received update command.') diff = self.parser.refresh() self.log.info('Affected/new tasks: %s.' % diff) parser = self.parser programs_names = [program.name for program in self.programs] affected = list() if parser.configuration.get('programs', {}): for program_name, _program_params in parser.configuration.get('programs', {}).items(): program_params = copy.deepcopy(_program_params) if program_name in programs_names and program_name not in diff: # not affected continue elif program_name in programs_names: # affected -- w/restart affected.append(program_name) program = self._get_program_by_name(program_name) cmd = program_params.pop('cmd') program.update(program_name, cmd, **program_params) continue # start affected/new programs cmd = program_params.pop('cmd') task = Task(program_name, cmd, **program_params) self.programs.append(task) if program_name in programs_names: programs_names.remove(program_name) for program_name in programs_names: if program_name not in diff or program_name in affected: continue program = self._get_program_by_name(program_name) program.stop() self._remove_program_by_name(program_name) return {"raw_output": "Updated tasks %s" % diff, "updated_tasks": diff} def load_tcp_command(self, request): command = request.get('command', '') args = request.get('args', list()) with_refresh = request.get('with_refresh', False) response = [] if command.upper() == 'UPDATE': ret = self.update() if not with_refresh: return ret for program in self.programs: if program.name in args: args.remove(program.name) ret = program.send_command(command) if 'error' in ret: response.append(dict(raw_output='{}: ERROR ({})'.format( ret['task'], ret['message'], ), **ret)) else: response.append(dict(raw_output='{}: {}'.format( ret['task'], ret['message'], ), **ret)) if response and not with_refresh: return response if len(response) > 1 else response[0] if command.upper() == 'REFRESH' or with_refresh: if args: return [{ "task": program.name, "uptime": program.uptime, "started_processes": len(program.processes), "pids": [p.pid for p in program.processes], } for program in self.programs if program.name in args] return [{ "task": program.name, "uptime": program.uptime, "started_processes": len(program.processes), "pids": [p.pid for p in program.processes], } for program in self.programs] if command.upper() == 'STOP_DAEMON': self.stop_all() raise Exception return { 'raw_output': '%s: ERROR (no such command)' % command, 'error': True, 'input_request': request, 'message': 'no such process', }