예제 #1
0
 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)
예제 #2
0
    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)
예제 #3
0
    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
예제 #4
0
    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)
예제 #5
0
    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()
예제 #6
0
    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()
예제 #7
0
    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()
예제 #8
0
    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
예제 #9
0
    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!')
예제 #10
0
    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()
예제 #11
0
    def _exit(self) -> None:
        if self.exit_count == 0:
            self.exit_count += 1
            fns_log('GrowAutomation service: Farewell!')

        raise SystemExit('Service exited gracefully.')
예제 #12
0
    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