Exemple #1
0
    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)
Exemple #2
0
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
Exemple #3
0
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}')
Exemple #4
0
    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()
Exemple #5
0
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(
Exemple #6
0
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"
Exemple #7
0
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'}
Exemple #8
0
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)
Exemple #9
0
"""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()
Exemple #10
0
 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')
Exemple #11
0
    def __init__(self, programs, parser):
        self.programs = programs
        self.parser = parser

        self.log = Logger(level=LOGLEVEL)
Exemple #12
0
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',
        }