def info(hash): if hash == '0': try: schedules = crontab_manager.get_tasks_with_info() except Exception as exc: app.logger.error('Ошибка при получении crontab: %s', exc) flash(str(exc)) schedules = [] return render_template('info.html', full_name=AppConfig.get_server_name(), remote=False, schedules=schedules, sorted_schedules=sorted(schedules), actions=AppConfig.conf().get('actions'), sorted_actions=sorted( AppConfig.conf().get('actions')), logs=Logging.get_logs_list()) else: s = RemoteServers.find_server(hash) if s is None or not s.state: return redirect(url_for('servers')) resp = RemoteServers.get_remote_server_config(s.url) if len(resp) < 2: from collections import defaultdict flash(resp.get('status', 'Возникла неизвестная ошибка')) resp = defaultdict(dict) return render_template('info.html', full_name=resp['full_name'], hash=s.hash, remote=True, schedules=resp['schedules'], sorted_schedules=sorted(resp['schedules']), actions=resp['actions'], sorted_actions=sorted(resp['actions']), logs=resp['logs'])
def get_config(self): conf = {} conf['host'] = '127.0.0.1' conf['port'] = 5556 if AppConfig.conf().get('web'): conf['host'] = AppConfig.conf().get('web').get('host', '127.0.0.1') conf['port'] = AppConfig.conf().get('web').get('port', 5556) return conf
class webappconf: HOST = '127.0.0.1' if 'web' in AppConfig.conf().keys(): HOST = AppConfig.conf().get('web').get('host', '127.0.0.1') PORT = 5555 if AppConfig.conf().get('web') and 'port' in AppConfig.conf().get( 'web', {}).keys(): PORT = AppConfig.conf().get('web').get('port', '5555') CSRF_ENABLED = True SECRET_KEY = 'you-will-never-guess' LOGIN_MESSAGE = u'Пожалуйста, авторизуйтесь для доступа к этой странице.' DEBUG = False
def start_task(self): """Запускает последовательное выполнение сформированных действий. Если в конфигурации параметр allow_parallel имеет значение False, то перед запуском сначала происходит проверка отсутствия параллельных процессов выполнения заданий и только потом запуск. """ if not AppConfig.conf().get('allow_parallel', True): keywords = [get_executable_filename(), constants.RUN_OPT_NAME] pid = check_process(keywords) if pid: self.logger.error( 'Другое задание уже запущено: PID: %s', pid, ) sys.exit(-1) for action in self.actions: self.logger.info('Запускается действие %s', action.name) try: success = action.start() except KeyboardInterrupt: self.logger.warning('Выполнение прервано нажатием Ctrl+C') break if success: self.logger.info('Выполнено действие %s', action.name) else: self.logger.error( 'Действие %s выполнено неудачно, выполнение остановлено', action.name, ) break
def create_actions(self): """Создаёт действия по описанию из self.action_records. Вызывает self._create_action для всех записей из self.action_records и делает дополнительную обработку (добавляет флаг --dry). """ all_actions_records = AppConfig.conf().setdefault('actions', {}) action_builder = ActionBuilder(all_actions_records) for action_record in self.action_records: dry = False if isinstance(action_record, list): args = arguments.parse_action_record(action_record) action_record = args.action_name dry = args.dry try: action = action_builder(action_record) except Exception as exc: self.logger.error( 'Ошибка при создании действия %s: %s', action_record, exc, ) sys.exit(-1) if action is not None: if dry: action.dry = dry self.logger.debug('Добавлен флаг dry') self.actions.append(action) self.logger.debug('Действие добавлено')
def is_active(self, name): """Проверяет активно ли текущее задание. Args: name: Строка, имя задания Returns: bool, активно или нет Raises: OSError: нет доступа к crontab необходимого пользователя """ user = AppConfig.conf().get('cron', {}).get('cron_user', CURRENT_USER) try: cron = CronTab(user) except OSError as exc: exc = OSError(exc) exc.__cause__ = None raise exc for job in cron: if job.comment == name: return True return False
def get_trigger_filepath(): """Возвращает путь к триггеру. Рекурсивно создаёт директорию необходимую, если на момент инициализации её не существовало. Если в конфигурации отстутствует параметр logging.trigger_filepath, то возвращает None. Returns: Строка, путь к триггеру, или None. """ try: conf = AppConfig.conf() except (FileNotFoundError, ConfigError): trigger_filepath = None else: trigger_filepath = conf.get('logging', {}).get('trigger_filepath') if not trigger_filepath: return trigger_filepath trigger_dirpath = os.path.dirname(trigger_filepath) if trigger_dirpath and not os.path.exists(trigger_dirpath): os.makedirs(trigger_dirpath) return trigger_filepath
def get_log_dirpath(subdir=None): """Возвращает путь к директории логирования. Если директория не сущестувет, то она создаётся. Args: subdir: Строка или None, требуемая поддиректория. Returns: Строку, путь к существующей директории. """ try: conf = AppConfig.conf() except (FileNotFoundError, ConfigError): path = os.path.abspath(DEFAULT_LOGS_PATH) else: path = conf.get('logging', {}).get( 'logs_path', os.path.abspath(DEFAULT_LOGS_PATH), ) if subdir: path = os.path.join(path, subdir) if not os.path.exists(path): os.makedirs(path) return path
def get_server_config(): result = { 'full_name': AppConfig.get_server_name(), 'schedules': crontab_manager.get_tasks_with_info(), 'actions': AppConfig.conf().get('actions'), 'logs': get_logs_list() } return result
def addSchedule(): form = ScheduleForm() if form.validate_on_submit(): try: crontab_manager.update_task(form.name.data, form.cron.data, form.descr.data, form.actions.data) except Exception as exc: flash('Произошла ошибка:'.format(exc)) else: flash('Изменения применены') return redirect(url_for('servers')) return render_template('add_schedule.html', edit=False, form=form, actions=AppConfig.conf().get('actions'), sorted_actions=sorted( AppConfig.conf().get('actions')))
def editSchedule(name): form = ScheduleForm() if not form.validate_on_submit(): sch = crontab_manager.get_task(name) form['name'].data = name form['descr'].data = sch['descr'] form['cron'].data = sch['cron'] form['actions'].data = ', '.join(sch['actions']) else: crontab_manager.update_task(form.name.data, form.cron.data, form.descr.data, form.actions.data) flash('Изменения применены') return redirect(url_for('servers')) return render_template('add_schedule.html', edit=True, form=form, actions=AppConfig.conf().get('actions'), sorted_actions=sorted( AppConfig.conf().get('actions')))
def get_task(self, name): """Возвращает описание задания из конфигурации. Args: name: Строка, имя задания. Returns: dict """ return AppConfig.conf().get('schedule').get(name, None)
def update_task(self, name, cron, descr, actions): actions = [action.strip() for action in actions.split(',')] AppConfig.conf().get('schedule')[name] = { 'descr': descr, 'cron': cron, 'actions': actions, } AppConfig._config.storeAll() if self.is_active(name): self.activate_task(name)
def activate_task(self, name): """Позволяет активировать одно или все задания. Args: name (str): имя задания, если имеет значение "all", то будут задействованы все задания. Raises: ConfigErorr: Если отсутствует задание с данным именем. """ if name == 'all': schedules = self.get_tasks_with_info() else: schedule = self.get_task(name) if schedule is None: raise ConfigError( 'Задания с именем {0} отсутствует'.format(name), ) schedules = {name: schedule} for name, schedule in schedules.items(): crontab = CronTab(user=AppConfig.conf().get('cron').get( 'cron_user', CURRENT_USER, ), ) self._clean_crontab_by_comment(crontab, name) cron_time = schedule.get('cron', None) if cron_time and not CronSlices.is_valid(cron_time): raise ConfigError( 'Неверно указано время в задании {0}'.format(name), ) if schedule.get('all_fields_match', None): cron_time = cron_time.split() job = crontab.new( command=self._generate_command( name=name, wdays=self._parse_cron_wday_field(cron_time[-1]), ), comment=name, ) cron_time[-1] = '*' cron_time = ' '.join(cron_time) else: job = crontab.new( command=self._generate_command(name), comment=name, ) if (cron_time): job.setall(cron_time) crontab.write()
def deactivate_task(self, name): if name == 'all': schedules = self.get_tasks_with_info() else: schedules = {name: ''} for name, _ in schedules.items(): cron = CronTab(user=AppConfig.conf().get('cron').get( 'cron_user', CURRENT_USER, ), ) for job in cron: if job.comment == name: cron.remove(job) cron.write()
def get_tasks_with_info(self): """Возвращает словарь с заданиями и информацией о них. Returns: tasks (dict) с записями вида task_name : {'is_active' : bool, \*\*task_configuration} """ tasks = {} for taskname in sorted(AppConfig.conf().get('schedule').keys()): task_configuration = self.get_task(taskname) try: task_configuration['active'] = self.is_active(taskname) except OSError: task_configuration['active'] = None tasks[taskname] = task_configuration return tasks
def load(self, unit_name, dry): """Загружает действия из требуемого задания. Если нет, то: 1) Проверяет наличие задания с именем unit_name. 2) Если находит: читает его действия и создаёт их. Если нет: ищет действие с именем unit_name и создаёт его. Args: unit_name: Строка, имя требуемого юнита. Если передано действие, то оно превращается в задание с одним действием. dry: Логическое значение, включает тестовый режим. Если True, тоюнит автоматически интерпретируется как действие. Raises: SystemExit, если не удалось найти необходимый юнит. """ appconf = AppConfig.conf() task_record = appconf.setdefault('schedule', {}).get(unit_name) if task_record is not None and not dry: self.action_records = task_record.get('actions') if not self.action_records: self.logger.error( 'Список действий в задании %s пуст', unit_name, ) sys.exit(-1) elif unit_name in appconf.get('actions', {}): action_record = [unit_name] if dry: action_record.append('--dry') self.action_records = [action_record] elif dry: self.logger.error( 'Отсутствует требуемое действие %s', unit_name, ) sys.exit(-1) else: self.logger.error( 'Отсутствует требуемое задание/действие %s', unit_name, ) sys.exit(-1)
def delete_task(self, name): self.deactivate_task(name) AppConfig.conf().get('schedule').pop(name, None) AppConfig._config.storeAll()
def configure_logging(verbose=False): """Конфигурирует логирование для выполнения заданий/действий. Инициализирует и добавляет хэндлеры для логирования. Следующие хэндлеры создаются: - log_default - INFO, в файл. - log_debug - DEBUG, в файл. - trigger - >=WARNING, в триггер файл, если он указан в конфиге. - stdout - DEBUG, в консоль, если указан параметр verbose. Args: verbose: Логическое значение, логировать в stdout. """ try: full_name = '{region}-{project}-{servername}'.format( region=AppConfig._region, project=AppConfig._project, servername=AppConfig.get_server_name(), ) except AttributeError: full_name = 'created-with-error' log_filename = '{full_name}-{time}.log'.format( full_name=full_name, time=AppConfig.get_starttime_str(), ) log_debug_filename = '{full_name}-{time}-debug.log'.format( full_name=full_name, time=AppConfig.get_starttime_str(), ) log_path_info = get_log_dirpath( subdir=AppConfig.get_starttime().strftime('%Y'), ) log_path_debug = get_log_dirpath(subdir='debug') handlers = [ get_default_file_handler( full_name, os.path.join(log_path_info, log_filename), ), get_debug_file_handler( full_name, os.path.join(log_path_debug, log_debug_filename), ), ] if verbose or AppConfig.conf().get('logging', {}).get( 'use_console', False): # Инициализация хэндлера консоли, если стоит verbose или use_console. handlers.append(get_console_handler(full_name)) if get_trigger_filepath(): # Инициализация хэндлера триггера, если к нему указан путь. handlers.append(get_trigger_handler()) logger = logging.getLogger() logger.setLevel(logging.DEBUG) for log_handler in handlers: logger.addHandler(log_handler)