diff --git a/Changelog.md b/Changelog.md index ea308860cdd5b2d515dc62d5716e4a45463533a6..32c39090bfc7425b3288c99c6adba55b2a7f2e67 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## [0.3] - 2021-08-20 +- less strict inventory response validation +- gws direct & indirect checks + ## [0.2] - 2021-06-10 - ensure checks are published diff --git a/brian_polling_manager/configuration.py b/brian_polling_manager/configuration.py index bbb136dd4601b54c3863813d80a39eb4b2463f6d..a200bb881182fd2d521b7474ca318054b207f18a 100644 --- a/brian_polling_manager/configuration.py +++ b/brian_polling_manager/configuration.py @@ -29,14 +29,19 @@ _DEFAULT_CONFIG = { 'interface-check': { 'script': '/var/lib/sensu/bin/counter2influx.sh', 'measurement': 'counters', - 'interval': 300, - 'subscriptions': ['interfacecounters'], - 'output_metric_handlers': ['influx-db-handler'], - 'namespace': 'default', - 'round_robin': True, '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}' + }, + 'dscp32-service-check': { + 'script': '/var/lib/sensu/bin/poll-gws-indirect.sh', + 'measurement': 'dscp32_counters', + 'command': '{script} {measurement} {service}' } }, 'statedir': '/tmp/', @@ -56,22 +61,9 @@ CONFIG_SCHEMA = { 'properties': { 'script': {'type': 'string'}, 'measurement': {'type': 'string'}, - 'interval': {'type': 'integer'}, - 'subscriptions': { - 'type': 'array', - 'items': {'type': 'string'} - }, - 'output_metric_handlers': { - 'type': 'array', - 'items': {'type': 'string'} - }, - 'namespace': {'type': 'string'}, - 'round_robin': {'type': 'boolean'}, 'command': {'type': 'string'}, }, - 'required': ['script', 'measurement', 'interval', - 'subscriptions', 'output_metric_handlers', - 'namespace', 'round_robin', 'command'], + 'required': ['script', 'measurement', 'command'], 'additionalProperties': False }, 'sensu': { @@ -83,9 +75,16 @@ 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'}, }, - 'required': ['api-base', 'api-key', 'interface-check'], + 'required': [ + 'api-base', 'api-key', + 'interface-check', + 'gws-direct-interface-check', + 'dscp32-service-check'], 'additionalProperties': False }, 'statsd': { @@ -118,6 +117,8 @@ CONFIG_SCHEMA = { class State(object): + GWS_DIRECT = 'gws-direct.json' + GWS_INDIRECT = 'gws-indirect.json' INTERFACES = 'interfaces.json' STATE = 'state.json' @@ -133,9 +134,11 @@ class State(object): def __init__(self, state_dir: str): assert os.path.isdir(state_dir) - self.filenames = { + self.cache_filenames = { 'state': os.path.join(state_dir, State.STATE), - 'cache': os.path.join(state_dir, State.INTERFACES) + '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) } @staticmethod @@ -150,38 +153,71 @@ class State(object): f'unable to open state file {filename}') return None + @staticmethod + def _save_json(filename, new_data, schema): + try: + jsonschema.validate(new_data, schema) + except jsonschema.ValidationError: + logger.exception('invalid interface state data') + return + + with open(filename, 'w') as f: + f.write(json.dumps(new_data)) + @property def last(self) -> int: - state = State._load_json(self.filenames['state'], State.STATE_SCHEMA) + state = State._load_json( + self.cache_filenames['state'], State.STATE_SCHEMA) return state['last'] if state else -1 @last.setter def last(self, new_last: Union[float, None]): if not new_last or new_last < 0: - os.unlink(self.filenames['state']) + os.unlink(self.cache_filenames['state']) else: - state = {'last': new_last} - with open(self.filenames['state'], 'w') as f: - f.write(json.dumps(state)) + State._save_json( + self.cache_filenames['state'], + {'last': new_last}, + State.STATE_SCHEMA) @property def interfaces(self) -> list: return State._load_json( - self.filenames['cache'], + self.cache_filenames['interfaces'], inventory.INVENTORY_INTERFACES_SCHEMA) @interfaces.setter def interfaces(self, new_interfaces): - try: - jsonschema.validate( - new_interfaces, - inventory.INVENTORY_INTERFACES_SCHEMA) - except jsonschema.ValidationError: - logger.exception('invalid interface state data') - return + State._save_json( + self.cache_filenames['interfaces'], + new_interfaces, + inventory.INVENTORY_INTERFACES_SCHEMA) + + @property + def gws_direct(self) -> list: + return State._load_json( + self.cache_filenames['gws-direct'], + inventory.GWS_DIRECT_SCHEMA) + + @gws_direct.setter + def gws_direct(self, new_interfaces): + State._save_json( + self.cache_filenames['gws-direct'], + new_interfaces, + inventory.GWS_DIRECT_SCHEMA) + + @property + def gws_indirect(self) -> list: + return State._load_json( + self.cache_filenames['gws-indirect'], + inventory.GWS_INDIRECT_SCHEMA) - with open(self.filenames['cache'], 'w') as f: - f.write(json.dumps(new_interfaces)) + @gws_indirect.setter + def gws_indirect(self, new_services): + State._save_json( + self.cache_filenames['gws-indirect'], + new_services, + inventory.GWS_INDIRECT_SCHEMA) def _setup_logging(filename=None): diff --git a/brian_polling_manager/gws_direct.py b/brian_polling_manager/gws_direct.py new file mode 100644 index 0000000000000000000000000000000000000000..12a4f92350ccff5ec1b2f29e736388df2fa1e62f --- /dev/null +++ b/brian_polling_manager/gws_direct.py @@ -0,0 +1,57 @@ +from brian_polling_manager import sensu + + +def load_gws_direct_checks(sensu_params): + def _is_gws_direct_check(check): + name = check['metadata']['name'] + return name.startswith('gwsd') + ifc_checks = filter( + _is_gws_direct_check, sensu.load_all_checks(sensu_params)) + return {c['metadata']['name']: c for c in ifc_checks} + + +class GwSDirectInterfaceCheck(sensu.AbstractCheck): + + def __init__(self, ifc_check_params, interface): + super().__init__() + self.ifc_check_params = ifc_check_params + self.interface = interface + + @sensu.AbstractCheck.name.getter + def name(self): + isp = self.interface['isp'] + isp = isp.replace(' ', '_') + tag = self.interface['tag'] + tag = tag.replace(' ', '_') + return f'gwsd-{self.interface["nren"]}-{isp}-{tag}' + + @sensu.AbstractCheck.command.getter + def command(self): + isp = self.interface["isp"] + if ' ' in isp: + isp = f'"{self.interface["isp"]}"' + + return self.ifc_check_params['command'].format( + script=self.ifc_check_params['script'], + measurement=self.ifc_check_params['measurement'], + hostname=self.interface['hostname'], + isp=isp, + nren=self.interface['nren'], + tag=self.interface['tag']) + + @sensu.AbstractCheck.proxy_entity_name.getter + def proxy_entity_name(self): + return self.interface['hostname'] + + +def refresh(sensu_params, inventory_interfaces): + + required_checks = [ + GwSDirectInterfaceCheck( + sensu_params['gws-direct-interface-check'], ifc) + for ifc in inventory_interfaces] + + return sensu.refresh( + sensu_params, + required_checks, + load_gws_direct_checks(sensu_params)) diff --git a/brian_polling_manager/gws_indirect.py b/brian_polling_manager/gws_indirect.py new file mode 100644 index 0000000000000000000000000000000000000000..64d2a57e25aef29505532fe32671e37ad6f7c101 --- /dev/null +++ b/brian_polling_manager/gws_indirect.py @@ -0,0 +1,47 @@ +import re +from brian_polling_manager import sensu + + +def load_dscp32_checks(sensu_params): + def _is_dscp32_check(check): + name = check['metadata']['name'] + return name.startswith('dscp32') + ifc_checks = filter( + _is_dscp32_check, sensu.load_all_checks(sensu_params)) + return {c['metadata']['name']: c for c in ifc_checks} + + +class DSCP32CountersCheck(sensu.AbstractCheck): + + def __init__(self, sensu_check_params, service): + super().__init__() + self.sensu_check_params = sensu_check_params + self.service = service + + @sensu.AbstractCheck.name.getter + def name(self): + name = re.sub(r'[\s_-]+', '_', self.service['name']) + return f'dscp32-{name}' + + @sensu.AbstractCheck.command.getter + def command(self): + return self.sensu_check_params['command'].format( + script=self.sensu_check_params['script'], + measurement=self.sensu_check_params['measurement'], + service=self.service['id']) + + @sensu.AbstractCheck.proxy_entity_name.getter + def proxy_entity_name(self): + return self.service['hostname'] + + +def refresh(sensu_params, services): + + required_checks = [ + DSCP32CountersCheck(sensu_params['dscp32-service-check'], s) + for s in services] + + return sensu.refresh( + sensu_params, + required_checks, + load_dscp32_checks(sensu_params)) diff --git a/brian_polling_manager/interfaces.py b/brian_polling_manager/interfaces.py index f824c0d69d30262a4d87501f5b6291213b817131..ab6a15e2f8f03984d7c0c4c58a2f0fc8c8c3eb3a 100644 --- a/brian_polling_manager/interfaces.py +++ b/brian_polling_manager/interfaces.py @@ -11,100 +11,47 @@ def load_ifc_checks(sensu_params): name = check['metadata']['name'] # check-* is the old-style name (add to the returned # data so it can be deleted) - return re.match(r'^(check|ifc)-([^-]+\.geant\.net)-(.+)$', name) + return re.match(r'^(check|ifc)-[^-]+\.geant\.net-.+$', name) ifc_checks = filter(_is_ifc_check, sensu.load_all_checks(sensu_params)) return {c['metadata']['name']: c for c in ifc_checks} -def _check_name(interface): - ifc_name = interface['name'].replace('/', '-') - return f'ifc-{interface["router"]}-{ifc_name}' +class InterfaceCheck(sensu.AbstractCheck): + def __init__(self, ifc_check_params, interface): + super().__init__() + self.ifc_check_params = ifc_check_params + self.interface = interface -def _make_check(check_params, interface): - command = check_params['command'].format( - script=check_params['script'], - measurement=check_params['measurement'], - community='0pBiFbD', # TODO: add this to /poller/interfaces response - hostname=interface['router'], - interface=interface['name'], - ifIndex=interface['snmp-index'] - ) + @sensu.AbstractCheck.name.getter + def name(self): + ifc_name = self.interface['name'].replace('/', '-') + return f'ifc-{self.interface["router"]}-{ifc_name}' - return { - 'command': command, - 'interval': check_params['interval'], - 'subscriptions': sorted(check_params['subscriptions']), - 'proxy_entity_name': interface['router'], - 'round_robin': check_params['round_robin'], - 'output_metric_format': 'influxdb_line', - 'output_metric_handlers': sorted( - check_params['output_metric_handlers']), - 'metadata': { - 'name': _check_name(interface), - 'namespace': check_params['namespace'] - }, - 'publish': True - } + @sensu.AbstractCheck.command.getter + def command(self): + return self.ifc_check_params['command'].format( + script=self.ifc_check_params['script'], + measurement=self.ifc_check_params['measurement'], + # TODO: add community string to /poller/interfaces response + # (cf. POL1-339) + community='0pBiFbD', + hostname=self.interface['router'], + interface=self.interface['name'], + ifIndex=self.interface['snmp-index']) + @sensu.AbstractCheck.proxy_entity_name.getter + def proxy_entity_name(self): + return self.interface['router'] -def _checks_match(a, b) -> bool: - if a['publish'] != b['publish']: - return False - if a['command'] != b['command']: - return False - if a['interval'] != b['interval']: - return False - if a['proxy_entity_name'] != b['proxy_entity_name']: - return False - if a['round_robin'] != b['round_robin']: - return False - if a['output_metric_format'] != b['output_metric_format']: - return False - if sorted(a['subscriptions']) != sorted(b['subscriptions']): - return False - if sorted(a['output_metric_handlers']) \ - != sorted(b['output_metric_handlers']): - return False - if a['metadata']['name'] != b['metadata']['name']: - return False - if a['metadata']['namespace'] != b['metadata']['namespace']: - return False - return True +def refresh(sensu_params, inventory_interfaces): -def refresh(sensu_params, state): + required_checks = [ + InterfaceCheck(sensu_params['interface-check'], ifc) + for ifc in inventory_interfaces] - ifc_checks = load_ifc_checks(sensu_params) - - created = 0 - updated = 0 - interfaces = 0 - for interface in state.interfaces: - - interfaces += 1 - - expected_check = _make_check( - sensu_params['interface-check'], interface) - - expected_name = _check_name(interface) - if expected_name not in ifc_checks: - created += 1 - sensu.create_check(sensu_params, expected_check) - elif not _checks_match(ifc_checks[expected_name], expected_check): - updated += 1 - sensu.update_check(sensu_params, expected_check) - - wanted_checks = {_check_name(ifc) for ifc in state.interfaces} - extra_checks = set(ifc_checks.keys()) - wanted_checks - for name in extra_checks: - sensu.delete_check(sensu_params, name) - - # cf. main.REFRESH_RESULT_SCHEMA - return { - 'checks': len(ifc_checks), - 'input': interfaces, - 'created': created, - 'updated': updated, - 'deleted': len(extra_checks) - } + return sensu.refresh( + sensu_params, + required_checks, + load_ifc_checks(sensu_params)) diff --git a/brian_polling_manager/inventory.py b/brian_polling_manager/inventory.py index b5211049129076e924420c21d2a7fd4ede1e0eee..5d118036f2002699866d11427d5036795f802df2 100644 --- a/brian_polling_manager/inventory.py +++ b/brian_polling_manager/inventory.py @@ -6,7 +6,7 @@ import requests logger = logging.getLogger(__name__) -# minimal inventory response schema +# minimal inventory response schema for our purposes INVENTORY_VERSION_SCHEMA = { '$schema': 'http://json-schema.org/draft-07/schema#', @@ -29,50 +29,23 @@ INVENTORY_VERSION_SCHEMA = { 'additionalProperties': True } +# minimal inventory response schema for our purposes INVENTORY_INTERFACES_SCHEMA = { '$schema': 'http://json-schema.org/draft-07/schema#', 'definitions': { - 'service': { - 'type': 'object', - 'properties': { - 'id': {'type': 'integer'}, - 'name': {'type': 'string'}, - 'type': {'type': 'string'}, - 'status': {'type': 'string'}, - }, - 'required': ['id', 'name', 'type', 'status'], - 'additionalProperties': False - }, 'interface': { 'type': 'object', 'properties': { 'router': {'type': 'string'}, 'name': {'type': 'string'}, - 'description': {'type': 'string'}, 'snmp-index': { 'type': 'integer', 'minimum': 1 - }, - 'bundle': { - 'type': 'array', - 'items': {'type': 'string'} - }, - 'bundle-parents': { - 'type': 'array', - 'items': {'type': 'string'} - }, - 'circuits': { - 'type': 'array', - 'items': {'$ref': '#/definitions/service'} } }, - 'required': [ - 'router', 'name', 'description', - 'snmp-index', 'bundle', 'bundle-parents', - 'circuits'], - 'additionalProperties': False - }, + 'required': ['router', 'name', 'snmp-index'], + } }, 'type': 'array', @@ -80,33 +53,112 @@ INVENTORY_INTERFACES_SCHEMA = { } -def _pick_one(things): - if not isinstance(things, (list, tuple, set)): - things = [things] - return random.choice(things) +# minimal inventory response schema for our purposes +GWS_DIRECT_SCHEMA = { + '$schema': 'http://json-schema.org/draft-07/schema#', + 'definitions': { + 'interface-counters': { + 'type': 'object', + 'properties': { + 'nren': {'type': 'string'}, + 'isp': { + 'type': 'string', + 'enum': ['Cogent', 'Telia', 'CenturyLink'] + }, + 'hostname': {'type': 'string'}, + 'tag': {'type': 'string'}, + }, + 'required': ['nren', 'isp', 'hostname', 'tag'] + } + }, -def load_interfaces(base_urls): + 'type': 'array', + 'items': {'$ref': '#/definitions/interface-counters'} +} + +GWS_INDIRECT_SCHEMA = { + '$schema': 'http://json-schema.org/draft-07/schema#', + + 'definitions': { + 'service': { + 'type': 'object', + 'properties': { + # minimal validation for our purposes + 'name': {'type': 'string'}, + 'hostname': {'type': 'string'} + }, + 'required': ['name', 'hostname'] + } + }, + + 'type': 'array', + 'items': {'$ref': '#/definitions/service'} +} + + +def _pick_one(haystack): + if not isinstance(haystack, (list, tuple, set)): + haystack = [haystack] + return random.choice(haystack) + + +def _load_inventory_json(api_route, base_urls, schema): """ - Load /poller/interfaces from inventory provider - and return a slightly reformatted dict. + Load & decode the specified inventory api data + :param api_route: the api-specific handler route :param base_urls: inventory provider base api url, or a list of them - :return: a dict like [<router>][<interface>] = inventory leaf data + :param schema: jsonschema to validate the response against + :return: the decoded json reponse """ url = _pick_one(base_urls) logger.debug(f'using inventory base api url: {url}') rsp = requests.get( - f'{url}/poller/interfaces', + f'{url}/{api_route}', headers={'Accept': 'application/json'}) rsp.raise_for_status() result = rsp.json() - jsonschema.validate(result, INVENTORY_INTERFACES_SCHEMA) + jsonschema.validate(result, schema) return result +def load_interfaces(base_urls): + """ + Load /poller/interfaces from inventory provider + and return a slightly reformatted dict. + + :param base_urls: inventory provider base api url, or a list of them + :return: a list (INVENTORY_INTERFACES_SCHEMA) + """ + return _load_inventory_json( + 'poller/interfaces', base_urls, INVENTORY_INTERFACES_SCHEMA) + + +def load_gws_direct_interfaces(base_urls): + """ + Load /poller/gws/direct from inventory provider + + :param base_urls: inventory provider base api url, or a list of them + :return: an interable of interface-specific check data + """ + return _load_inventory_json( + 'poller/gws/direct', base_urls, GWS_DIRECT_SCHEMA) + + +def load_gws_indirect_services(base_urls): + """ + Load /poller/gws/indirect from inventory provider + + :param base_urls: inventory provider base api url, or a list of them + :return: an iterable of strings (service names) + """ + return _load_inventory_json( + 'poller/gws/indirect', base_urls, GWS_INDIRECT_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 5e92a164b9706344ec9d558fd37ed2fd2fdc4473..ab837be04feb9235447bee664f800355493088b7 100644 --- a/brian_polling_manager/main.py +++ b/brian_polling_manager/main.py @@ -29,7 +29,8 @@ import click import jsonschema from statsd import StatsClient -from brian_polling_manager import inventory, interfaces, configuration +from brian_polling_manager \ + import inventory, configuration, interfaces, gws_direct, gws_indirect logger = logging.getLogger(__name__) @@ -51,7 +52,9 @@ REFRESH_RESULT_SCHEMA = { }, 'type': 'object', 'properties': { - 'interfaces': {'$ref': '#/definitions/refresh-result'} + 'interfaces': {'$ref': '#/definitions/refresh-result'}, + 'gws_direct': {'$ref': '#/definitions/refresh-result'}, + 'gws_indirect': {'$ref': '#/definitions/refresh-result'} }, 'required': ['interfaces'], 'additionalProperties': False @@ -76,24 +79,31 @@ def refresh(config, force=False): if force or not last or last != state.last: state.last = last state.interfaces = inventory.load_interfaces(config['inventory']) - + state.gws_direct = inventory.load_gws_direct_interfaces( + config['inventory']) + state.gws_indirect = inventory.load_gws_indirect_services( + config['inventory']) result = { - 'interfaces': interfaces.refresh(config['sensu'], state) + '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), } + jsonschema.validate(result, REFRESH_RESULT_SCHEMA) # sanity statsd_config = config.get('statsd', None) if statsd_config: - statsd = StatsClient( - host=statsd_config['hostname'], - port=statsd_config['port'], - prefix=f'{statsd_config["prefix"]}_interfaces') - statsd.gauge('checks', result['interfaces']['checks']) - statsd.gauge('input', result['interfaces']['input']) - statsd.gauge('created', result['interfaces']['created']) - statsd.gauge('updated', result['interfaces']['updated']) - statsd.gauge('deleted', result['interfaces']['deleted']) + for key, counts in result.items(): + statsd = StatsClient( + host=statsd_config['hostname'], + port=statsd_config['port'], + prefix=f'{statsd_config["prefix"]}_{key}') + statsd.gauge('checks', counts['checks']) + statsd.gauge('input', counts['input']) + statsd.gauge('created', counts['created']) + statsd.gauge('updated', counts['updated']) + statsd.gauge('deleted', counts['deleted']) - jsonschema.validate(result, REFRESH_RESULT_SCHEMA) # sanity return result diff --git a/brian_polling_manager/sensu.py b/brian_polling_manager/sensu.py index 0c60d091320f7ddfbc8a94aa5423b34c7a1dc9f2..b03ec13862c92fb7f1d8d0db4a5a0b3c094c939c 100644 --- a/brian_polling_manager/sensu.py +++ b/brian_polling_manager/sensu.py @@ -1,6 +1,7 @@ """ Sensu api utilities """ +import copy import logging import random import requests @@ -8,7 +9,14 @@ import requests logger = logging.getLogger(__name__) +_cached_checks = None # not using lru_cache, since params is a dict + + def load_all_checks(params, namespace='default'): + global _cached_checks + if _cached_checks is not None: + return _cached_checks + url = random.choice(params['api-base']) r = requests.get( f'{url}/api/core/v2/namespaces/{namespace}/checks', @@ -18,8 +26,8 @@ def load_all_checks(params, namespace='default'): }) r.raise_for_status() - for check in r.json(): - yield check + _cached_checks = r.json() + return _cached_checks def create_check(params, check, namespace='default'): @@ -60,3 +68,188 @@ def delete_check(params, check, namespace='default'): f'{url}/api/core/v2/namespaces/{namespace}/checks/{name}', headers={'Authorization': f'Key {params["api-key"]}'}) r.raise_for_status() + + +def checks_match(a, b) -> bool: + if a['publish'] != b['publish']: + return False + if a['command'] != b['command']: + return False + if a['interval'] != b['interval']: + return False + if a['proxy_entity_name'] != b['proxy_entity_name']: + return False + if a['round_robin'] != b['round_robin']: + return False + if a['output_metric_format'] != b['output_metric_format']: + return False + if sorted(a['subscriptions']) != sorted(b['subscriptions']): + return False + if sorted(a['output_metric_handlers']) \ + != sorted(b['output_metric_handlers']): + return False + if a['metadata']['name'] != b['metadata']['name']: + return False + if a['metadata']['namespace'] != b['metadata']['namespace']: + return False + return True + + +class AbstractCheck(object): + """ + not explicitly using abc.ABC ... more readable than stacks of decorators + """ + + INTERVAL_S = 300 + SUBSCRIPTIONS = ['interfacecounters'] + METRIC_FORMAT = 'influxdb_line' + METRIC_HANDLERS = ['influx-db-handler'] + NAMESPACE = 'default' + + CHECK_DICT_SCHEMA = { + # for unit tests + '$schema': 'http://json-schema.org/draft-07/schema#', + + 'definitions': { + 'metadata': { + 'type': 'object', + 'properties': { + 'name': {'type': 'string'}, + 'namespace': {'type': 'string'} + }, + 'required': ['name', 'namespace'] + } + }, + + 'type': 'object', + 'properties': { + 'command': {'type': 'string'}, + 'interval': {'type': 'integer'}, + 'subscriptions': { + 'type': 'array', + 'items': {'type': 'string'}, + 'minItems': 1 + }, + 'proxy_entity_name': {'type': 'string'}, + 'round_robin': {'type': 'boolean'}, + 'output_metric_format': {'type': 'string'}, + 'output_metric_handlers': { + 'type': 'array', + 'items': {'type': 'string'}, + 'minItems': 1 + }, + 'metadata': {'$ref': '#/definitions/metadata'}, + 'publish': {'type': 'boolean'} + }, + 'required': [ + 'command', 'interval', 'subscriptions', + 'proxy_entity_name', 'round_robin', + 'output_metric_format', 'output_metric_handlers', + 'metadata', 'publish'] + } + + def __init__(self): + self.publish = True + self.round_robin = True + self.interval = AbstractCheck.INTERVAL_S + self.subscriptions = copy.copy(AbstractCheck.SUBSCRIPTIONS) + self.output_metric_handlers = copy.copy(AbstractCheck.METRIC_HANDLERS) + self.output_metric_format = AbstractCheck.METRIC_FORMAT + self.namespace = AbstractCheck.NAMESPACE + + @property + def name(self): + # overridden implementation must return a string + assert False, 'property StandardCheck.name must be overridden' + + @property + def command(self): + # overridden implementation must return a string + assert False, 'property StandardCheck.command must be overridden' + + @property + def proxy_entity_name(self): + # overridden implementation must return a string + assert False, \ + 'property StandardCheck.proxy_entity_name must be overridden' + + def to_dict(self): + return { + 'command': self.command, + 'interval': self.interval, + 'subscriptions': sorted(self.subscriptions), + 'proxy_entity_name': self.proxy_entity_name, + 'round_robin': self.round_robin, + 'output_metric_format': self.output_metric_format, + 'output_metric_handlers': sorted(self.output_metric_handlers), + 'metadata': { + 'name': self.name, + 'namespace': self.namespace + }, + 'publish': self.publish + } + + @staticmethod + def match_check_dicts(a, b) -> bool: + if a['publish'] != b['publish']: + return False + if a['command'] != b['command']: + return False + if a['interval'] != b['interval']: + return False + if a['proxy_entity_name'] != b['proxy_entity_name']: + return False + if a['round_robin'] != b['round_robin']: + return False + if a['output_metric_format'] != b['output_metric_format']: + return False + if sorted(a['subscriptions']) != sorted(b['subscriptions']): + return False + if sorted(a['output_metric_handlers']) \ + != sorted(b['output_metric_handlers']): + return False + if a['metadata']['name'] != b['metadata']['name']: + return False + if a['metadata']['namespace'] != b['metadata']['namespace']: + return False + return True + + +def refresh(sensu_params, required_checks, current_checks): + """ + update any current_checks that are not present in required_checks + remove any extras + + :param sensu_params: + :param required_checks: list of AbstractCheck instances + :param current_checks: dict of {name:check_dict} from sensu + :return: dict with change counts + """ + + # cf. main.REFRESH_RESULT_SCHEMA + result = { + 'checks': len(current_checks), + 'input': len(required_checks), + 'created': 0, + 'updated': 0, + 'deleted': 0 + } + + for expected_check in required_checks: + + if expected_check.name not in current_checks: + create_check(sensu_params, expected_check.to_dict()) + result['created'] += 1 + elif not AbstractCheck.match_check_dicts( + current_checks[expected_check.name], + expected_check.to_dict()): + update_check(sensu_params, expected_check.to_dict()) + result['updated'] += 1 + + wanted_checks = {check.name for check in required_checks} + extra_checks = set(current_checks.keys()) - wanted_checks + for name in extra_checks: + delete_check(sensu_params, name) + + result['deleted'] = len(extra_checks) + return result diff --git a/setup.py b/setup.py index 5869c7014767c852d01296b5faeb5a31625a889b..3a42b85f51d7e227bd1530c6d66791682acd8b4e 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup( name='brian-polling-manager', - version="0.2", + version="0.3", author='GEANT', author_email='swd@geant.org', description='service for managing BRIAN polling checks', diff --git a/test/conftest.py b/test/conftest.py index 2553e8fcafecbeb13bf967ab4ae92e28ebe5f704..a2e99401dcc03df611c1d555c74562ffb496191b 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -36,14 +36,20 @@ def config(): 'interface-check': { 'script': '/var/lib/sensu/bin/counter2influx.sh', 'measurement': 'counters', - 'interval': 300, - 'subscriptions': ['interfacecounters'], - 'output_metric_handlers': ['influx-db-handler'], - 'namespace': 'default', - 'round_robin': True, '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}') + }, + 'dscp32-service-check': { + 'script': '/var/lib/sensu/bin/poll-gws-indirect.sh', + 'measurement': 'dscp32_counters', + 'command': '{script} {measurement} {service}' } }, 'statedir': state_dir_name, @@ -163,12 +169,21 @@ def mocked_sensu(): @pytest.fixture def mocked_inventory(): - # mocked api for returning all checks responses.add( method=responses.GET, url=re.compile(r'.*inventory.+/poller/interfaces.*'), body=_load_test_data('interfaces.json')) + responses.add( + method=responses.GET, + url=re.compile(r'.*inventory.+/poller/gws/direct'), + body=_load_test_data('gws-direct.json')) + + responses.add( + method=responses.GET, + url=re.compile(r'.*inventory.+/poller/gws/indirect.*'), + body=_load_test_data('gws-indirect.json')) + bogus_version = {'latch': {'timestamp': 10000 * random.random()}} # mocked api for returning all checks responses.add( diff --git a/test/data/checks.json b/test/data/checks.json index 6ff1cee3be38169120c93b88181119e626d559e2..5e0232bb0c1d89a9c3a06518cd781c8069cb1f10 100644 --- a/test/data/checks.json +++ b/test/data/checks.json @@ -1,4 +1,95 @@ [ + { + "command": "/var/lib/sensu/bin/poll-gws-direct.sh gwsd_counters ARNES Cogent 88.200.0.63 a", + "handlers": [], + "high_flap_threshold": 0, + "interval": 300, + "low_flap_threshold": 0, + "publish": true, + "runtime_assets": null, + "subscriptions": [ + "interfacecounters" + ], + "proxy_entity_name": "88.200.0.63", + "check_hooks": null, + "stdin": false, + "subdue": null, + "ttl": 0, + "timeout": 0, + "round_robin": true, + "output_metric_format": "influxdb_line", + "output_metric_handlers": [ + "influx-db-handler" + ], + "env_vars": null, + "metadata": { + "name": "gwsd-06D560CF", + "namespace": "default", + "created_by": "admin" + }, + "secrets": null + }, + { + "command": "/var/lib/sensu/bin/poll-gws-direct.sh gwsd_counters ARNES Cogent 88.200.0.63 x-to-be-deleted", + "handlers": [], + "high_flap_threshold": 0, + "interval": 300, + "low_flap_threshold": 0, + "publish": true, + "runtime_assets": null, + "subscriptions": [ + "interfacecounters" + ], + "proxy_entity_name": "88.200.0.63", + "check_hooks": null, + "stdin": false, + "subdue": null, + "ttl": 0, + "timeout": 0, + "round_robin": true, + "output_metric_format": "influxdb_line", + "output_metric_handlers": [ + "influx-db-handler" + ], + "env_vars": null, + "metadata": { + "name": "gwsd-AAAAA", + "namespace": "default", + "created_by": "admin" + }, + "secrets": null + }, + + { + "command": "/var/lib/sensu/bin/poll-gws-direct.sh gwsd_counters ARNES Cogent 88.200.0.63 to-be-updated", + "handlers": [], + "high_flap_threshold": 0, + "interval": 300, + "low_flap_threshold": 0, + "publish": true, + "runtime_assets": null, + "subscriptions": [ + "interfacecounters" + ], + "proxy_entity_name": "88.200.0.63", + "check_hooks": null, + "stdin": false, + "subdue": null, + "ttl": 0, + "timeout": 0, + "round_robin": true, + "output_metric_format": "influxdb_line", + "output_metric_handlers": [ + "influx-db-handler" + ], + "env_vars": null, + "metadata": { + "name": "gwsd-0B3898D4", + "namespace": "default", + "created_by": "admin" + }, + "secrets": null + }, { "command": "/var/lib/sensu/bin/counter2influx.sh counters 0pBiFbD mx1.ams.nl.geant.net ae1 1211", "handlers": [], diff --git a/test/data/gws-direct.json b/test/data/gws-direct.json new file mode 100644 index 0000000000000000000000000000000000000000..13708864c5b31be4b98630fcd174cea0172f3d1b --- /dev/null +++ b/test/data/gws-direct.json @@ -0,0 +1,538 @@ +[ + { + "nren": "ARNES", + "isp": "Cogent", + "hostname": "88.200.0.63", + "tag": "a", + "counters": [ + { + "field": "discards_in", + "oid": "1.3.6.1.2.1.2.2.1.13.533", + "snmp": { + "community": "gn2nocT3st" + } + }, + { + "field": "discards_out", + "oid": "1.3.6.1.2.1.2.2.1.19.533", + "snmp": { + "community": "gn2nocT3st" + } + }, + { + "field": "errors_in", + "oid": "1.3.6.1.2.1.2.2.1.14.533", + "snmp": { + "community": "gn2nocT3st" + } + }, + { + "field": "errors_out", + "oid": "1.3.6.1.2.1.2.2.1.20.533", + "snmp": { + "community": "gn2nocT3st" + } + } + ] + }, + { + "nren": "ARNES", + "isp": "Cogent", + "hostname": "88.200.0.63", + "tag": "b", + "counters": [ + { + "field": "traffic_in", + "oid": "1.3.6.1.2.1.31.1.1.1.6.531", + "snmp": { + "community": "gn2nocT3st" + } + }, + { + "field": "traffic_out", + "oid": "1.3.6.1.2.1.31.1.1.1.10.531", + "snmp": { + "community": "gn2nocT3st" + } + } + ] + }, + { + "nren": "ARNES", + "isp": "Cogent", + "hostname": "88.200.0.63", + "tag": "c", + "counters": [ + { + "field": "traffic_in", + "oid": "1.3.6.1.2.1.31.1.1.1.6.525", + "snmp": { + "community": "gn2nocT3st" + } + }, + { + "field": "traffic_out", + "oid": "1.3.6.1.2.1.31.1.1.1.10.525", + "snmp": { + "community": "gn2nocT3st" + } + } + ] + }, + { + "nren": "ARNES", + "isp": "Cogent", + "hostname": "88.200.0.63", + "tag": "d", + "counters": [ + { + "field": "traffic_in", + "oid": "1.3.6.1.2.1.31.1.1.1.6.553", + "snmp": { + "community": "gn2nocT3st" + } + }, + { + "field": "traffic_out", + "oid": "1.3.6.1.2.1.31.1.1.1.10.553", + "snmp": { + "community": "gn2nocT3st" + } + } + ] + }, + { + "nren": "ARNES", + "isp": "Cogent", + "hostname": "88.200.0.63", + "tag": "e", + "counters": [ + { + "field": "traffic_in", + "oid": "1.3.6.1.2.1.31.1.1.1.6.563", + "snmp": { + "community": "gn2nocT3st" + } + }, + { + "field": "traffic_out", + "oid": "1.3.6.1.2.1.31.1.1.1.10.563", + "snmp": { + "community": "gn2nocT3st" + } + } + ] + }, + { + "nren": "ARNES", + "isp": "Telia", + "hostname": "62.40.124.6", + "tag": "a", + "counters": [ + { + "field": "traffic_in", + "oid": "1.3.6.1.2.1.31.1.1.1.6.611", + "snmp": { + "community": "gn2nocT3st" + } + }, + { + "field": "traffic_out", + "oid": "1.3.6.1.2.1.31.1.1.1.10.611", + "snmp": { + "community": "gn2nocT3st" + } + } + ] + }, + { + "nren": "ARNES", + "isp": "Telia", + "hostname": "62.40.124.6", + "tag": "b", + "counters": [ + { + "field": "traffic_in", + "oid": "1.3.6.1.2.1.31.1.1.1.6.589", + "snmp": { + "community": "gn2nocT3st" + } + }, + { + "field": "traffic_out", + "oid": "1.3.6.1.2.1.31.1.1.1.10.589", + "snmp": { + "community": "gn2nocT3st" + } + } + ] + }, + { + "nren": "CARNET", + "isp": "Cogent", + "hostname": "62.40.124.10", + "tag": "a", + "counters": [ + { + "field": "traffic_in", + "oid": "1.3.6.1.2.1.31.1.1.1.6.35", + "snmp": { + "community": "atlas1453" + } + }, + { + "field": "traffic_out", + "oid": "1.3.6.1.2.1.31.1.1.1.10.35", + "snmp": { + "community": "atlas1453" + } + } + ] + }, + { + "nren": "CARNET", + "isp": "Telia", + "hostname": "62.40.125.150", + "tag": "a", + "counters": [ + { + "field": "traffic_in", + "oid": "1.3.6.1.2.1.31.1.1.1.6.48", + "snmp": { + "community": "atlas1453" + } + }, + { + "field": "traffic_out", + "oid": "1.3.6.1.2.1.31.1.1.1.10.48", + "snmp": { + "community": "atlas1453" + } + } + ] + }, + { + "nren": "KIFU", + "isp": "Cogent", + "hostname": "195.111.97.108", + "tag": "a", + "counters": [ + { + "field": "traffic_in", + "oid": "1.3.6.1.2.1.31.1.1.1.6.155", + "snmp": { + "community": "atlas1453" + } + }, + { + "field": "traffic_out", + "oid": "1.3.6.1.2.1.31.1.1.1.10.155", + "snmp": { + "community": "atlas1453" + } + } + ] + }, + { + "nren": "KIFU", + "isp": "Telia", + "hostname": "195.111.97.108", + "tag": "a", + "counters": [ + { + "field": "traffic_in", + "oid": "1.3.6.1.2.1.31.1.1.1.6.148", + "snmp": { + "community": "atlas1453" + } + }, + { + "field": "traffic_out", + "oid": "1.3.6.1.2.1.31.1.1.1.10.148", + "snmp": { + "community": "atlas1453" + } + } + ] + }, + { + "nren": "RedIRIS", + "isp": "Telia", + "hostname": "130.206.206.250", + "tag": "a", + "counters": [ + { + "field": "traffic_in", + "oid": "1.3.6.1.2.1.31.1.1.1.6.1487", + "snmp": { + "community": "atlas1453" + } + }, + { + "field": "traffic_out", + "oid": "1.3.6.1.2.1.31.1.1.1.10.1487", + "snmp": { + "community": "atlas1453" + } + } + ] + }, + { + "nren": "RedIRIS", + "isp": "Telia", + "hostname": "130.206.206.250", + "tag": "b", + "counters": [ + { + "field": "traffic_in", + "oid": "1.3.6.1.2.1.31.1.1.1.6.1488", + "snmp": { + "community": "atlas1453" + } + }, + { + "field": "traffic_out", + "oid": "1.3.6.1.2.1.31.1.1.1.10.1488", + "snmp": { + "community": "atlas1453" + } + } + ] + }, + { + "nren": "RedIRIS", + "isp": "Telia", + "hostname": "130.206.206.250", + "tag": "c", + "counters": [ + { + "field": "traffic_in", + "oid": "1.3.6.1.2.1.31.1.1.1.6.1489", + "snmp": { + "community": "atlas1453" + } + }, + { + "field": "traffic_out", + "oid": "1.3.6.1.2.1.31.1.1.1.10.1489", + "snmp": { + "community": "atlas1453" + } + } + ] + }, + { + "nren": "RedIRIS", + "isp": "Telia", + "hostname": "130.206.206.250", + "tag": "d", + "counters": [ + { + "field": "traffic_in", + "oid": "1.3.6.1.2.1.31.1.1.1.6.760", + "snmp": { + "community": "atlas1453" + } + }, + { + "field": "traffic_out", + "oid": "1.3.6.1.2.1.31.1.1.1.10.760", + "snmp": { + "community": "atlas1453" + } + } + ] + }, + { + "nren": "RedIRIS", + "isp": "Telia", + "hostname": "130.206.206.250", + "tag": "e", + "counters": [ + { + "field": "traffic_in", + "oid": "1.3.6.1.2.1.31.1.1.1.6.796", + "snmp": { + "community": "atlas1453" + } + }, + { + "field": "traffic_out", + "oid": "1.3.6.1.2.1.31.1.1.1.10.796", + "snmp": { + "community": "atlas1453" + } + } + ] + }, + { + "nren": "RoEduNet", + "isp": "Cogent", + "hostname": "149.6.50.10", + "tag": "a", + "counters": [ + { + "field": "traffic_in", + "oid": "1.3.6.1.2.1.31.1.1.1.6.531", + "snmp": { + "community": "dante" + } + }, + { + "field": "traffic_out", + "oid": "1.3.6.1.2.1.31.1.1.1.10.531", + "snmp": { + "community": "dante" + } + } + ] + }, + { + "nren": "RoEduNet", + "isp": "CenturyLink", + "hostname": "212.162.45.194", + "tag": "a", + "counters": [ + { + "field": "traffic_in", + "oid": "1.3.6.1.2.1.31.1.1.1.6.9", + "snmp": { + "community": "dante" + } + }, + { + "field": "traffic_out", + "oid": "1.3.6.1.2.1.31.1.1.1.10.9", + "snmp": { + "community": "dante" + } + } + ] + }, + { + "nren": "EENet", + "isp": "Telia", + "hostname": "193.40.133.2", + "tag": "a", + "counters": [ + { + "field": "traffic_in", + "oid": "1.3.6.1.2.1.31.1.1.1.6.263", + "snmp": { + "community": "geant-mon-telia" + } + }, + { + "field": "traffic_out", + "oid": "1.3.6.1.2.1.31.1.1.1.10.263", + "snmp": { + "community": "geant-mon-telia" + } + } + ] + }, + { + "nren": "PSNC", + "isp": "CenturyLink", + "hostname": "212.191.126.6", + "tag": "a", + "counters": [ + { + "field": "traffic_in", + "oid": "1.3.6.1.2.1.31.1.1.1.6.675", + "snmp": { + "community": "atlas1453" + } + }, + { + "field": "traffic_out", + "oid": "1.3.6.1.2.1.31.1.1.1.10.675", + "snmp": { + "community": "atlas1453" + } + } + ] + }, + { + "nren": "PSNC", + "isp": "CenturyLink", + "hostname": "212.191.126.7", + "tag": "b", + "counters": [ + { + "field": "traffic_in", + "oid": "1.3.6.1.2.1.31.1.1.1.6.677", + "snmp": { + "community": "atlas1453" + } + }, + { + "field": "traffic_out", + "oid": "1.3.6.1.2.1.31.1.1.1.10.677", + "snmp": { + "community": "atlas1453" + } + } + ] + }, + { + "nren": "FCCN", + "isp": "Cogent", + "hostname": "193.136.5.43", + "tag": "a", + "counters": [ + { + "field": "traffic_in", + "oid": "1.3.6.1.2.1.31.1.1.1.6.47", + "snmp": { + "community": "geantcom" + } + }, + { + "field": "traffic_out", + "oid": "1.3.6.1.2.1.31.1.1.1.10.47", + "snmp": { + "community": "geantcom" + } + } + ] + }, + { + "nren": "HEANET", + "isp": "CenturyLink", + "hostname": "core2-cwt.nn.hea.net", + "tag": "a", + "counters": [ + { + "field": "traffic_in", + "oid": "1.3.6.1.2.1.31.1.1.1.6.645", + "snmp": { + "sec-name": "geant", + "auth": { + "protocol": "MD5", + "password": "aagagag" + }, + "priv": { + "protocol": "DES", + "password": "asdfadfads" + } + } + }, + { + "field": "traffic_out", + "oid": "1.3.6.1.2.1.31.1.1.1.10.645", + "snmp": { + "sec-name": "geant", + "auth": { + "protocol": "MD5", + "password": "aagagag" + }, + "priv": { + "protocol": "DES", + "password": "asdfadfads" + } + } + } + ] + } +] diff --git a/test/data/gws-indirect.json b/test/data/gws-indirect.json new file mode 100644 index 0000000000000000000000000000000000000000..c3b2d352adae77f4cf4f2f50e6d4cc74b53fa845 --- /dev/null +++ b/test/data/gws-indirect.json @@ -0,0 +1 @@ +[{"id": 662955, "name": "RENAM-AP-IAS", "customer": "RENAM", "speed": 10737418240, "pop": "BUCHAREST", "hostname": "mx1.buc.ro.geant.net", "interface": "ae12.333", "type": "GWS - INDIRECT", "status": "non-monitored"}, {"id": 702139, "name": "FR_ARN_IAS", "customer": "ARN", "speed": 10737418240, "pop": "MARSEILLE", "hostname": "mx1.mar.fr.geant.net", "interface": "ae18.333", "type": "GWS - INDIRECT", "status": "operational"}, {"id": 663103, "name": "AMRES-AP-IAS", "customer": "AMRES", "speed": 21474836480, "pop": "BUDAPEST", "hostname": "mx1.bud.hu.geant.net", "interface": "ae16.333", "type": "GWS - INDIRECT", "status": "operational"}, {"id": 663198, "name": "CYNET-AP3-IAS", "customer": "CYNET", "speed": 10737418240, "pop": "ATHENS 2", "hostname": "mx1.ath2.gr.geant.net", "interface": "ae12.333", "type": "GWS - INDIRECT", "status": "operational"}, {"id": 663075, "name": "BELNET-AP3-IAS", "customer": "BELNET", "speed": 107374182400, "pop": "AMSTERDAM", "hostname": "mx1.ams.nl.geant.net", "interface": "ae13.333", "type": "GWS - INDIRECT", "status": "operational"}, {"id": 661220, "name": "UK_ASREN_IAS", "customer": "ASREN", "speed": 10737418240, "pop": "LONDON 2", "hostname": "mx1.lon2.uk.geant.net", "interface": "ae17.333", "type": "GWS - INDIRECT", "status": "operational"}, {"id": 661313, "name": "MARNET-AP2-IAS", "customer": "MARNET", "speed": 10737418240, "pop": "VIENNA", "hostname": "mx1.vie.at.geant.net", "interface": "ae17.333", "type": "GWS - INDIRECT", "status": "operational"}, {"id": 661510, "name": "UOM-AP1-IAS", "customer": "UOM", "speed": 10737418240, "pop": "MILAN 2 CALDERA", "hostname": "mx1.mil2.it.geant.net", "interface": "xe-11/0/0.333", "type": "GWS - INDIRECT", "status": "operational"}, {"id": 661936, "name": "KIFU-AP2-IAS", "customer": "KIFU", "speed": 42949672960, "pop": "VIENNA", "hostname": "mx1.vie.at.geant.net", "interface": "ae18.333", "type": "GWS - INDIRECT", "status": "operational"}, {"id": 663070, "name": "KIFU_AP1_IAS", "customer": "KIFU", "speed": 107374182400, "pop": "BUDAPEST", "hostname": "mx1.bud.hu.geant.net", "interface": "ae10.333", "type": "GWS - INDIRECT", "status": "operational"}, {"id": 661641, "name": "FCCN-AP1-IAS", "customer": "FCCN", "speed": 42949672960, "pop": "LISBON", "hostname": "mx2.lis.pt.geant.net", "interface": "ae10.333", "type": "GWS - INDIRECT", "status": "operational"}, {"id": 663220, "name": "RESTENA-AP1-IAS", "customer": "RESTENA", "speed": 107374182400, "pop": "FRANKFURT", "hostname": "mx1.fra.de.geant.net", "interface": "ae18.333", "type": "GWS - INDIRECT", "status": "operational"}, {"id": 661496, "name": "SANET_AP_IAS", "customer": "SANET", "speed": 21474836480, "pop": "BRATISLAVA", "hostname": "mx2.bra.sk.geant.net", "interface": "ae13.421", "type": "GWS - INDIRECT", "status": "operational"}, {"id": 662908, "name": "MREN-AP-IAS", "customer": "MREN", "speed": 10737418240, "pop": "BUDAPEST", "hostname": "mx1.bud.hu.geant.net", "interface": "ae15.333", "type": "GWS - INDIRECT", "status": "operational"}, {"id": 661308, "name": "RASH-AP1-IAS", "customer": "RASH", "speed": 10737418240, "pop": "MILAN 2 CALDERA", "hostname": "mx1.mil2.it.geant.net", "interface": "ae13.333", "type": "GWS - INDIRECT", "status": "operational"}, {"id": 663228, "name": "IUCC-AP2-IAS", "customer": "IUCC", "speed": 32212254720, "pop": "FRANKFURT", "hostname": "mx1.fra.de.geant.net", "interface": "ae21.333", "type": "GWS - INDIRECT", "status": "operational"}, {"id": 679570, "name": "ROEDUNET-AP2-IAS", "customer": "ROEDUNET", "speed": 107374182400, "pop": "VIENNA", "hostname": "mx1.vie.at.geant.net", "interface": "ae21.333", "type": "GWS - INDIRECT", "status": "operational"}, {"id": 702074, "name": "CYNET_AP2_IAS", "customer": "CYNET", "speed": 10737418240, "pop": "FRANKFURT", "hostname": "mx1.fra.de.geant.net", "interface": "ae38.333", "type": "GWS - INDIRECT", "status": "operational"}, {"id": 707416, "name": "LITNET-AP2-IAS", "customer": "LITNET", "speed": 10737418240, "pop": "KAUNAS", "hostname": "rt1.kau.lt.geant.net", "interface": "xe-0/1/1.333", "type": "GWS - INDIRECT", "status": "operational"}, {"id": 662907, "name": "GRNET-AP2-IAS", "customer": "GRNET", "speed": 42949672960, "pop": "ATHENS 2", "hostname": "mx1.ath2.gr.geant.net", "interface": "ae11.333", "type": "GWS - INDIRECT", "status": "operational"}, {"id": 673674, "name": "BELNET_AP2_IAS", "customer": "BELNET", "speed": 107374182400, "pop": "LONDON", "hostname": "mx1.lon.uk.geant.net", "interface": "ae16.333", "type": "GWS - INDIRECT", "status": "operational"}, {"id": 661222, "name": "FCCN-AP2-IAS", "customer": "FCCN", "speed": 42949672960, "pop": "LISBON 2", "hostname": "mx1.lis.pt.geant.net", "interface": "ae10.333", "type": "GWS - INDIRECT", "status": "operational"}, {"id": 661977, "name": "GRNET-AP1-IAS", "customer": "GRNET", "speed": 42949672960, "pop": "ATHENS", "hostname": "mx2.ath.gr.geant.net", "interface": "ae10.333", "type": "GWS - INDIRECT", "status": "operational"}, {"id": 661500, "name": "IUCC-AP1-IAS", "customer": "IUCC", "speed": 32212254720, "pop": "LONDON", "hostname": "mx1.lon.uk.geant.net", "interface": "ae21.333", "type": "GWS - INDIRECT", "status": "operational"}, {"id": 663112, "name": "ROEDUNET_AP1_IAS", "customer": "ROEDUNET", "speed": 42949672960, "pop": "BUCHAREST", "hostname": "mx1.buc.ro.geant.net", "interface": "ae11.333", "type": "GWS - INDIRECT", "status": "operational"}, {"id": 678593, "name": "URAN-AP1-IAS", "customer": "URAN", "speed": 21474836480, "pop": "VIENNA", "hostname": "mx1.vie.at.geant.net", "interface": "ae20.333", "type": "GWS - INDIRECT", "status": "operational"}, {"id": 661703, "name": "LITNET_AP1_IAS", "customer": "LITNET", "speed": 10737418240, "pop": "KAUNAS", "hostname": "rt2.kau.lt.geant.net", "interface": "xe-0/1/1.333", "type": "GWS - INDIRECT", "status": "operational"}] \ No newline at end of file diff --git a/test/test_sensu_checks.py b/test/test_sensu_checks.py index 7a5a8178cfd72ff4d3d6a1899b1d1012e45f5df4..b72dd0578a020747bf5a648f1933a5c2a5cf6148 100644 --- a/test/test_sensu_checks.py +++ b/test/test_sensu_checks.py @@ -1,6 +1,7 @@ import copy import random +import jsonschema import responses from brian_polling_manager import sensu, inventory, interfaces @@ -8,7 +9,7 @@ from brian_polling_manager import sensu, inventory, interfaces @responses.activate def test_load_checks(config, mocked_sensu): - checks = list(sensu.load_all_checks(config['sensu'])) + checks = sensu.load_all_checks(config['sensu']) assert len(checks) > 0 # test data has checks in it @@ -19,9 +20,9 @@ def test_check_lifecycle(config, mocked_sensu, mocked_inventory): inventory.load_interfaces(config['inventory'])) test_interface['name'] = 'xyz' - new_check = interfaces._make_check( + new_check = interfaces.InterfaceCheck( config['sensu']['interface-check'], - test_interface) + test_interface).to_dict() # create the new check check_name = new_check['metadata']['name'] @@ -38,3 +39,84 @@ def test_check_lifecycle(config, mocked_sensu, mocked_inventory): # delete the check and confirm the correct call was made sensu.delete_check(config['sensu'], check_name) assert check_name not in mocked_sensu + + +class DummyCheck(sensu.AbstractCheck): + + def __init__(self, name, command, proxy_entity_name): + super().__init__() + self._name = name + self._command = command + self._proxy_entity_name = proxy_entity_name + + @sensu.AbstractCheck.name.getter + def name(self): + return self._name + + @sensu.AbstractCheck.command.getter + def command(self): + return self._command + + @sensu.AbstractCheck.proxy_entity_name.getter + def proxy_entity_name(self): + return self._proxy_entity_name + + +def test_check_dict_schema(): + c = DummyCheck(name='x', command='y', proxy_entity_name='z') + jsonschema.validate(c.to_dict(), sensu.AbstractCheck.CHECK_DICT_SCHEMA) + + +def test_check_compare(): + a = DummyCheck(name='x', command='y', proxy_entity_name='z') + b = DummyCheck(name='x', command='y', proxy_entity_name='z') + assert sensu.checks_match(a.to_dict(), b.to_dict()) + + +def test_checks_differ(): + a = DummyCheck(name='x', command='x', proxy_entity_name='1') + b = DummyCheck(name='x', command='x', proxy_entity_name='2') + assert not sensu.checks_match(a.to_dict(), b.to_dict()) + + a = DummyCheck(name='x', command='1', proxy_entity_name='x') + b = DummyCheck(name='x', command='2', proxy_entity_name='x') + assert not sensu.checks_match(a.to_dict(), b.to_dict()) + + a = DummyCheck(name='1', command='x', proxy_entity_name='x') + b = DummyCheck(name='2', command='x', proxy_entity_name='x') + assert not sensu.checks_match(a.to_dict(), b.to_dict()) + + a = DummyCheck(name='x', command='x', proxy_entity_name='x') + b = DummyCheck(name='x', command='x', proxy_entity_name='x') + a.publish = not a.publish + assert not sensu.checks_match(a.to_dict(), b.to_dict()) + + a = DummyCheck(name='x', command='x', proxy_entity_name='x') + b = DummyCheck(name='x', command='x', proxy_entity_name='x') + a.interval += 1 + assert not sensu.checks_match(a.to_dict(), b.to_dict()) + + a = DummyCheck(name='x', command='x', proxy_entity_name='x') + b = DummyCheck(name='x', command='x', proxy_entity_name='x') + a.round_robin = not a.round_robin + assert not sensu.checks_match(a.to_dict(), b.to_dict()) + + a = DummyCheck(name='x', command='x', proxy_entity_name='x') + b = DummyCheck(name='x', command='x', proxy_entity_name='x') + a.output_metric_format = a.output_metric_format + 'x' + assert not sensu.checks_match(a.to_dict(), b.to_dict()) + + a = DummyCheck(name='x', command='x', proxy_entity_name='x') + b = DummyCheck(name='x', command='x', proxy_entity_name='x') + a.subscriptions.append('x') + assert not sensu.checks_match(a.to_dict(), b.to_dict()) + + a = DummyCheck(name='x', command='x', proxy_entity_name='x') + b = DummyCheck(name='x', command='x', proxy_entity_name='x') + a.output_metric_handlers.append('x') + assert not sensu.checks_match(a.to_dict(), b.to_dict()) + + a = DummyCheck(name='x', command='x', proxy_entity_name='x') + b = DummyCheck(name='x', command='x', proxy_entity_name='x') + a.namespace = a.namespace + 'x' + assert not sensu.checks_match(a.to_dict(), b.to_dict())