diff --git a/brian_polling_manager/configuration.py b/brian_polling_manager/configuration.py index 699633a04bf94e468488f8eeaea150b47a8a44e6..160b07dcbe8870351dc5a27b418ca4dc91a108d8 100644 --- a/brian_polling_manager/configuration.py +++ b/brian_polling_manager/configuration.py @@ -31,6 +31,10 @@ _DEFAULT_CONFIG = { 'config': '/var/lib/sensu/conf/get-interface-stats.config.json', 'command': '{script} --config {config} {args}', }, + 'snmp-interface-check': { + 'script': '/var/lib/sensu/bin/counter2influx-v6.sh', + 'measurement': 'counters', + }, 'gws-direct-interface-check': { 'script': '/var/lib/sensu/bin/poll-gws-direct.sh', 'measurement': 'gwsd_counters', @@ -81,6 +85,15 @@ CONFIG_SCHEMA = { 'required': ['script', 'config', 'command'], 'additionalProperties': False }, + 'snmp-interface-check': { + 'type': 'object', + 'properties': { + 'script': {'type': 'string'}, + 'measurement': {'type': 'string'}, + }, + 'required': ['script', 'measurement'], + 'additionalProperties': False + }, 'sensu': { 'type': 'object', 'properties': { @@ -92,6 +105,8 @@ CONFIG_SCHEMA = { 'api-key': {'type': 'string'}, 'interface-check': {'$ref': '#/definitions/router-check'}, + 'snmp-interface-check': + {'$ref': '#/definitions/snmp-interface-check'}, 'gws-direct-interface-check': {'$ref': '#/definitions/influx-check'}, 'dscp32-service-check': diff --git a/brian_polling_manager/interfaces.py b/brian_polling_manager/interfaces.py index bdcfbbec2b030115e286a3eebf19bdccf7db7876..4aaf2a7e940e7fde0cebb5619a32ac3ff869b199 100644 --- a/brian_polling_manager/interfaces.py +++ b/brian_polling_manager/interfaces.py @@ -9,8 +9,6 @@ logger = logging.getLogger(__name__) def _is_rtr_check(check): name = check["metadata"]["name"] - # check-* is the old-style name (add to the returned - # data so it can be deleted) return re.match(r"^(rtr)-[^-]+\.geant\.net$", name) @@ -26,8 +24,7 @@ def load_checks(sensu_params, filter_by): return {c["metadata"]["name"]: c for c in checks} -# TODO [POL1-797] rm after POL1-773 runs in production -def load_deprecated_ifc_checks(sensu_params): +def load_ifc_checks(sensu_params): return load_checks(sensu_params, _is_ifc_check) @@ -35,6 +32,45 @@ def load_router_checks(sensu_params): return load_checks(sensu_params, _is_rtr_check) +class SNMPInterfaceCheck(sensu.AbstractCheck): + COMMAND_STR = "{script} {measurement} {community} {hostname} {interface} {if_index}" + + 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): + # fix POL1-386 - replace : in interface name with . + # https://docs.sensu.io/sensu-go/latest/observability-pipeline/ + # observe-schedule/checks/#metadata-attributes + ifc_name = self.interface["name"].replace("/", "-").replace(":", ".") + return f'ifc-{self.interface["router"]}-{ifc_name}' + + @sensu.AbstractCheck.command.getter + def command(self): + return self.COMMAND_STR.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"], + if_index=self.interface["snmp-index"], + ) + + @sensu.AbstractCheck.proxy_entity_name.getter + def proxy_entity_name(self): + return self.interface["router"] + + @staticmethod + def requires_snmp_check(interface): + # only juniper ae subinterfaces for now + return re.match(r"^ae\d+\.\d+", interface["name"]) + + class NetconfRouterCheck(sensu.AbstractCheck): METRIC_FORMAT = "" METRIC_HANDLERS = None @@ -68,7 +104,7 @@ class NetconfRouterCheck(sensu.AbstractCheck): return self.router -def refresh(sensu_params, inventory_interfaces): +def netconf_router_refresh(sensu_params, inventory_interfaces): routers_w_vendor = { (ifc["router"], ifc.get("vendor")) for ifc in inventory_interfaces } @@ -78,12 +114,16 @@ def refresh(sensu_params, inventory_interfaces): for router, vendor in routers_w_vendor ] - r1 = sensu.refresh(sensu_params, [], load_deprecated_ifc_checks(sensu_params)) - r2 = sensu.refresh(sensu_params, required_checks, load_router_checks(sensu_params)) - return { - "checks": r1["checks"] + r2["checks"], - "input": r1["input"] + r2["input"], - "created": r1["created"] + r2["created"], - "updated": r1["updated"] + r2["updated"], - "deleted": r1["deleted"] + r2["deleted"], - } + return sensu.refresh( + sensu_params, required_checks, load_router_checks(sensu_params) + ) + + +def snmp_interfaces_refresh(sensu_params, inventory_interfaces): + required_checks = [ + SNMPInterfaceCheck(sensu_params["snmp-interface-check"], ifc) + for ifc in inventory_interfaces + if SNMPInterfaceCheck.requires_snmp_check(ifc) + ] + + return sensu.refresh(sensu_params, required_checks, load_ifc_checks(sensu_params)) diff --git a/brian_polling_manager/main.py b/brian_polling_manager/main.py index 1228b238a0b512b172b8cc631e8e646ca81e4797..6e7cd985f8778012c53a8db4b3875e32e5e46204 100644 --- a/brian_polling_manager/main.py +++ b/brian_polling_manager/main.py @@ -52,12 +52,13 @@ REFRESH_RESULT_SCHEMA = { }, 'type': 'object', 'properties': { - 'interfaces': {'$ref': '#/definitions/refresh-result'}, + 'netconf': {'$ref': '#/definitions/refresh-result'}, + 'snmp': {'$ref': '#/definitions/refresh-result'}, 'gws_direct': {'$ref': '#/definitions/refresh-result'}, 'gws_indirect': {'$ref': '#/definitions/refresh-result'}, 'eumetsat_multicast': {'$ref': '#/definitions/refresh-result'}, }, - 'required': ['interfaces'], + 'required': ['netconf'], 'additionalProperties': False } @@ -90,7 +91,8 @@ def refresh(config, force=False): = inventory.load_eumetsat_multicast_subscriptions( config['inventory']) result = { - 'interfaces': interfaces.refresh(config['sensu'], state.interfaces), + 'netconf': interfaces.netconf_router_refresh(config['sensu'], state.interfaces), + 'snmp': interfaces.snmp_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), diff --git a/test/conftest.py b/test/conftest.py index e2bd49b1f5ce643b6aefb26d57925b9947fb3360..e8850df8b5ba6f2a828cd8936366de43bf20f717 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -44,6 +44,10 @@ def config(): "config": "/var/lib/sensu/conf/get-interface-stats.config.json", "command": "{script} --config {config} {args}", }, + "snmp-interface-check": { + "script": "/var/lib/sensu/bin/counter2influx-v6.sh", + "measurement": "counters", + }, "gws-direct-interface-check": { "script": "/var/lib/sensu/bin/poll-gws-direct.sh", "measurement": "gwsd_counters", diff --git a/test/data/interfaces.json b/test/data/interfaces.json index 04a1eeda7b4c115e6950e92ecac6d25f420c0a9e..86d8a747847f6e020b4f170b98aba6995b62ba24 100644 --- a/test/data/interfaces.json +++ b/test/data/interfaces.json @@ -55,5 +55,21 @@ "description": "blah blah nokia router", "circuits": [], "snmp-index": 9999 + }, + { + "router": "mx1.fra.de.geant.net", + "name": "ae10.1", + "bundle": [], + "bundle-parents": [], + "description": "blah blah", + "circuits": [ + { + "id": 50028, + "name": "something", + "type": "SERVICE", + "status": "operational" + } + ], + "snmp-index": 9999 } ] diff --git a/test/test_sensu_checks.py b/test/test_sensu_checks.py index fd03063c802896b28b1c9310afffc16541619536..f331c4bbc74468615d958411640b48ab5ccf69d0 100644 --- a/test/test_sensu_checks.py +++ b/test/test_sensu_checks.py @@ -53,6 +53,30 @@ def test_router_check(config): assert check["output_metric_handlers"] == [] +def test_snmp_interface_check(config): + check = interfaces.SNMPInterfaceCheck( + config["sensu"]["snmp-interface-check"], + {"router": "bogus.router", "name": "bogus/1/1", "snmp-index": 123}, + ).to_dict() + assert check["command"] == ( + "/var/lib/sensu/bin/counter2influx-v6.sh counters 0pBiFbD bogus.router " + "bogus/1/1 123" + ) + assert check["proxy_entity_name"] == "bogus.router" + assert check["metadata"]["name"] == "ifc-bogus.router-bogus-1-1" + assert check["output_metric_format"] == "influxdb_line" + assert check["output_metric_handlers"] == ["influx-db-handler"] + + +@responses.activate +def test_snmp_interface_refresh(config, mocked_sensu, mocked_inventory): + routers = inventory.load_interfaces("http://inventory1") + result = interfaces.snmp_interfaces_refresh( + config["sensu"], inventory_interfaces=routers + ) + assert result == {"checks": 3, "input": 1, "created": 1, "updated": 0, "deleted": 3} + + def test_nokia_router_check(config): check = interfaces.NetconfRouterCheck( config["sensu"]["interface-check"], "xyz", vendor="nokia" @@ -68,22 +92,14 @@ def test_nokia_router_check(config): assert check["output_metric_handlers"] == [] -@responses.activate -def test_cleans_up_old_interface_checks(config, mocked_sensu, mocked_inventory): - routers = inventory.load_interfaces("http://inventory1") - result = interfaces.refresh(config["sensu"], inventory_interfaces=routers) - assert result == {"checks": 3, "input": 3, "created": 3, "updated": 0, "deleted": 3} - sensu.clear_cached_values() - result = interfaces.refresh(config["sensu"], inventory_interfaces=routers) - assert result == {"checks": 3, "input": 3, "created": 0, "updated": 0, "deleted": 0} - - @responses.activate def test_runs_idempotent(config, mocked_sensu, mocked_inventory): routers = inventory.load_interfaces("http://inventory1") - interfaces.refresh(config["sensu"], inventory_interfaces=routers) + interfaces.netconf_router_refresh(config["sensu"], inventory_interfaces=routers) sensu.clear_cached_values() - result = interfaces.refresh(config["sensu"], inventory_interfaces=routers) + result = interfaces.netconf_router_refresh( + config["sensu"], inventory_interfaces=routers + ) assert result == {"checks": 3, "input": 3, "created": 0, "updated": 0, "deleted": 0}