diff --git a/bin/linspector b/bin/linspector index 425caed..b38fa71 100755 --- a/bin/linspector +++ b/bin/linspector @@ -32,7 +32,7 @@ from linspector.core.environment import Environment from linspector.core.linspector import Linspector from linspector.core.monitors import Monitors -__version__ = '0.19.10' +__version__ = '0.19.11.dev1' __author__ = 'Johannes Findeisen ' logger = logging.getLogger('linspector') @@ -125,6 +125,7 @@ def main(): try: linspector = Linspector(configuration, environment, monitors, plugins, scheduler) + #linspector.print_debug() except Exception as err: logger.critical('[linspector] core initialization error: {0}'.format(err)) sys.exit(1) diff --git a/etc/linspector.conf b/etc/linspector.conf index cc81b1a..f67176a 100644 --- a/etc/linspector.conf +++ b/etc/linspector.conf @@ -24,7 +24,7 @@ members = superadmin@example.com,developers@example.com ; scheduler configuration start_scheduler = true max_threads = 3500 -max_processes = 100 +max_processes = 10 timezone = CET ; hostgroup parents; if the hostgroup "group1" is down, don't alert for the hosts in group2. see TODO.txt for more diff --git a/etc/monitors/network1/gateway.conf b/etc/monitors/network1/gateway.conf index 0aecc1b..6a0ee82 100644 --- a/etc/monitors/network1/gateway.conf +++ b/etc/monitors/network1/gateway.conf @@ -19,6 +19,7 @@ tasks = redis ; internally to single hosts to become a monitor for each host internally. some more ideas are in ;) ; hosts with added hostgroup defined in main configuration file hosts = 192.168.0.1,192.168.23.24,@group1,@testgroup1 +hostgroup = group1 user = USERNAME password = PASSWORD info = Cable Provider diff --git a/etc/monitors/test/test1.conf b/etc/monitors/test/test1.conf index f0b9f1e..8a29e22 100644 --- a/etc/monitors/test/test1.conf +++ b/etc/monitors/test/test1.conf @@ -1,2 +1,3 @@ [monitor] -service = misc.dummy \ No newline at end of file +service = misc.dummy +interval = 1 \ No newline at end of file diff --git a/etc/monitors/test/test10.conf b/etc/monitors/test/test10.conf new file mode 100644 index 0000000..d0691df --- /dev/null +++ b/etc/monitors/test/test10.conf @@ -0,0 +1,3 @@ +[monitor] +service = misc.dummy +interval = 60 diff --git a/etc/monitors/test/test2.conf b/etc/monitors/test/test2.conf new file mode 100644 index 0000000..d0691df --- /dev/null +++ b/etc/monitors/test/test2.conf @@ -0,0 +1,3 @@ +[monitor] +service = misc.dummy +interval = 60 diff --git a/etc/monitors/test/test3.conf b/etc/monitors/test/test3.conf new file mode 100644 index 0000000..079c034 --- /dev/null +++ b/etc/monitors/test/test3.conf @@ -0,0 +1,3 @@ +[monitor] +service = misc.dummy +interval = 120 diff --git a/etc/monitors/test/test4.conf b/etc/monitors/test/test4.conf new file mode 100644 index 0000000..d032ac7 --- /dev/null +++ b/etc/monitors/test/test4.conf @@ -0,0 +1,3 @@ +[monitor] +service = misc.dummy +interval = 50 diff --git a/etc/monitors/test/test5.conf b/etc/monitors/test/test5.conf new file mode 100644 index 0000000..18bd318 --- /dev/null +++ b/etc/monitors/test/test5.conf @@ -0,0 +1,3 @@ +[monitor] +service = misc.dummy +interval = 62 diff --git a/etc/monitors/test/test6.conf b/etc/monitors/test/test6.conf new file mode 100644 index 0000000..01cd045 --- /dev/null +++ b/etc/monitors/test/test6.conf @@ -0,0 +1,3 @@ +[monitor] +service = misc.dummy +interval = 30 diff --git a/etc/monitors/test/test7.conf b/etc/monitors/test/test7.conf new file mode 100644 index 0000000..d0691df --- /dev/null +++ b/etc/monitors/test/test7.conf @@ -0,0 +1,3 @@ +[monitor] +service = misc.dummy +interval = 60 diff --git a/etc/monitors/test/test8.conf b/etc/monitors/test/test8.conf new file mode 100644 index 0000000..d0691df --- /dev/null +++ b/etc/monitors/test/test8.conf @@ -0,0 +1,3 @@ +[monitor] +service = misc.dummy +interval = 60 diff --git a/etc/monitors/test/test9.conf b/etc/monitors/test/test9.conf new file mode 100644 index 0000000..cfd41ce --- /dev/null +++ b/etc/monitors/test/test9.conf @@ -0,0 +1,3 @@ +[monitor] +service = misc.dummy +interval = 240 diff --git a/linspector/core/configuration.py b/linspector/core/configuration.py index 4b59961..3f67291 100644 --- a/linspector/core/configuration.py +++ b/linspector/core/configuration.py @@ -7,9 +7,7 @@ import configparser import glob import os -from logging import getLogger - -logger = getLogger('linspector') +from linspector.core.helpers import log # TODO: check for all required configuration options and set defaults if needed. do this only for @@ -21,7 +19,8 @@ class Configuration: self.__configuration_path = configuration_path self.__environment = environment - logger.info('reading configuration file: ' + configuration_path + '/linspector.conf') + log('info', __name__, 'reading configuration file: ' + configuration_path + + '/linspector.conf') if os.path.isfile(configuration_path + '/linspector.conf'): try: self.__configuration.read(configuration_path + '/linspector.conf', 'utf-8') diff --git a/linspector/core/environment.py b/linspector/core/environment.py index af59bb5..99b6e17 100644 --- a/linspector/core/environment.py +++ b/linspector/core/environment.py @@ -3,9 +3,7 @@ This file is part of Linspector (https://linspector.org/) Copyright (c) 2022 Johannes Findeisen . All Rights Reserved. See LICENSE (MIT license) """ -from logging import getLogger - -logger = getLogger('linspector') +from linspector.core.helpers import log class Environment: @@ -13,6 +11,7 @@ class Environment: Object for storing environment variables at runtime. These variables must not affect the stability or runtime of Linspector. """ + def __init__(self): self.__env = {} @@ -20,17 +19,20 @@ class Environment: if key in self.__env: return self.__env[key] else: - logger.info('environment var "' + key + '" not found! could be that it is set later at ' - 'runtime. if you encounter any errors ' - 'executing monipyd, something is wrong in the ' - 'logic of the code. please consider reporting ' - 'this as a bug! btw. INFO is not an ERROR! ' - 'monipyd should work even with missing ' - 'environment variables.') + log('warning', __name__, 'environment var "' + key + '" not found! could be that it is ' + 'set later at runtime. if you ' + 'encounter any errors executing ' + 'linspector, something is wrong ' + 'in the logic of the code. please ' + 'consider reporting this as a ' + 'bug! btw. WARNING is not an ' + 'ERROR! Linspector should work ' + 'even with missing environment ' + 'variables.') return None def set_env_var(self, key, value): if self.__env[key]: - logger.warning('environment var "' + key + ' existed and was overwritten!') + log('warning', __name__, 'environment var "' + key + ' existed and was overwritten!') self.__env[key] = value diff --git a/linspector/core/helpers.py b/linspector/core/helpers.py new file mode 100644 index 0000000..c43e883 --- /dev/null +++ b/linspector/core/helpers.py @@ -0,0 +1,21 @@ +""" +This file is part of Linspector (https://linspector.org/) +Copyright (c) 2022 Johannes Findeisen . All Rights Reserved. +See LICENSE (MIT license) +""" +from logging import getLogger + +logger = getLogger('linspector') + + +def log(level, name, msg): + if level == 'critical': + logger.critical('[' + name + '] ' + str(msg)) + if level == 'error': + logger.error('[' + name + '] ' + str(msg)) + elif level == 'warning': + logger.warning('[' + name + '] ' + str(msg)) + elif level == 'info': + logger.info('[' + name + '] ' + str(msg)) + elif level == 'debug': + logger.debug('[' + name + '] ' + str(msg)) diff --git a/linspector/core/linspector.py b/linspector/core/linspector.py index 935416c..61ed984 100644 --- a/linspector/core/linspector.py +++ b/linspector/core/linspector.py @@ -3,19 +3,19 @@ This file is part of Linspector (https://linspector.org/) Copyright (c) 2022 Johannes Findeisen . All Rights Reserved. See LICENSE (MIT license) """ +import datetime import importlib - -from logging import getLogger +import random from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.jobstores.memory import MemoryJobStore -from apscheduler.executors.pool import ThreadPoolExecutor # , ProcessPoolExecutor +from apscheduler.executors.pool import ThreadPoolExecutor, ProcessPoolExecutor -logger = getLogger('linspector') +from linspector.core.helpers import log -def job_function(job): - job.handle_call() +def job_function(monitor): + monitor.handle_call() class Linspector: @@ -23,19 +23,20 @@ class Linspector: def __init__(self, configuration, environment, monitors, plugins, scheduler): self.__configuration = configuration self.__environment = environment + self.__jobs = [] self.__monitors = monitors self.__plugin_list = None self.__plugins = plugins self.__scheduler = scheduler # load plugins - logger.info('loading plugins...') + log('info', __name__, 'loading plugins...') if configuration.get_option('linspector', 'plugins'): plugin_list = configuration.get_option('linspector', 'plugins') self.__plugin_list = plugin_list.split(',') for plugin_option in plugin_list.split(','): if plugin_option not in plugins: - logger.info('loading plugin: ' + plugin_option) + log('info', __name__, 'loading plugin: ' + plugin_option) plugin_package = 'linspector.plugins.' + plugin_option.lower() plugin_module = importlib.import_module(plugin_package) plugin = plugin_module.get(configuration, environment, self) @@ -45,10 +46,10 @@ class Linspector: 'memory': MemoryJobStore() } executors = { - 'default': ThreadPoolExecutor(int(configuration.get_option('linspector', - 'max_threads'))), - #'default': ProcessPoolExecutor(int(configuration.get_option('linspector', - # 'max_processes'))) + #'default': ThreadPoolExecutor(int(configuration.get_option('linspector', + # 'max_threads'))), + 'default': ProcessPoolExecutor(int(configuration.get_option('linspector', + 'max_processes'))) } job_defaults = { 'max_instances': 10000 @@ -57,6 +58,24 @@ class Linspector: executors=executors, job_defaults=job_defaults) + start_date = datetime.datetime.now() + log('debug', __name__, monitors.get_monitors()) + monitors = self.__monitors.get_monitors() + for monitor in monitors: + time_delta = round(random.uniform(1.00, 10.00), 2) + new_start_date = start_date + datetime.timedelta(seconds=time_delta) + monitor_job = monitors.get(monitor) + interval = monitor_job.get_interval() + scheduler_job = scheduler['linspector'].add_job(job_function, 'interval', + start_date=new_start_date, + seconds=interval, timezone="CET", + args=[monitors.get(monitor)]) + + monitor_job.set_job(scheduler_job) + self.__jobs.append(monitor_job) + log('info', __name__, 'scheduling job ' + monitor + ' with delta ' + str(time_delta) + + ' @' + str(new_start_date) + ' running service ') + if configuration.get_option('linspector', 'start_scheduler') == 'true': self.__scheduler['linspector'].start() @@ -64,7 +83,7 @@ class Linspector: def print_debug(self): # example on how to access the monitor objects in monitors monitors = self.__monitors.get_monitors() - print(__file__ + ' (59): ' + str(monitors)) + print(__file__ + ' (78): ' + str(monitors)) for monitor in monitors: - print(__file__ + ' (61): ' + monitors.get(monitor).get_identifier()) - print(__file__ + ' (62): ' + monitors.get(monitor).get_service()) + print(__file__ + ' (80): ' + monitors.get(monitor).get_identifier()) + print(__file__ + ' (81): ' + monitors.get(monitor).get_service()) diff --git a/linspector/core/linspectord.py b/linspector/core/linspectord.py index 1e2e1e7..608a1eb 100644 --- a/linspector/core/linspectord.py +++ b/linspector/core/linspectord.py @@ -9,9 +9,7 @@ import signal import sys import time -from logging import getLogger - -logger = getLogger('linspector') +from linspector.core.helpers import log # TODO: there is a bug when stopping the daemon. the pid_file is not being deleted. NEEDS A FIX! @@ -24,7 +22,7 @@ class Linspectord: try: self.__pid_file = configuration.get_option('linspector', 'pid_file') except Exception as err: - logger.critical(str('daemonize error (no pid_file set): {0}'.format(err))) + log('critical', __name__, str('daemonize error (no pid_file set): {0}'.format(err))) def daemonize(self): # daemonize the class using the UNIX double fork mechanism. @@ -36,7 +34,7 @@ class Linspectord: # exit first parent. sys.exit(0) except OSError as err: - logger.critical(str('fork #1 failed: {0}'.format(err))) + log('critical', __name__, str('fork #1 failed: {0}'.format(err))) sys.exit(1) # decouple from parent environment. @@ -51,7 +49,7 @@ class Linspectord: # Exit from second parent. sys.exit(0) except OSError as err: - logger.critical(str('fork #2 failed: {0}'.format(err))) + log('critical', __name__, str('fork #2 failed: {0}'.format(err))) sys.exit(1) # redirect standard file descriptors. @@ -77,7 +75,7 @@ class Linspectord: def start(self): # start the daemon. check for a pidfile to see if the daemon already runs before. - logger.info('starting daemon using pid_file: ' + self.__pid_file) + log('info', __name__, 'starting daemon using pid_file: ' + self.__pid_file) try: with open(self.__pid_file, 'r') as pf: pid = int(pf.read().strip()) @@ -86,7 +84,7 @@ class Linspectord: if pid: message = 'pid_file {0} already exist. daemon already running?' - logger.critical(str(message.format(self.__pid_file))) + log('critical', __name__, str(message.format(self.__pid_file))) sys.exit(1) # start the daemon. @@ -95,7 +93,7 @@ class Linspectord: def stop(self): # stop the daemon. - logger.info('stopping daemon using pid_file: ' + self.__pid_file) + log('info', __name__, 'stopping daemon using pid_file: ' + self.__pid_file) # get the pid from the pid file. try: with open(self.__pid_file, 'r') as pf: @@ -105,7 +103,7 @@ class Linspectord: if not pid: message = 'pid_file {0} does not exist. daemon not running?' - logger.error(str(message.format(self.__pid_file))) + log('error', __name__, str(message.format(self.__pid_file))) return # not an error in a restart # try killing the daemon process. @@ -119,12 +117,12 @@ class Linspectord: if os.path.exists(self.__pid_file): os.remove(self.__pid_file) else: - logger.critical(str(err.args)) + log('critical', __name__, str(err.args)) sys.exit(1) def restart(self): # restart the daemon. - logger.info('restarting daemon using pid_file: ' + self.__pid_file) + log('info', __name__, 'restarting daemon using pid_file: ' + self.__pid_file) self.stop() self.start() diff --git a/linspector/core/logger.py b/linspector/core/logger.py index 7f2887f..2e94106 100644 --- a/linspector/core/logger.py +++ b/linspector/core/logger.py @@ -3,9 +3,7 @@ This file is part of Linspector (https://linspector.org/) Copyright (c) 2022 Johannes Findeisen . All Rights Reserved. See LICENSE (MIT license) """ -from logging import getLogger - -logger = getLogger('linspector') +from linspector.core.helpers import log # The logger can be used by any monitor to write arbitrary data to any arbitrary place. diff --git a/linspector/core/model.py b/linspector/core/model.py index 39e3726..77692ac 100644 --- a/linspector/core/model.py +++ b/linspector/core/model.py @@ -3,9 +3,7 @@ This file is part of Linspector (https://linspector.org/) Copyright (c) 2022 Johannes Findeisen . All Rights Reserved. See LICENSE (MIT license) """ -from logging import getLogger - -logger = getLogger('linspector') +from linspector.core.helpers import log # This class can maybe be used for a general data model for Linspector data processing. currently diff --git a/linspector/core/monitor.py b/linspector/core/monitor.py index c30b9cd..d01937c 100644 --- a/linspector/core/monitor.py +++ b/linspector/core/monitor.py @@ -5,9 +5,11 @@ See LICENSE (MIT license) """ import importlib -from logging import getLogger +from datetime import datetime +from binascii import crc32 -logger = getLogger('linspector') +from linspector.core.helpers import log +from linspector.core.task import Task, TaskExecutor class Monitor: @@ -17,14 +19,38 @@ class Monitor: self.__configuration = configuration self.__environment = environment self.__identifier = identifier + self.__interval = int(monitor_configuration.get('monitor', 'interval')) self.__monitor_configuration = monitor_configuration self.__notification_list = [] - self.__service = None + self.__service = monitor_configuration.get('monitor', 'service') self.__notifications = notifications self.__services = services self.__task_list = [] # put tasks for the dedicated job here. self.__tasks = tasks + self.service = self.__service + self.host = monitor_configuration.get('monitor', 'host') + #self.members = members + #self.core = core + self.hostgroup = monitor_configuration.get('monitor', 'hostgroup') + self.job_threshold = 0 + self.enabled = True + self.scheduler_job = None + self.job_id = self.hex_string() + + """ + NONE job was not executed + OK when everything is fine + WARNING when a job has errors but not the threshold overridden + RECOVER when a job recovers e.g. the threshold decrements (not implemented) + ERROR when a jobs threshold is overridden + UNKNOWN when a job throws an exception which is not handled by the job itself (not implemented) + """ + self.status = "NONE" + self.last_execution = None + self.monitor_information = MonitorInformation(self.job_id, self.hostgroup, self.host, + self.service) + if configuration.get_option('linspector', 'notifications') or \ monitor_configuration.get('monitor', 'notifications'): @@ -47,7 +73,7 @@ class Monitor: if notification_option not in notifications: notification_package = 'linspector.notifications.' + notification_option.lower() notification_module = importlib.import_module(notification_package) - notification = notification_module.get(configuration, environment) + notification = notification_module.create(configuration, environment) notifications[notification_option.lower()] = notification if self.__monitor_configuration.get('monitor', 'service'): @@ -57,7 +83,7 @@ class Monitor: service_module = importlib.import_module(service_package) self.__service = monitor_configuration.get('monitor', 'service').lower() - service = service_module.get(configuration, environment) + service = service_module.create(configuration, environment) services[monitor_configuration.get('monitor', 'service').lower()] = service #service.execute(self) @@ -83,14 +109,201 @@ class Monitor: if task_option not in tasks: task_package = 'linspector.tasks.' + task_option.lower() task_module = importlib.import_module(task_package) - task = task_module.get(configuration, environment) + task = task_module.create(configuration, environment) tasks[task_option.lower()] = task def get_identifier(self): return self.__identifier + def get_interval(self): + return self.__interval + + def get_service(self): + return self.__service + # currently only used for testing but maybe i will add get functions for all known variables. # but not all variables can be known because all monitors are different. only the service # implementation can know all variables which are being used inside the service. def get_service(self): - return self.__monitor_configuration.get('monitor', 'service') + return self.__service + + def __str__(self): + return str(self.__dict__) + + def __hex__(self): + return hex(crc32(bytes(self.hostgroup + self.host + self.service, 'utf-8'))) + + def hex_string(self): + ret = self.__hex__() + if ret[0] == "-": + ret = ret[3:] + else: + ret = ret[2:] + while len(ret) < 8: + ret = "0" + ret + return ret + + def get_job_id(self): + return self.job_id + + def set_job(self, scheduler_job): + self.scheduler_job = scheduler_job + + def set_enabled(self, enabled=True): + self.enabled = enabled + + def handle_threshold(self, service_threshold, execution_successful): + if execution_successful: + if self.job_threshold > 0: + if "threshold_reset" in self.core and self.core["threshold_reset"]: + #logger.info("Job " + self.get_job_id() + ", Threshold Reset") + self.job_threshold = 0 + else: + #logger.info("Job " + self.get_job_id() + ", Threshold Decrement") + self.job_threshold -= 1 + + self.status = "OK" + self.monitor_information.set_status(self.status) + self.monitor_information.inc_job_overall_wins() + else: + self.status = "WARNING" + self.monitor_information.set_status(self.status) + self.monitor_information.inc_job_overall_fails() + self.job_threshold += 1 + + if self.job_threshold >= service_threshold: + #logger.info("Job " + self.get_job_id() + ", Threshold reached!") + self.status = "ERROR" + self.monitor_information.set_status(self.status) + + def handle_tasks(self, monitor_information): + for task in self.__tasks: + if self.status.lower() in task.get_task_type().lower(): + log('debug', __name__, 'executing task of type: ' + self.status) + # tasks can but should not be executed here. putting them in a queue is the better + # solution to execute them in a serial process. + #TaskExecutor.instance().schedule_task(monitor_information, task) + + def handle_call(self): + log('info', __name__, "handle call to identifier: " + self.__identifier) + #logger.debug("handle call") + #logger.debug(self.service) + if self.enabled: + self.last_execution = None + try: + self.last_execution = MonitorExecution(self.get_host()) + self.service.execute(self.last_execution) + except Exception as err: + log.debug('debug', __name__, err) + + self.last_execution.set_execution_end() + + self.handle_threshold(self.service.get_threshold(), + self.last_execution.was_successful()) + + #logger.info("Job " + self.get_job_id() + + # ", Code: " + str(self.last_execution.get_error_code()) + + # ", Message: " + str(self.last_execution.get_message())) + + self.monitor_information.set_response_message( + self.last_execution.get_response_message(self)) + + self.handle_tasks(self.monitor_information) + else: + log('info', __name__, "job " + self.get_job_id() + " disabled") + + def get_host(self): + return self.host + + def get_hostgroup(self): + return self.hostgroup + + +class MonitorExecution: + def __init__(self, host): + self.execution_start = datetime.now() + self.execution_end = -1 + self.host = host + self.error_code = -1 + self.message = None + self.kwargs = None + + def get_host_name(self): + return self.host + + def get_message(self): + return self.message + + def get_kwargs(self): + return self.kwargs + + def set_execution_end(self): + self.execution_end = datetime.now() + + def get_error_code(self): + return self.error_code + + def was_successful(self): + return self.get_error_code() == 0 + + def set_result(self, error_code=0, message="", kwargs=None): + self.error_code = error_code + self.message = message + self.kwargs = kwargs + + def get_response_message(self, job): + msg = str(job.status) + " [" + job.service.get_config_name() + ": " + str(job.get_job_id()) + "] " + \ + str(job.get_hostgroup()) + " " + str(job.get_host()) + if self.get_message() is not None: + msg += " " + str(self.get_message()) + if self.get_kwargs() is not None: + msg += " " + str(self.get_kwargs()) + return msg + + +class MonitorInformation: + def __init__(self, job_id, hostgroup, host, service): + self.job_id = job_id + self.hostgroup = hostgroup + self.host = host + self.service = service + + self.response_massage = None + self.period = None + self.next_run = None + self.runs = 0 + self.enabled = None + self.threshold = 0 + self.fails = 0 + self.job_overall_fails = 0 + self.job_overall_wins = 0 + self.last_execution = None + self.last_run = None + self.last_fail = None + self.last_success = None + self.last_disabled = None + self.last_enabled = None + self.last_threshold_override = None + self.last_escalation = None + self.status = "NONE" + + def inc_job_overall_fails(self): + self.job_overall_fails += 1 + + def inc_job_overall_wins(self): + self.job_overall_wins += 1 + + def get_job_id(self): + return self.job_id + + def get_response_message(self): + return self.response_massage + + def set_response_message(self, msg): + self.response_massage = msg + + def get_status(self): + return self.status + + def set_status(self, status): + self.status = status diff --git a/linspector/core/monitors.py b/linspector/core/monitors.py index 56b6bfa..70324dc 100644 --- a/linspector/core/monitors.py +++ b/linspector/core/monitors.py @@ -8,12 +8,8 @@ import copy import glob import os -from logging import getLogger - from linspector.core.monitor import Monitor - - -logger = getLogger('linspector') +from linspector.core.helpers import log # monitors may could / should be added (and maybe changed) at runtime to add new monitors without @@ -27,12 +23,12 @@ class Monitors: self.__monitors = {} monitor_groups = os.listdir(self.__configuration.get_configuration_path() + '/monitors/') - logger.debug('[' + __name__ + '] monitor groups: ' + str(monitor_groups)) + log('debug', __name__, 'monitor groups: ' + str(monitor_groups)) monitor_configuration = configparser.ConfigParser() for monitor_group in monitor_groups: monitors_file_list = glob.glob(self.__configuration.get_configuration_path() + '/monitors/' + monitor_group + '/*.conf') - logger.debug('[' + __name__ + '] monitor files: ' + str(monitors_file_list)) + log('debug', __name__, 'monitor files: ' + str(monitors_file_list)) for monitor_file in monitors_file_list: monitor_configuration.read(monitor_file, 'utf-8') @@ -41,9 +37,9 @@ class Monitors: # create Monitor() object and copy monitor_configuration for each instance because # they else refer to the same object. - self.__monitors[identifier] = Monitor(configuration, environment, identifier, + self.__monitors[identifier] = copy.deepcopy(Monitor(configuration, environment, identifier, copy.deepcopy(monitor_configuration), - notifications, services, tasks) + notifications, services, tasks)) def get_monitors(self): return self.__monitors diff --git a/linspector/core/notification.py b/linspector/core/notification.py index fdfcb4b..a5b73fb 100644 --- a/linspector/core/notification.py +++ b/linspector/core/notification.py @@ -3,9 +3,7 @@ This file is part of Linspector (https://linspector.org/) Copyright (c) 2022 Johannes Findeisen . All Rights Reserved. See LICENSE (MIT license) """ -from logging import getLogger - -logger = getLogger('linspector') +from linspector.core.helpers import log class Notification: diff --git a/linspector/core/periods.py b/linspector/core/periods.py index ff4bcd2..4ea7c3e 100644 --- a/linspector/core/periods.py +++ b/linspector/core/periods.py @@ -3,10 +3,9 @@ This file is part of Linspector (https://linspector.org/) Copyright (c) 2022 Johannes Findeisen . All Rights Reserved. See LICENSE (MIT license) """ -from logging import getLogger from apscheduler.util import convert_to_datetime -logger = getLogger('linspector') +from linspector.core.helpers import log class Period(object): diff --git a/linspector/core/plugin.py b/linspector/core/plugin.py index cf0805b..d3f885b 100644 --- a/linspector/core/plugin.py +++ b/linspector/core/plugin.py @@ -3,9 +3,7 @@ This file is part of Linspector (https://linspector.org/) Copyright (c) 2022 Johannes Findeisen . All Rights Reserved. See LICENSE (MIT license) """ -from logging import getLogger - -logger = getLogger('linspector') +from linspector.core.helpers import log class Plugin: diff --git a/linspector/core/service.py b/linspector/core/service.py index a97be7f..9061b57 100644 --- a/linspector/core/service.py +++ b/linspector/core/service.py @@ -3,9 +3,7 @@ This file is part of Linspector (https://linspector.org/) Copyright (c) 2022 Johannes Findeisen . All Rights Reserved. See LICENSE (MIT license) """ -from logging import getLogger - -logger = getLogger('linspector') +from linspector.core.helpers import log class Service: diff --git a/linspector/core/task.py b/linspector/core/task.py index 4cfb720..f270a75 100644 --- a/linspector/core/task.py +++ b/linspector/core/task.py @@ -3,13 +3,100 @@ This file is part of Linspector (https://linspector.org/) Copyright (c) 2022 Johannes Findeisen . All Rights Reserved. See LICENSE (MIT license) """ -from logging import getLogger +from queue import Queue +from threading import Event, Thread -logger = getLogger('linspector') +from linspector.core.helpers import log +from linspector.utils.singleton import Singleton + +KEY_TYPE = "type" +KEY_ARGS = "args" +KEY_CLASS = "class" class Task: - - def __init__(self, configuration, environment): + def __init__(self, configuration, environment, **kwargs): self.__configuration = configuration self._environment = environment + + self._args = {} + if KEY_ARGS in kwargs: + self.add_arguments(kwargs[KEY_ARGS]) + elif self.needs_arguments(): + raise Exception("Error: needs arguments but none provided!") + + if KEY_CLASS in kwargs: + self.name = kwargs[KEY_CLASS] + else: + self.name = self.__class__ + + self._type = None + if KEY_TYPE in kwargs: + self._type = kwargs[KEY_TYPE] + + def get_task_type(self): + return str(self._type) + + def get_config_name(self): + return str(self.name) + + def add_arguments(self, args): + for key, val in args.items(): + self._args[key] = val + + def get_arguments(self): + return self._args + + #def set_member(self, member): + # self.member = member + + def needs_arguments(self): + return False + + def execute(self, job): + try: + self.execute(job) + except Exception as e: + #logger.debug("Task execute failed!!!") + raise e + + +@Singleton +class TaskExecutor: + def __init__(self): + self.queue = Queue() + self.taskInfos = [] + task_thread = Thread(target=self._run_worker_thread) + self._instantEnd = False + self._running = True + task_thread.daemon = True + task_thread.start() + + def _run_worker_thread(self): + while self.is_running() or not self.is_instant_end(): + + try: + msg, task = self.queue.get() + if task: + log('debug', __name__, "starting task execution...") + #task.execute(msg) + self.queue.task_done() + + except Exception as err: + log('error', __name__, "error " + str(err)) + + def is_instant_end(self): + return self._instantEnd + + def is_running(self): + return self._running + + def stop(self): + self._running = False + + def stop_immediately(self): + self._running = False + self._instantEnd = True + + def schedule_task(self, msg, task): + self.queue.put((msg, task)) diff --git a/linspector/notifications/call.py b/linspector/notifications/call.py index 42cd94c..29729a0 100644 --- a/linspector/notifications/call.py +++ b/linspector/notifications/call.py @@ -3,14 +3,11 @@ This file is part of Linspector (https://linspector.org/) Copyright (c) 2022 Johannes Findeisen . All Rights Reserved. See LICENSE (MIT license) """ -from logging import getLogger - +from linspector.core.helpers import log from linspector.core.notification import Notification -logger = getLogger('linspector') - -def get(configuration, environment): +def create(configuration, environment): return CallNotification(configuration, environment) diff --git a/linspector/notifications/email.py b/linspector/notifications/email.py index a6e947b..d57616d 100644 --- a/linspector/notifications/email.py +++ b/linspector/notifications/email.py @@ -3,14 +3,11 @@ This file is part of Linspector (https://linspector.org/) Copyright (c) 2022 Johannes Findeisen . All Rights Reserved. See LICENSE (MIT license) """ -from logging import getLogger - +from linspector.core.helpers import log from linspector.core.notification import Notification -logger = getLogger('linspector') - -def get(configuration, environment): +def create(configuration, environment): return EmailNotification(configuration, environment) diff --git a/linspector/notifications/sms.py b/linspector/notifications/sms.py index 8bd7905..80f3dcf 100644 --- a/linspector/notifications/sms.py +++ b/linspector/notifications/sms.py @@ -3,14 +3,13 @@ This file is part of Linspector (https://linspector.org/) Copyright (c) 2022 Johannes Findeisen . All Rights Reserved. See LICENSE (MIT license) """ -from logging import getLogger +import gammu +from linspector.core.helpers import log from linspector.core.notification import Notification -logger = getLogger('linspector') - -def get(configuration, environment): +def create(configuration, environment): return SMSNotification(configuration, environment) diff --git a/linspector/notifications/twitter.py b/linspector/notifications/twitter.py index d70f4f2..f92018c 100644 --- a/linspector/notifications/twitter.py +++ b/linspector/notifications/twitter.py @@ -3,14 +3,11 @@ This file is part of Linspector (https://linspector.org/) Copyright (c) 2022 Johannes Findeisen . All Rights Reserved. See LICENSE (MIT license) """ -from logging import getLogger - +from linspector.core.helpers import log from linspector.core.notification import Notification -logger = getLogger('linspector') - -def get(configuration, environment): +def create(configuration, environment): return TwitterNotification(configuration, environment) diff --git a/linspector/notifications/xmpp.py b/linspector/notifications/xmpp.py index 2dc0fea..1c98707 100644 --- a/linspector/notifications/xmpp.py +++ b/linspector/notifications/xmpp.py @@ -3,14 +3,13 @@ This file is part of Linspector (https://linspector.org/) Copyright (c) 2022 Johannes Findeisen . All Rights Reserved. See LICENSE (MIT license) """ -from logging import getLogger +import xmpp +from linspector.core.helpers import log from linspector.core.notification import Notification -logger = getLogger('linspector') - -def get(configuration, environment): +def create(configuration, environment): return XMPPNotification(configuration, environment) diff --git a/linspector/plugins/httpserver.py b/linspector/plugins/httpserver.py index 51a9195..c12fbf2 100644 --- a/linspector/plugins/httpserver.py +++ b/linspector/plugins/httpserver.py @@ -6,12 +6,9 @@ See LICENSE (MIT license) import cherrypy import json -from logging import getLogger - +from linspector.core.helpers import log from linspector.core.plugin import Plugin -logger = getLogger('linspector') - def get(configuration, environment, linspector): return HTTPServerPlugin(configuration, environment, linspector) diff --git a/linspector/plugins/lish.py b/linspector/plugins/lish.py index b28c211..9a9478c 100644 --- a/linspector/plugins/lish.py +++ b/linspector/plugins/lish.py @@ -3,12 +3,9 @@ This file is part of Linspector (https://linspector.org/) Copyright (c) 2022 Johannes Findeisen . All Rights Reserved. See LICENSE (MIT license) """ -from logging import getLogger - +from linspector.core.helpers import log from linspector.core.plugin import Plugin -logger = getLogger('linspector') - def get(configuration, environment, linspector): return LishPlugin(configuration, environment, linspector) diff --git a/linspector/services/http/httpkeyword.py b/linspector/services/http/httpkeyword.py index 6aba6e6..e1506c2 100644 --- a/linspector/services/http/httpkeyword.py +++ b/linspector/services/http/httpkeyword.py @@ -3,14 +3,11 @@ This file is part of Linspector (https://linspector.org/) Copyright (c) 2022 Johannes Findeisen . All Rights Reserved. See LICENSE (MIT license) """ -from logging import getLogger - +from linspector.core.helpers import log from linspector.core.service import Service -logger = getLogger('linspector') - -def get(configuration, environment): +def create(configuration, environment): return HTTPKeywordService(configuration, environment) diff --git a/linspector/services/misc/dummy.py b/linspector/services/misc/dummy.py index 1d11455..f82b679 100644 --- a/linspector/services/misc/dummy.py +++ b/linspector/services/misc/dummy.py @@ -3,14 +3,11 @@ This file is part of Linspector (https://linspector.org/) Copyright (c) 2022 Johannes Findeisen . All Rights Reserved. See LICENSE (MIT license) """ -from logging import getLogger - +from linspector.core.helpers import log from linspector.core.service import Service -logger = getLogger('linspector') - -def get(configuration, environment): +def create(configuration, environment): return DummyService(configuration, environment) @@ -22,4 +19,5 @@ class DummyService(Service): self.__environment = environment def execute(self): + log('debug', __name__, 'dummy object @' + str(self)) return diff --git a/linspector/services/misc/random.py b/linspector/services/misc/random.py index adc49b3..3812e30 100644 --- a/linspector/services/misc/random.py +++ b/linspector/services/misc/random.py @@ -3,14 +3,11 @@ This file is part of Linspector (https://linspector.org/) Copyright (c) 2022 Johannes Findeisen . All Rights Reserved. See LICENSE (MIT license) """ -from logging import getLogger - +from linspector.core.helpers import log from linspector.core.service import Service -logger = getLogger('linspector') - -def get(configuration, environment): +def create(configuration, environment): return RandomService(configuration, environment) diff --git a/linspector/services/net/fritzboxuplink.py b/linspector/services/net/fritzboxuplink.py index a7975eb..91cde0d 100644 --- a/linspector/services/net/fritzboxuplink.py +++ b/linspector/services/net/fritzboxuplink.py @@ -4,14 +4,12 @@ Copyright (c) 2022 Johannes Findeisen . All Rights Reserved. See LICENSE (MIT) """ from fritzconnection.lib.fritzstatus import FritzStatus -from logging import getLogger +from linspector.core.helpers import log from linspector.core.service import Service -logger = getLogger('linspector') - -def get(configuration, environment): +def create(configuration, environment): return FritzboxUplinkService(configuration, environment) diff --git a/linspector/services/net/ping.py b/linspector/services/net/ping.py index 75af871..803caa8 100644 --- a/linspector/services/net/ping.py +++ b/linspector/services/net/ping.py @@ -3,14 +3,11 @@ This file is part of Linspector (https://linspector.org/) Copyright (c) 2022 Johannes Findeisen . All Rights Reserved. See LICENSE (MIT license) """ -from logging import getLogger - +from linspector.core.helpers import log from linspector.core.service import Service -logger = getLogger('linspector') - -def get(configuration, environment): +def create(configuration, environment): return PingService(configuration, environment) diff --git a/linspector/services/net/port.py b/linspector/services/net/port.py index 854a65d..68cf138 100644 --- a/linspector/services/net/port.py +++ b/linspector/services/net/port.py @@ -5,14 +5,11 @@ See LICENSE (MIT license) """ import socket -from logging import getLogger - +from linspector.core.helpers import log from linspector.core.service import Service -logger = getLogger('linspector') - -def get(configuration, environment): +def create(configuration, environment): return PortService(configuration, environment) diff --git a/linspector/services/net/speedtest.py b/linspector/services/net/speedtest.py index 77e0338..28b0068 100644 --- a/linspector/services/net/speedtest.py +++ b/linspector/services/net/speedtest.py @@ -7,14 +7,11 @@ import calendar import requests import time -from logging import getLogger - +from linspector.core.helpers import log from linspector.core.service import Service -logger = getLogger('linspector') - -def get(configuration, environment): +def create(configuration, environment): return SpeedtestService(configuration, environment) diff --git a/linspector/services/net/ssh.py b/linspector/services/net/ssh.py index 5be4bd7..546b37d 100644 --- a/linspector/services/net/ssh.py +++ b/linspector/services/net/ssh.py @@ -7,15 +7,11 @@ import os import paramiko import pprint - -from logging import getLogger - +from linspector.core.helpers import log from linspector.core.service import Service -logger = getLogger('linspector') - -def get(configuration, environment): +def create(configuration, environment): return SSHService(configuration, environment) diff --git a/linspector/services/net/tcpconnect.py b/linspector/services/net/tcpconnect.py index 77dde56..e4519d4 100644 --- a/linspector/services/net/tcpconnect.py +++ b/linspector/services/net/tcpconnect.py @@ -3,14 +3,11 @@ This file is part of Linspector (https://linspector.org/) Copyright (c) 2022 Johannes Findeisen . All Rights Reserved. See LICENSE (MIT license) """ -from logging import getLogger - +from linspector.core.helpers import log from linspector.core.service import Service -logger = getLogger('linspector') - -def get(configuration, environment): +def create(configuration, environment): return TCPConnectService(configuration, environment) diff --git a/linspector/services/snmp/get.py b/linspector/services/snmp/get.py index 8278c7a..9f9ed26 100644 --- a/linspector/services/snmp/get.py +++ b/linspector/services/snmp/get.py @@ -3,15 +3,13 @@ This file is part of Linspector (https://linspector.org/) Copyright (c) 2022 Johannes Findeisen . All Rights Reserved. See LICENSE (MIT license) """ -from logging import getLogger from pysnmp.entity.rfc3413.oneliner import cmdgen +from linspector.core.helpers import log from linspector.core.service import Service -logger = getLogger('linspector') - -def get(configuration, environment): +def create(configuration, environment): return GetService(configuration, environment) diff --git a/linspector/services/sys/shell.py b/linspector/services/sys/shell.py index 2e902ea..df1e212 100644 --- a/linspector/services/sys/shell.py +++ b/linspector/services/sys/shell.py @@ -3,14 +3,11 @@ This file is part of Linspector (https://linspector.org/) Copyright (c) 2022 Johannes Findeisen . All Rights Reserved. See LICENSE (MIT license) """ -from logging import getLogger - +from linspector.core.helpers import log from linspector.core.service import Service -logger = getLogger('linspector') - -def get(configuration, environment): +def create(configuration, environment): return ShellService(configuration, environment) diff --git a/linspector/services/sys/uptime.py b/linspector/services/sys/uptime.py index e42f11f..314bec2 100644 --- a/linspector/services/sys/uptime.py +++ b/linspector/services/sys/uptime.py @@ -3,12 +3,9 @@ This file is part of Linspector (https://linspector.org/) Copyright (c) 2022 Johannes Findeisen . All Rights Reserved. See LICENSE (MIT license) """ -from logging import getLogger - +from linspector.core.helpers import log from linspector.core.service import Service -logger = getLogger('linspector') - def get(configuration, environment): return UptimeService(configuration, environment) diff --git a/linspector/tasks/csv.py b/linspector/tasks/csv.py index db56aed..82c3d52 100644 --- a/linspector/tasks/csv.py +++ b/linspector/tasks/csv.py @@ -3,14 +3,11 @@ This file is part of Linspector (https://linspector.org/) Copyright (c) 2022 Johannes Findeisen . All Rights Reserved. See LICENSE (MIT license) """ -from logging import getLogger - +from linspector.core.helpers import log from linspector.core.task import Task -logger = getLogger('linspector') - -def get(configuration, environment): +def create(configuration, environment): return CSVTask(configuration, environment) diff --git a/linspector/tasks/filelogger.py b/linspector/tasks/filelogger.py index c6b9843..10179be 100644 --- a/linspector/tasks/filelogger.py +++ b/linspector/tasks/filelogger.py @@ -3,14 +3,11 @@ This file is part of Linspector (https://linspector.org/) Copyright (c) 2022 Johannes Findeisen . All Rights Reserved. See LICENSE (MIT license) """ -from logging import getLogger - +from linspector.core.helpers import log from linspector.core.task import Task -logger = getLogger('linspector') - -def get(configuration, environment): +def create(configuration, environment): return FileLoggerTask(configuration, environment) diff --git a/linspector/tasks/mariadb.py b/linspector/tasks/mariadb.py index 5030826..49b0122 100644 --- a/linspector/tasks/mariadb.py +++ b/linspector/tasks/mariadb.py @@ -3,14 +3,11 @@ This file is part of Linspector (https://linspector.org/) Copyright (c) 2022 Johannes Findeisen . All Rights Reserved. See LICENSE (MIT license) """ -from logging import getLogger - +from linspector.core.helpers import log from linspector.core.task import Task -logger = getLogger('linspector') - -def get(configuration, environment): +def create(configuration, environment): return MariaDBTask(configuration, environment) diff --git a/linspector/tasks/redis.py b/linspector/tasks/redis.py index fd6303b..11d2918 100644 --- a/linspector/tasks/redis.py +++ b/linspector/tasks/redis.py @@ -3,14 +3,11 @@ This file is part of Linspector (https://linspector.org/) Copyright (c) 2022 Johannes Findeisen . All Rights Reserved. See LICENSE (MIT license) """ -from logging import getLogger - +from linspector.core.helpers import log from linspector.core.task import Task -logger = getLogger('linspector') - -def get(configuration, environment): +def create(configuration, environment): return RedisTask(configuration, environment) diff --git a/linspector/tasks/sqlite.py b/linspector/tasks/sqlite.py index 45ed5b0..03cb3b1 100644 --- a/linspector/tasks/sqlite.py +++ b/linspector/tasks/sqlite.py @@ -3,14 +3,11 @@ This file is part of Linspector (https://linspector.org/) Copyright (c) 2022 Johannes Findeisen . All Rights Reserved. See LICENSE (MIT license) """ -from logging import getLogger - +from linspector.core.helpers import log from linspector.core.task import Task -logger = getLogger('linspector') - -def get(configuration, environment): +def create(configuration, environment): return SQLiteTask(configuration, environment) diff --git a/linspector/utils/__init__.py b/linspector/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/linspector/utils/singleton.py b/linspector/utils/singleton.py new file mode 100644 index 0000000..f152564 --- /dev/null +++ b/linspector/utils/singleton.py @@ -0,0 +1,48 @@ +""" +This file is part of Linspector (https://linspector.org/) +Copyright (c) 2022 Johannes Findeisen . All Rights Reserved. +See LICENSE (MIT license) +""" + +#see http://stackoverflow.com/questions/42558/python-and-the-singleton-pattern + + +class Singleton: + """ + A non-thread-safe helper class to ease implementing singletons. + This should be used as a decorator -- not a metaclass -- to the + class that should be a singleton. + + The decorated class can define one `__init__` function that + takes only the `self` argument. Other than that, there are + no restrictions that apply to the decorated class. + + To get the singleton instance, use the `Instance` method. Trying + to use `__call__` will result in a `TypeError` being raised. + + Limitations: The decorated class cannot be inherited from. + + """ + + def __init__(self, decorated): + self._decorated = decorated + self._instance = None + + def instance(self): + """ + Returns the singleton instance. Upon its first call, it creates a + new instance of the decorated class and calls its `__init__` method. + On all subsequent calls, the already created instance is returned. + + """ + try: + return self._instance + except AttributeError: + self._instance = self._decorated() + return self._instance + + def __call__(self): + raise TypeError('Singletons must be accessed through `Instance()`.') + + def __instancecheck__(self, inst): + return isinstance(inst, self._decorated) diff --git a/requirements.txt b/requirements.txt index ebe63f5..a93d60d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,8 @@ APScheduler==3.9.1 # (required) CherryPy==18.8.0 # (optional) -fritzconnection==1.10.3 # (optional) -paramiko==2.11.0 # (optional) -pysnmp==4.4.12 # (optional) \ No newline at end of file +fritzconnection==1.10.3 # (optional, used by fritzuplink service) +python-gammu==3.2.4 # (optional, used by the sms notification) +paramiko==2.11.0 # (optional, used by ssh service) +pysnmp==4.4.12 # (optional, used by snmp services) +requests==2.28.1 # (optional, used by speedtest service) +xmpp2==0.4 # (optional, used by the xmpp notification) \ No newline at end of file