def _status(self): thread_list = self.THREADER.list() detailed_thread_list = '\n'.join( [str(thread.__dict__) for thread in thread_list]) simple_thread_list = [thread.name for thread in thread_list] fns_log(f"Status - threads running: {simple_thread_list}") fns_log(f"Detailed info on running threads:\n{detailed_thread_list}", level=7)
def _signum_log(signum): if signum is not None: try: fns_log( f"Service received signal {signum} \"{sys_exc_info()[0].__name__}\"", level=3) except AttributeError: fns_log(f"Service received signal {signum}", level=3)
def _check_changes(self, changes: dict, check: str) -> bool: result = False for key in self.reload_categories[check]: if changes['changed'][key]: result = True break fns_log(f'Reload - got changes for type {check}: {result}', level=6) return result
def _log(self, output, level: int = 1): try: from core.utils.debug import fns_log, log if len(self.log_cache) > 0: for msg in self.log_cache: log(output=msg['output'], level=msg['level']) self.log_cache = [] fns_log(output=output, level=level) except Exception: self.log_cache.append({'level': level, 'output': output}) if level == 1: journal.send(output)
def reload(self, signum=None, stack=None): fns_log(f"Service received signal {signum}", level=3) fns_log('Service reload -> checking for config changes', level=4) # check current db config against currently loaded config reload_needed, reload_threads, _new_config, _new_config_dict = Reload( object_list=self.CONFIG, config_dict=self.current_config_dict, timers=self.timer_list, ).get() if reload_needed: fns_log('Reload - config has changed. Updating threads.') # update shared config self.CONFIG = _new_config self.current_config_dict = _new_config_dict config.CONFIG = self.CONFIG self._update_config_file() self._init_shared_vars() for old_timer in reload_threads['remove']: self.THREADER.stop_thread(description=old_timer.name) if isinstance(old_timer, (GaOutputModel, GaOutputDevice)): self._reverse_outputs(instance=old_timer) self.timer_list.remove(old_timer) del old_timer self._wait(seconds=config.SVC_WAIT_TIME) for new_timer in reload_threads['add']: self._thread(instance=new_timer) self.THREADER.start_thread(description=new_timer.name) self.timer_list.append(new_timer) fns_log('Reload - Done reloading.') self._status() else: fns_log('Reload - config is up-to-date.') self._run()
def start(self): try: fns_log(f"Service has process id {os_getpid()}", level=7) for instance in self.timer_list: # we'll check if any output devices are active => they shouldn't be at this point in time if self._reverse_outputs(instance=instance): self.THREADER.start_thread(description=instance.name) self.THREADER.stop_thread(description=instance.name) self._wait(seconds=config.SVC_WAIT_TIME) for instance in self.timer_list: self._thread(instance=instance) self.THREADER.start() fns_log('Start - finished starting threads.') self._status() except TypeError as error_msg: fns_log( f"Service encountered an error while starting:\n\"{error_msg}\"" ) self.stop() self._run()
def stop(self, signum=None, stack=None): if self.stop_count >= config.SVC_MAX_STOP_COUNT: self.hard_exit(signum=signum) else: self.stop_count += 1 fns_log('Service is stopping', level=6) fns_log('Stopping service.') self._signum_log(signum=signum) fns_log('Stopping timer threads', level=6) self.THREADER.stop() self._wait(seconds=config.SVC_WAIT_TIME) fns_log('Service stopped.') self._exit()
def _supply_changes(self) -> dict: changes = { 'add': {}, 'replace': {}, 'remove': {}, 'changed': {}, } for key, supply_data in self.new_config_dict.items(): _add = [] _replace = [] _remove = [] _old_supply_data = self.old_config_dict[key] for _id, data in supply_data.items(): if _id not in _old_supply_data: _add.append(_id) elif data != _old_supply_data[_id]: _replace.append(_id) for _id in _old_supply_data.keys(): if _id not in supply_data: _remove.append(_id) changes['add'][key] = _add changes['replace'][key] = _replace changes['remove'][key] = _remove if len(_add) > 0 or len(_replace) > 0 or len(_remove) > 0: changes['changed'][key] = True else: changes['changed'][key] = False fns_log(f'Reload - config raw changes: {changes}', level=8) return changes
def hard_exit(self, signum=None, stack=None): self._signum_log(signum) if self.stop_count >= config.SVC_MAX_STOP_COUNT: fns_log( f"Hard exiting service since it was stopped more than {config.SVC_MAX_STOP_COUNT} times", level=6) fns_log('Stopping service merciless', level=3) fns_log('Service stopped.') raise SystemExit('Service exited merciless!')
def _run(self): try: self._wait(seconds=config.SVC_WAIT_TIME) fns_log('Entering service runtime', level=7) run_last_reload_time = time() run_last_status_time = time() while True: if time() > (run_last_reload_time + config.AGENT.svc_interval_reload): self.reload() break if time() > (run_last_status_time + config.AGENT.svc_interval_status): self._status() run_last_status_time = time() time_sleep(config.SVC_LOOP_INTERVAL) except: try: exc_type, error, _ = sys_exc_info() if str(error).find('Service exited') == -1: fns_log( f"A fatal error occurred: \"{exc_type} - {error}\"") fns_log( f"{format_exc(limit=config.LOG_MAX_TRACEBACK_LENGTH)}") except IndexError: pass if self.exit_count > 0: fns_log('Skipping service stop (gracefully) -> exiting (hard)', level=5) self._exit() else: self.stop()
def _exit(self) -> None: if self.exit_count == 0: self.exit_count += 1 fns_log('GrowAutomation service: Farewell!') raise SystemExit('Service exited gracefully.')
def _get_timer(self) -> dict: timer_updates = { 'add': [], 'remove': [], } fns_log(f'Reload - got any changes: {self.any_changes}', level=6) if self.any_changes: new_timers = get_timer(config_dict=self.new_object_list, system_tasks=False) changes = self._supply_changes() reload_condition = self._check_changes(changes=changes, check='condition') reload_output = self._check_changes(changes=changes, check='output') reload_input = self._check_changes(changes=changes, check='input') # prepare some data to evaluate # output models/groups old_output_models = self.old_object_list[self._get_obj_key( GaOutputModel)] _remove_output_model = changes['remove'][self._get_obj_key( GaOutputModel)].copy() _remove_output_model.extend( changes['replace'][self._get_obj_key(GaOutputModel)]) remove_output_model = [ obj for obj in old_output_models if obj.object_id in _remove_output_model ] _add_output_model = changes['add'][self._get_obj_key( GaOutputModel)].copy() _add_output_model.extend( changes['replace'][self._get_obj_key(GaOutputModel)]) add_output_model = [ obj for obj in self.new_object_list[self._get_obj_key( GaOutputModel)] if obj.object_id in _add_output_model ] # output devices old_output_devices = self.old_object_list[self._get_obj_key( GaOutputDevice)].copy() _remove_output_device = changes['remove'][self._get_obj_key( GaOutputDevice)].copy() _remove_output_device.extend( changes['replace'][self._get_obj_key(GaOutputDevice)]) remove_output_device = [ obj for obj in old_output_devices if obj.object_id in _remove_output_device ] remove_output_device.extend( [ member for parent in remove_output_model for member in parent.member_list ] ) # all children of this model must be replaced because of setting inheritance remove_output_device = set(remove_output_device) _add_output_device = changes['add'][self._get_obj_key( GaOutputDevice)].copy() _add_output_device.extend( changes['replace'][self._get_obj_key(GaOutputDevice)]) add_output_device = [ obj for obj in self.new_object_list[self._get_obj_key( GaOutputDevice)] if obj.object_id in _add_output_device ] add_output_device.extend( [ member for parent in add_output_model for member in parent.member_list ] ) # all children of this model must be replaced because of setting inheritance add_output_device = set(add_output_device) # input models/devices _add_input_model = changes['add'][self._get_obj_key( GaInputModel)].copy() _add_input_model.extend( changes['replace'][self._get_obj_key(GaInputModel)]) _add_input_device = changes['add'][self._get_obj_key( GaInputDevice)].copy() _add_input_device.extend( changes['replace'][self._get_obj_key(GaInputDevice)]) add_input = { self._get_obj_key(GaInputModel): [ obj for obj in self.new_object_list[self._get_obj_key( GaInputModel)] if obj.object_id in _add_input_model ], self._get_obj_key(GaInputDevice): [ obj for obj in self.new_object_list[self._get_obj_key( GaInputDevice)] if obj.object_id in _add_input_device ] } # simple additions fns_log(f"Reload - additions: {changes['add']}", level=6) if reload_condition: timer_updates['add'].extend([ timer for timer in new_timers if isinstance(timer, GaConditionGroup) ]) if reload_input: for key in self.reload_categories['input']: if len(changes['add'][key]) > 0: timer_updates['add'].extend([ obj for obj in add_input[key] if obj.object_id in changes['add'][key] ]) # replacement and deletions => we sometimes have to link new instances with existing ones fns_log(f"Reload - replacements: {changes['replace']}", level=6) fns_log(f"Reload - removal: {changes['remove']}", level=6) for timer in self.old_timers: if reload_condition and isinstance(timer, GaConditionGroup): # if condition changed => update the condition timer_updates['remove'].append(timer) if reload_input and isinstance(timer, (GaInputDevice, GaInputModel)): # if a input changed => replace the input key = self._get_obj_key(type(timer)) if len(changes['remove'][key]) > 0 or len( changes['replace'][key]) > 0: if timer.object_id in changes['remove'][ key] or timer.object_id in changes['replace'][ key]: timer_updates['remove'].append(timer) if len(changes['replace'][key]) > 0: try: timer_updates['add'].append([ obj for obj in add_input[key] if obj.object_id == timer.object_id ][0]) except IndexError: # if this model should not be replaced it will not be matched pass if reload_output and not reload_condition and isinstance( timer, GaConditionGroup): # if a output changed => check all conditions for links to it and update them # add new 'versions' of output devices and models to the condition lists fns_log(f"Reload - updating outputs of condition {timer}", level=7) for new_model in add_output_model: try: old_model = [ old_model for old_model in old_output_models if old_model.object_id == new_model.object_id ][0] if old_model in timer.output_group_list: fns_log( f"Reload - updating output {old_model} liked to condition {timer}", level=7) timer.output_group_list.append(new_model) if old_model.object_id in remove_output_model: timer.output_group_list.remove(old_model) remove_output_model.remove( old_model.object_id) del old_model except IndexError: # if this model should not be replaced it will not be matched continue for new_device in add_output_device: try: old_device = [ old_device for old_device in old_output_devices if old_device.object_id == new_device.object_id ][0] if old_device in timer.output_object_list: fns_log( f"Reload - updating output {old_device} liked to condition {timer}", level=7) timer.output_object_list.append(new_device) if old_device.object_id in remove_output_device: timer.output_object_list.remove(old_device) remove_output_device.remove( old_device.object_id) del old_device except IndexError: # if this model should not be replaced it will not be matched continue # remove the old 'versions' of output devices and models from condition lists for old_model in remove_output_model: if old_model in timer.output_group_list: timer.output_group_list.remove(old_model) del old_model for old_device in remove_output_device: if old_device in timer.output_object_list: timer.output_object_list.remove(old_device) del old_device fns_log(f'Reload - got timer changes: {timer_updates}', level=6) return timer_updates