Skip to content
Snippets Groups Projects
Commit 0559c373 authored by Erik Reid's avatar Erik Reid
Browse files

Finished feature POL1-444-poll-gws-direct-counters.

parents ae0d9b3d 5fd219af
Branches
Tags
No related merge requests found
...@@ -29,14 +29,14 @@ _DEFAULT_CONFIG = { ...@@ -29,14 +29,14 @@ _DEFAULT_CONFIG = {
'interface-check': { 'interface-check': {
'script': '/var/lib/sensu/bin/counter2influx.sh', 'script': '/var/lib/sensu/bin/counter2influx.sh',
'measurement': 'counters', 'measurement': 'counters',
'interval': 300,
'subscriptions': ['interfacecounters'],
'output_metric_handlers': ['influx-db-handler'],
'namespace': 'default',
'round_robin': True,
'command': ('{script} {measurement} ' 'command': ('{script} {measurement} '
'{community} {hostname} ' '{community} {hostname} '
'{interface} {ifIndex}'), '{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}'
} }
}, },
'statedir': '/tmp/', 'statedir': '/tmp/',
...@@ -56,22 +56,9 @@ CONFIG_SCHEMA = { ...@@ -56,22 +56,9 @@ CONFIG_SCHEMA = {
'properties': { 'properties': {
'script': {'type': 'string'}, 'script': {'type': 'string'},
'measurement': {'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'}, 'command': {'type': 'string'},
}, },
'required': ['script', 'measurement', 'interval', 'required': ['script', 'measurement', 'command'],
'subscriptions', 'output_metric_handlers',
'namespace', 'round_robin', 'command'],
'additionalProperties': False 'additionalProperties': False
}, },
'sensu': { 'sensu': {
...@@ -83,9 +70,13 @@ CONFIG_SCHEMA = { ...@@ -83,9 +70,13 @@ CONFIG_SCHEMA = {
'minItems': 1 'minItems': 1
}, },
'api-key': {'type': 'string'}, 'api-key': {'type': 'string'},
'interface-check': {'$ref': '#/definitions/influx-check'} 'interface-check': {'$ref': '#/definitions/influx-check'},
'gws-direct-interface-check':
{'$ref': '#/definitions/influx-check'}
}, },
'required': ['api-base', 'api-key', 'interface-check'], 'required': [
'api-base', 'api-key',
'interface-check', 'gws-direct-interface-check'],
'additionalProperties': False 'additionalProperties': False
}, },
'statsd': { 'statsd': {
...@@ -118,6 +109,7 @@ CONFIG_SCHEMA = { ...@@ -118,6 +109,7 @@ CONFIG_SCHEMA = {
class State(object): class State(object):
GWS_DIRECT = 'gws-direct.json'
INTERFACES = 'interfaces.json' INTERFACES = 'interfaces.json'
STATE = 'state.json' STATE = 'state.json'
...@@ -133,9 +125,10 @@ class State(object): ...@@ -133,9 +125,10 @@ class State(object):
def __init__(self, state_dir: str): def __init__(self, state_dir: str):
assert os.path.isdir(state_dir) assert os.path.isdir(state_dir)
self.filenames = { self.cache_filenames = {
'state': os.path.join(state_dir, State.STATE), '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)
} }
@staticmethod @staticmethod
...@@ -150,38 +143,58 @@ class State(object): ...@@ -150,38 +143,58 @@ class State(object):
f'unable to open state file {filename}') f'unable to open state file {filename}')
return None 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 @property
def last(self) -> int: 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 return state['last'] if state else -1
@last.setter @last.setter
def last(self, new_last: Union[float, None]): def last(self, new_last: Union[float, None]):
if not new_last or new_last < 0: if not new_last or new_last < 0:
os.unlink(self.filenames['state']) os.unlink(self.cache_filenames['state'])
else: else:
state = {'last': new_last} State._save_json(
with open(self.filenames['state'], 'w') as f: self.cache_filenames['state'],
f.write(json.dumps(state)) {'last': new_last},
State.STATE_SCHEMA)
@property @property
def interfaces(self) -> list: def interfaces(self) -> list:
return State._load_json( return State._load_json(
self.filenames['cache'], self.cache_filenames['interfaces'],
inventory.INVENTORY_INTERFACES_SCHEMA) inventory.INVENTORY_INTERFACES_SCHEMA)
@interfaces.setter @interfaces.setter
def interfaces(self, new_interfaces): def interfaces(self, new_interfaces):
try: State._save_json(
jsonschema.validate( self.cache_filenames['interfaces'],
new_interfaces, new_interfaces,
inventory.INVENTORY_INTERFACES_SCHEMA) inventory.INVENTORY_INTERFACES_SCHEMA)
except jsonschema.ValidationError:
logger.exception('invalid interface state data')
return
with open(self.filenames['cache'], 'w') as f: @property
f.write(json.dumps(new_interfaces)) 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)
def _setup_logging(filename=None): def _setup_logging(filename=None):
......
...@@ -11,100 +11,47 @@ def load_ifc_checks(sensu_params): ...@@ -11,100 +11,47 @@ def load_ifc_checks(sensu_params):
name = check['metadata']['name'] name = check['metadata']['name']
# check-* is the old-style name (add to the returned # check-* is the old-style name (add to the returned
# data so it can be deleted) # 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)) ifc_checks = filter(_is_ifc_check, sensu.load_all_checks(sensu_params))
return {c['metadata']['name']: c for c in ifc_checks} return {c['metadata']['name']: c for c in ifc_checks}
def _check_name(interface): class InterfaceCheck(sensu.AbstractCheck):
ifc_name = interface['name'].replace('/', '-')
return f'ifc-{interface["router"]}-{ifc_name}'
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): @sensu.AbstractCheck.name.getter
command = check_params['command'].format( def name(self):
script=check_params['script'], ifc_name = self.interface['name'].replace('/', '-')
measurement=check_params['measurement'], return f'ifc-{self.interface["router"]}-{ifc_name}'
community='0pBiFbD', # TODO: add this to /poller/interfaces response
hostname=interface['router'],
interface=interface['name'],
ifIndex=interface['snmp-index']
)
return { @sensu.AbstractCheck.command.getter
'command': command, def command(self):
'interval': check_params['interval'], return self.ifc_check_params['command'].format(
'subscriptions': sorted(check_params['subscriptions']), script=self.ifc_check_params['script'],
'proxy_entity_name': interface['router'], measurement=self.ifc_check_params['measurement'],
'round_robin': check_params['round_robin'], # TODO: add community string to /poller/interfaces response
'output_metric_format': 'influxdb_line', # (cf. POL1-339)
'output_metric_handlers': sorted( community='0pBiFbD',
check_params['output_metric_handlers']), hostname=self.interface['router'],
'metadata': { interface=self.interface['name'],
'name': _check_name(interface), ifIndex=self.interface['snmp-index'])
'namespace': check_params['namespace']
},
'publish': True
}
@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) return sensu.refresh(
sensu_params,
created = 0 required_checks,
updated = 0 load_ifc_checks(sensu_params))
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)
}
...@@ -6,7 +6,7 @@ import requests ...@@ -6,7 +6,7 @@ import requests
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# minimal inventory response schema # minimal inventory response schema for our purposes
INVENTORY_VERSION_SCHEMA = { INVENTORY_VERSION_SCHEMA = {
'$schema': 'http://json-schema.org/draft-07/schema#', '$schema': 'http://json-schema.org/draft-07/schema#',
...@@ -29,50 +29,23 @@ INVENTORY_VERSION_SCHEMA = { ...@@ -29,50 +29,23 @@ INVENTORY_VERSION_SCHEMA = {
'additionalProperties': True 'additionalProperties': True
} }
# minimal inventory response schema for our purposes
INVENTORY_INTERFACES_SCHEMA = { INVENTORY_INTERFACES_SCHEMA = {
'$schema': 'http://json-schema.org/draft-07/schema#', '$schema': 'http://json-schema.org/draft-07/schema#',
'definitions': { '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': { 'interface': {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
'router': {'type': 'string'}, 'router': {'type': 'string'},
'name': {'type': 'string'}, 'name': {'type': 'string'},
'description': {'type': 'string'},
'snmp-index': { 'snmp-index': {
'type': 'integer', 'type': 'integer',
'minimum': 1 'minimum': 1
},
'bundle': {
'type': 'array',
'items': {'type': 'string'}
},
'bundle-parents': {
'type': 'array',
'items': {'type': 'string'}
},
'circuits': {
'type': 'array',
'items': {'$ref': '#/definitions/service'}
} }
}, },
'required': [ 'required': ['router', 'name', 'snmp-index'],
'router', 'name', 'description', }
'snmp-index', 'bundle', 'bundle-parents',
'circuits'],
'additionalProperties': False
},
}, },
'type': 'array', 'type': 'array',
...@@ -80,33 +53,82 @@ INVENTORY_INTERFACES_SCHEMA = { ...@@ -80,33 +53,82 @@ INVENTORY_INTERFACES_SCHEMA = {
} }
def _pick_one(things): # minimal inventory response schema for our purposes
if not isinstance(things, (list, tuple, set)): GWS_DIRECT_SCHEMA = {
things = [things] '$schema': 'http://json-schema.org/draft-07/schema#',
return random.choice(things)
'definitions': {
'interface-counters': {
'type': 'object',
'properties': {
'nren': {'type': 'string'},
'isp': {
'type': 'string',
'enum': ['Cogent', 'Telia', 'Century Link']
},
'hostname': {'type': 'string'},
'tag': {'type': 'string'},
},
'required': ['nren', 'isp', 'hostname', 'tag']
}
},
'type': 'array',
'items': {'$ref': '#/definitions/interface-counters'}
}
def load_interfaces(base_urls):
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 Load & decode the specified inventory api data
and return a slightly reformatted dict.
:param api_route: the api-specific handler route
:param base_urls: inventory provider base api url, or a list of them :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) url = _pick_one(base_urls)
logger.debug(f'using inventory base api url: {url}') logger.debug(f'using inventory base api url: {url}')
rsp = requests.get( rsp = requests.get(
f'{url}/poller/interfaces', f'{url}/{api_route}',
headers={'Accept': 'application/json'}) headers={'Accept': 'application/json'})
rsp.raise_for_status() rsp.raise_for_status()
result = rsp.json() result = rsp.json()
jsonschema.validate(result, INVENTORY_INTERFACES_SCHEMA) jsonschema.validate(result, schema)
return result 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 last_update_timestamp(base_urls) -> float: def last_update_timestamp(base_urls) -> float:
try: try:
r = requests.get( r = requests.get(
......
...@@ -29,7 +29,8 @@ import click ...@@ -29,7 +29,8 @@ import click
import jsonschema import jsonschema
from statsd import StatsClient from statsd import StatsClient
from brian_polling_manager import inventory, interfaces, configuration from brian_polling_manager \
import inventory, configuration, interfaces, gws_direct
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -51,7 +52,8 @@ REFRESH_RESULT_SCHEMA = { ...@@ -51,7 +52,8 @@ REFRESH_RESULT_SCHEMA = {
}, },
'type': 'object', 'type': 'object',
'properties': { 'properties': {
'interfaces': {'$ref': '#/definitions/refresh-result'} 'interfaces': {'$ref': '#/definitions/refresh-result'},
'gws_direct': {'$ref': '#/definitions/refresh-result'}
}, },
'required': ['interfaces'], 'required': ['interfaces'],
'additionalProperties': False 'additionalProperties': False
...@@ -76,24 +78,27 @@ def refresh(config, force=False): ...@@ -76,24 +78,27 @@ def refresh(config, force=False):
if force or not last or last != state.last: if force or not last or last != state.last:
state.last = last state.last = last
state.interfaces = inventory.load_interfaces(config['inventory']) state.interfaces = inventory.load_interfaces(config['inventory'])
state.gws_direct = inventory.load_gws_direct_interfaces(
config['inventory'])
result = { result = {
'interfaces': interfaces.refresh(config['sensu'], state) 'interfaces': interfaces.refresh(config['sensu'], state.interfaces),
'gws_direct': gws_direct.refresh(config['sensu'], state.gws_direct)
} }
jsonschema.validate(result, REFRESH_RESULT_SCHEMA) # sanity
statsd_config = config.get('statsd', None) statsd_config = config.get('statsd', None)
if statsd_config: if statsd_config:
statsd = StatsClient( for key, counts in result.items():
host=statsd_config['hostname'], statsd = StatsClient(
port=statsd_config['port'], host=statsd_config['hostname'],
prefix=f'{statsd_config["prefix"]}_interfaces') port=statsd_config['port'],
statsd.gauge('checks', result['interfaces']['checks']) prefix=f'{statsd_config["prefix"]}_{key}')
statsd.gauge('input', result['interfaces']['input']) statsd.gauge('checks', counts['checks'])
statsd.gauge('created', result['interfaces']['created']) statsd.gauge('input', counts['input'])
statsd.gauge('updated', result['interfaces']['updated']) statsd.gauge('created', counts['created'])
statsd.gauge('deleted', result['interfaces']['deleted']) statsd.gauge('updated', counts['updated'])
statsd.gauge('deleted', counts['deleted'])
jsonschema.validate(result, REFRESH_RESULT_SCHEMA) # sanity
return result return result
......
""" """
Sensu api utilities Sensu api utilities
""" """
import copy
import logging import logging
import random import random
import requests import requests
...@@ -8,7 +9,14 @@ import requests ...@@ -8,7 +9,14 @@ import requests
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
_cached_checks = None # not using lru_cache, since params is a dict
def load_all_checks(params, namespace='default'): 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']) url = random.choice(params['api-base'])
r = requests.get( r = requests.get(
f'{url}/api/core/v2/namespaces/{namespace}/checks', f'{url}/api/core/v2/namespaces/{namespace}/checks',
...@@ -18,8 +26,8 @@ def load_all_checks(params, namespace='default'): ...@@ -18,8 +26,8 @@ def load_all_checks(params, namespace='default'):
}) })
r.raise_for_status() r.raise_for_status()
for check in r.json(): _cached_checks = r.json()
yield check return _cached_checks
def create_check(params, check, namespace='default'): def create_check(params, check, namespace='default'):
...@@ -60,3 +68,188 @@ def delete_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}', f'{url}/api/core/v2/namespaces/{namespace}/checks/{name}',
headers={'Authorization': f'Key {params["api-key"]}'}) headers={'Authorization': f'Key {params["api-key"]}'})
r.raise_for_status() 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': None
}
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
...@@ -36,14 +36,15 @@ def config(): ...@@ -36,14 +36,15 @@ def config():
'interface-check': { 'interface-check': {
'script': '/var/lib/sensu/bin/counter2influx.sh', 'script': '/var/lib/sensu/bin/counter2influx.sh',
'measurement': 'counters', 'measurement': 'counters',
'interval': 300,
'subscriptions': ['interfacecounters'],
'output_metric_handlers': ['influx-db-handler'],
'namespace': 'default',
'round_robin': True,
'command': ('{script} {measurement} ' 'command': ('{script} {measurement} '
'{community} {hostname} ' '{community} {hostname} '
'{interface} {ifIndex}'), '{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}')
} }
}, },
'statedir': state_dir_name, 'statedir': state_dir_name,
...@@ -169,6 +170,12 @@ def mocked_inventory(): ...@@ -169,6 +170,12 @@ def mocked_inventory():
url=re.compile(r'.*inventory.+/poller/interfaces.*'), url=re.compile(r'.*inventory.+/poller/interfaces.*'),
body=_load_test_data('interfaces.json')) body=_load_test_data('interfaces.json'))
# mocked api for returning all checks
responses.add(
method=responses.GET,
url=re.compile(r'.*inventory.+/poller/gws/direct'),
body=_load_test_data('gws-direct.json'))
bogus_version = {'latch': {'timestamp': 10000 * random.random()}} bogus_version = {'latch': {'timestamp': 10000 * random.random()}}
# mocked api for returning all checks # mocked api for returning all checks
responses.add( responses.add(
......
[ [
{
"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", "command": "/var/lib/sensu/bin/counter2influx.sh counters 0pBiFbD mx1.ams.nl.geant.net ae1 1211",
"handlers": [], "handlers": [],
......
[{"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", "community": "gn2nocT3st"}, {"field": "discards_out", "oid": "1.3.6.1.2.1.2.2.1.19.533", "community": "gn2nocT3st"}, {"field": "errors_in", "oid": "1.3.6.1.2.1.2.2.1.14.533", "community": "gn2nocT3st"}, {"field": "errors_out", "oid": "1.3.6.1.2.1.2.2.1.20.533", "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", "community": "gn2nocT3st"}, {"field": "traffic_out", "oid": "1.3.6.1.2.1.31.1.1.1.10.531", "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", "community": "gn2nocT3st"}, {"field": "traffic_out", "oid": "1.3.6.1.2.1.31.1.1.1.10.525", "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", "community": "gn2nocT3st"}, {"field": "traffic_out", "oid": "1.3.6.1.2.1.31.1.1.1.10.553", "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", "community": "gn2nocT3st"}, {"field": "traffic_out", "oid": "1.3.6.1.2.1.31.1.1.1.10.563", "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", "community": "gn2nocT3st"}, {"field": "traffic_out", "oid": "1.3.6.1.2.1.31.1.1.1.10.611", "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", "community": "gn2nocT3st"}, {"field": "traffic_out", "oid": "1.3.6.1.2.1.31.1.1.1.10.589", "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", "community": "atlas1453"}, {"field": "traffic_out", "oid": "1.3.6.1.2.1.31.1.1.1.10.35", "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", "community": "atlas1453"}, {"field": "traffic_out", "oid": "1.3.6.1.2.1.31.1.1.1.10.48", "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", "community": "atlas1453"}, {"field": "traffic_out", "oid": "1.3.6.1.2.1.31.1.1.1.10.155", "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", "community": "atlas1453"}, {"field": "traffic_out", "oid": "1.3.6.1.2.1.31.1.1.1.10.148", "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", "community": "atlas1453"}, {"field": "traffic_out", "oid": "1.3.6.1.2.1.31.1.1.1.10.1487", "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", "community": "atlas1453"}, {"field": "traffic_out", "oid": "1.3.6.1.2.1.31.1.1.1.10.1488", "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", "community": "atlas1453"}, {"field": "traffic_out", "oid": "1.3.6.1.2.1.31.1.1.1.10.1489", "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", "community": "atlas1453"}, {"field": "traffic_out", "oid": "1.3.6.1.2.1.31.1.1.1.10.760", "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", "community": "atlas1453"}, {"field": "traffic_out", "oid": "1.3.6.1.2.1.31.1.1.1.10.796", "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", "community": "dante"}, {"field": "traffic_out", "oid": "1.3.6.1.2.1.31.1.1.1.10.531", "community": "dante"}]}, {"nren": "RoEduNet", "isp": "Century Link", "hostname": "212.162.45.194", "tag": "a", "counters": [{"field": "traffic_in", "oid": "1.3.6.1.2.1.31.1.1.1.6.9", "community": "dante"}, {"field": "traffic_out", "oid": "1.3.6.1.2.1.31.1.1.1.10.9", "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", "community": "geant-mon-telia"}, {"field": "traffic_out", "oid": "1.3.6.1.2.1.31.1.1.1.10.263", "community": "geant-mon-telia"}]}, {"nren": "PSNC", "isp": "Century Link", "hostname": "212.191.126.6", "tag": "a", "counters": [{"field": "traffic_in", "oid": "1.3.6.1.2.1.31.1.1.1.6.675", "community": "atlas1453"}, {"field": "traffic_out", "oid": "1.3.6.1.2.1.31.1.1.1.10.675", "community": "atlas1453"}]}, {"nren": "PSNC", "isp": "Century Link", "hostname": "212.191.126.7", "tag": "a", "counters": [{"field": "traffic_in", "oid": "1.3.6.1.2.1.31.1.1.1.6.677", "community": "atlas1453"}, {"field": "traffic_out", "oid": "1.3.6.1.2.1.31.1.1.1.10.677", "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", "community": "geantcom"}, {"field": "traffic_out", "oid": "1.3.6.1.2.1.31.1.1.1.10.47", "community": "geantcom"}]}]
\ No newline at end of file
import copy import copy
import random import random
import jsonschema
import responses import responses
from brian_polling_manager import sensu, inventory, interfaces from brian_polling_manager import sensu, inventory, interfaces
...@@ -8,7 +9,7 @@ from brian_polling_manager import sensu, inventory, interfaces ...@@ -8,7 +9,7 @@ from brian_polling_manager import sensu, inventory, interfaces
@responses.activate @responses.activate
def test_load_checks(config, mocked_sensu): 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 assert len(checks) > 0 # test data has checks in it
...@@ -19,9 +20,9 @@ def test_check_lifecycle(config, mocked_sensu, mocked_inventory): ...@@ -19,9 +20,9 @@ def test_check_lifecycle(config, mocked_sensu, mocked_inventory):
inventory.load_interfaces(config['inventory'])) inventory.load_interfaces(config['inventory']))
test_interface['name'] = 'xyz' test_interface['name'] = 'xyz'
new_check = interfaces._make_check( new_check = interfaces.InterfaceCheck(
config['sensu']['interface-check'], config['sensu']['interface-check'],
test_interface) test_interface).to_dict()
# create the new check # create the new check
check_name = new_check['metadata']['name'] check_name = new_check['metadata']['name']
...@@ -38,3 +39,84 @@ def test_check_lifecycle(config, mocked_sensu, mocked_inventory): ...@@ -38,3 +39,84 @@ def test_check_lifecycle(config, mocked_sensu, mocked_inventory):
# delete the check and confirm the correct call was made # delete the check and confirm the correct call was made
sensu.delete_check(config['sensu'], check_name) sensu.delete_check(config['sensu'], check_name)
assert check_name not in mocked_sensu 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())
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment