diff --git a/brian_polling_manager/configuration.py b/brian_polling_manager/configuration.py index a200bb881182fd2d521b7474ca318054b207f18a..92a3ef1bd0928c67ba5d4012f866400e5b370476 100644 --- a/brian_polling_manager/configuration.py +++ b/brian_polling_manager/configuration.py @@ -36,12 +36,19 @@ _DEFAULT_CONFIG = { 'gws-direct-interface-check': { 'script': '/var/lib/sensu/bin/poll-gws-direct.sh', 'measurement': 'gwsd_counters', - 'command': '{script} {measurement} {nren} {isp} {hostname} {tag}' + 'command': '{script} --inventory http://localhost:18080' + ' {measurement} {nren} {isp} {hostname} {tag}' }, 'dscp32-service-check': { 'script': '/var/lib/sensu/bin/poll-gws-indirect.sh', 'measurement': 'dscp32_counters', 'command': '{script} {measurement} {service}' + }, + 'eumetsat-multicast-check': { + 'script': '/home/brian_checks/venv/eumetsat-multicast', + 'measurement': 'multicast', + 'command': '{script} --inventory http://localhost:18080' + ' --measurement {measurement} --hostname {hostname}' } }, 'statedir': '/tmp/', @@ -75,16 +82,21 @@ CONFIG_SCHEMA = { 'minItems': 1 }, 'api-key': {'type': 'string'}, - 'interface-check': {'$ref': '#/definitions/influx-check'}, + 'interface-check': + {'$ref': '#/definitions/influx-check'}, 'gws-direct-interface-check': {'$ref': '#/definitions/influx-check'}, - 'dscp32-service-check': {'$ref': '#/definitions/influx-check'}, + 'dscp32-service-check': + {'$ref': '#/definitions/influx-check'}, + 'eumetsat-multicast-check': + {'$ref': '#/definitions/influx-check'}, }, 'required': [ 'api-base', 'api-key', 'interface-check', 'gws-direct-interface-check', - 'dscp32-service-check'], + 'dscp32-service-check', + 'eumetsat-multicast-check'], 'additionalProperties': False }, 'statsd': { @@ -121,6 +133,7 @@ class State(object): GWS_INDIRECT = 'gws-indirect.json' INTERFACES = 'interfaces.json' STATE = 'state.json' + EUMET_MC = 'eumetsat-multicast.json' STATE_SCHEMA = { '$schema': 'http://json-schema.org/draft-07/schema#', @@ -138,7 +151,8 @@ class State(object): 'state': os.path.join(state_dir, State.STATE), 'interfaces': os.path.join(state_dir, State.INTERFACES), 'gws-direct': os.path.join(state_dir, State.GWS_DIRECT), - 'gws-indirect': os.path.join(state_dir, State.GWS_INDIRECT) + 'gws-indirect': os.path.join(state_dir, State.GWS_INDIRECT), + 'eumetsat-multicast': os.path.join(state_dir, State.EUMET_MC) } @staticmethod @@ -219,6 +233,19 @@ class State(object): new_services, inventory.GWS_INDIRECT_SCHEMA) + @property + def eumetsat_multicast(self) -> list: + return State._load_json( + self.cache_filenames['eumetsat-multicast'], + inventory.MULTICAST_SUBSCRIPTION_LIST_SCHEMA) + + @eumetsat_multicast.setter + def eumetsat_multicast(self, new_subscriptions): + State._save_json( + self.cache_filenames['eumetsat-multicast'], + new_subscriptions, + inventory.MULTICAST_SUBSCRIPTION_LIST_SCHEMA) + def _setup_logging(filename=None): """ diff --git a/brian_polling_manager/eumetsat_multicast.py b/brian_polling_manager/eumetsat_multicast.py new file mode 100644 index 0000000000000000000000000000000000000000..4bfdf497f747c0167b69ba81622ea2d6cd4c9ea8 --- /dev/null +++ b/brian_polling_manager/eumetsat_multicast.py @@ -0,0 +1,50 @@ +from brian_polling_manager import sensu + +_CHECK_PREFIX = 'eumetmc' + + +def load_eumetsat_multicast_checks(sensu_params): + def _is_eumetsat_multicast_check(check): + name = check['metadata']['name'] + return name.startswith(_CHECK_PREFIX) + ifc_checks = filter( + _is_eumetsat_multicast_check, sensu.load_all_checks(sensu_params)) + return {c['metadata']['name']: c for c in ifc_checks} + + +class EUMETSATMulticastHostCheck(sensu.AbstractCheck): + + def __init__(self, check_config, hostname): + super().__init__() + self.check_config = check_config + self.hostname = hostname + + @sensu.AbstractCheck.name.getter + def name(self): + return f'{_CHECK_PREFIX}-{self.hostname}' + + @sensu.AbstractCheck.command.getter + def command(self): + return self.check_config['command'].format( + script=self.check_config['script'], + measurement=self.check_config['measurement'], + hostname=self.hostname) + + @sensu.AbstractCheck.proxy_entity_name.getter + def proxy_entity_name(self): + return self.hostname + + +def refresh(sensu_params, subscriptions): + + # one check per unique host + all_routers = {x['router'] for x in subscriptions} + required_checks = [ + EUMETSATMulticastHostCheck( + sensu_params['eumetsat-multicast-check'], hostname) + for hostname in all_routers] + + return sensu.refresh( + sensu_params, + required_checks, + load_eumetsat_multicast_checks(sensu_params)) diff --git a/brian_polling_manager/gws_direct.py b/brian_polling_manager/gws_direct.py index 12a4f92350ccff5ec1b2f29e736388df2fa1e62f..63c4e064fd10e42e168cbe5c5b8267e77d34feca 100644 --- a/brian_polling_manager/gws_direct.py +++ b/brian_polling_manager/gws_direct.py @@ -1,10 +1,12 @@ from brian_polling_manager import sensu +_CHECK_PREFIX = 'gwsd' + def load_gws_direct_checks(sensu_params): def _is_gws_direct_check(check): name = check['metadata']['name'] - return name.startswith('gwsd') + return name.startswith(_CHECK_PREFIX) ifc_checks = filter( _is_gws_direct_check, sensu.load_all_checks(sensu_params)) return {c['metadata']['name']: c for c in ifc_checks} @@ -23,7 +25,7 @@ class GwSDirectInterfaceCheck(sensu.AbstractCheck): isp = isp.replace(' ', '_') tag = self.interface['tag'] tag = tag.replace(' ', '_') - return f'gwsd-{self.interface["nren"]}-{isp}-{tag}' + return f'{_CHECK_PREFIX}-{self.interface["nren"]}-{isp}-{tag}' @sensu.AbstractCheck.command.getter def command(self): diff --git a/brian_polling_manager/gws_indirect.py b/brian_polling_manager/gws_indirect.py index 64d2a57e25aef29505532fe32671e37ad6f7c101..540dc6e7966ee5d290b0febe9fd8403807c9d4b4 100644 --- a/brian_polling_manager/gws_indirect.py +++ b/brian_polling_manager/gws_indirect.py @@ -1,11 +1,13 @@ import re from brian_polling_manager import sensu +_CHECK_PREFIX = 'dscp32' + def load_dscp32_checks(sensu_params): def _is_dscp32_check(check): name = check['metadata']['name'] - return name.startswith('dscp32') + return name.startswith(_CHECK_PREFIX) ifc_checks = filter( _is_dscp32_check, sensu.load_all_checks(sensu_params)) return {c['metadata']['name']: c for c in ifc_checks} @@ -21,7 +23,7 @@ class DSCP32CountersCheck(sensu.AbstractCheck): @sensu.AbstractCheck.name.getter def name(self): name = re.sub(r'[\s_-]+', '_', self.service['name']) - return f'dscp32-{name}' + return f'{_CHECK_PREFIX}-{name}' @sensu.AbstractCheck.command.getter def command(self): diff --git a/brian_polling_manager/inventory.py b/brian_polling_manager/inventory.py index 5d118036f2002699866d11427d5036795f802df2..4b4594d22ce9556793c55a9b18be992c70b1495e 100644 --- a/brian_polling_manager/inventory.py +++ b/brian_polling_manager/inventory.py @@ -97,6 +97,27 @@ GWS_INDIRECT_SCHEMA = { } +# much less strict version of the actual schema +MULTICAST_SUBSCRIPTION_LIST_SCHEMA = { + '$schema': 'http://json-schema.org/draft-07/schema#', + + 'definitions': { + 'subscription': { + 'type': 'object', + 'properties': { + # we really only use this field + # don't depend strictly on unused data + 'router': {'type': 'string'} + }, + 'required': ['router'] + } + }, + + 'type': 'array', + 'items': {'$ref': '#/definitions/subscription'} +} + + def _pick_one(haystack): if not isinstance(haystack, (list, tuple, set)): haystack = [haystack] @@ -159,6 +180,19 @@ def load_gws_indirect_services(base_urls): 'poller/gws/indirect', base_urls, GWS_INDIRECT_SCHEMA) +def load_eumetsat_multicast_subscriptions(base_urls): + """ + Load /poller/eumetsat-multicast from inventory provider + + :param base_urls: inventory provider base api url, or a list of them + :return: a list of dicts, each with a 'router' key + """ + return _load_inventory_json( + 'poller/eumetsat-multicast', + base_urls, + MULTICAST_SUBSCRIPTION_LIST_SCHEMA) + + def last_update_timestamp(base_urls) -> float: try: r = requests.get( diff --git a/brian_polling_manager/main.py b/brian_polling_manager/main.py index a753961cd9aa60da479c218461dde27b6de08dda..90ba9f94c8841906efaa2f50bab5a7f4c518781e 100644 --- a/brian_polling_manager/main.py +++ b/brian_polling_manager/main.py @@ -30,7 +30,7 @@ import jsonschema from statsd import StatsClient from brian_polling_manager import inventory, configuration, \ - interfaces, gws_direct, gws_indirect, sensu + interfaces, gws_direct, gws_indirect, eumetsat_multicast, sensu logger = logging.getLogger(__name__) @@ -54,7 +54,8 @@ REFRESH_RESULT_SCHEMA = { 'properties': { 'interfaces': {'$ref': '#/definitions/refresh-result'}, 'gws_direct': {'$ref': '#/definitions/refresh-result'}, - 'gws_indirect': {'$ref': '#/definitions/refresh-result'} + 'gws_indirect': {'$ref': '#/definitions/refresh-result'}, + 'eumetsat_multicast': {'$ref': '#/definitions/refresh-result'}, }, 'required': ['interfaces'], 'additionalProperties': False @@ -85,11 +86,17 @@ def refresh(config, force=False): config['inventory']) state.gws_indirect = inventory.load_gws_indirect_services( config['inventory']) + state.eumetsat_multicast \ + = inventory.load_eumetsat_multicast_subscriptions( + config['inventory']) result = { 'interfaces': interfaces.refresh(config['sensu'], state.interfaces), 'gws_direct': gws_direct.refresh(config['sensu'], state.gws_direct), 'gws_indirect': gws_indirect.refresh( config['sensu'], state.gws_indirect), + 'eumetsat_multicast': eumetsat_multicast.refresh( + config['sensu'], state.eumetsat_multicast), + } jsonschema.validate(result, REFRESH_RESULT_SCHEMA) # sanity diff --git a/test/conftest.py b/test/conftest.py index a2e99401dcc03df611c1d555c74562ffb496191b..529e19ac6ed0491320d3e91ab9b91f625794148f 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -36,20 +36,28 @@ def config(): 'interface-check': { 'script': '/var/lib/sensu/bin/counter2influx.sh', 'measurement': 'counters', - 'command': ('{script} {measurement} ' - '{community} {hostname} ' - '{interface} {ifIndex}'), + 'command': '{script} {measurement}' + ' {community} {hostname}' + ' {interface} {ifIndex}', }, 'gws-direct-interface-check': { 'script': '/var/lib/sensu/bin/poll-gws-direct.sh', 'measurement': 'gwsd_counters', - 'command': ('{script} {measurement} ' - '{nren} {isp} {hostname} {tag}') + 'command': '{script} {measurement}' + ' {nren} {isp} {hostname} {tag}' }, 'dscp32-service-check': { 'script': '/var/lib/sensu/bin/poll-gws-indirect.sh', 'measurement': 'dscp32_counters', 'command': '{script} {measurement} {service}' + }, + 'eumetsat-multicast-check': { + 'script': '/home/brian_checks/venv/eumetsat-multicast', + 'measurement': 'multicast', + 'command': '{script}' + ' --inventory http://localhost:18080' + ' --measurement {measurement}' + ' --hostname {hostname}' } }, 'statedir': state_dir_name, @@ -184,6 +192,11 @@ def mocked_inventory(): url=re.compile(r'.*inventory.+/poller/gws/indirect.*'), body=_load_test_data('gws-indirect.json')) + responses.add( + method=responses.GET, + url=re.compile(r'.*inventory.+/poller/eumetsat-multicast'), + body=_load_test_data('eumetsat-multicast.json')) + bogus_version = {'latch': {'timestamp': 10000 * random.random()}} # mocked api for returning all checks responses.add(