From c3dd93577a0ec0d34ddd956f096df825b464c5fe Mon Sep 17 00:00:00 2001
From: Pelle Koster <pelle.koster@geant.org>
Date: Thu, 25 Apr 2024 14:17:45 +0200
Subject: [PATCH] Feature/POL1-813 Provision error report as a daily sensu
 check

---
 brian_polling_manager/configuration.py      | 16 +++++++
 brian_polling_manager/error_report/check.py | 46 +++++++++++++++++++++
 brian_polling_manager/main.py               |  4 +-
 brian_polling_manager/sensu.py              | 38 +++++++----------
 test/conftest.py                            |  4 ++
 test/data/checks.json                       | 26 ++++++++++++
 test/error_report/test_check.py             | 40 ++++++++++++++++++
 test/test_api.py                            |  6 ++-
 8 files changed, 154 insertions(+), 26 deletions(-)
 create mode 100644 brian_polling_manager/error_report/check.py
 create mode 100644 test/error_report/test_check.py

diff --git a/brian_polling_manager/configuration.py b/brian_polling_manager/configuration.py
index 699633a..6175539 100644
--- a/brian_polling_manager/configuration.py
+++ b/brian_polling_manager/configuration.py
@@ -47,6 +47,10 @@ _DEFAULT_CONFIG = {
           'measurement': 'multicast',
           'command': '{script} --inventory http://localhost:18080'
                      ' --measurement {measurement} --hostname {hostname}'
+        },
+        'interface-error-report': {
+            'script': '/home/brian_checks/venv/report-interface-errors',
+            'config': '/home/brian_checks/conf/report-interface-errors.config.json'
         }
     },
     'statedir': '/tmp/',
@@ -81,6 +85,15 @@ CONFIG_SCHEMA = {
             'required': ['script', 'config', 'command'],
             'additionalProperties': False
         },
+        'interface-error-report': {
+            'type': 'object',
+            'properties': {
+                'script': {'type': 'string'},
+                'config': {'type': 'string'},
+            },
+            'required': ['script', 'config'],
+            'additionalProperties': False
+        },
         'sensu': {
             'type': 'object',
             'properties': {
@@ -98,6 +111,9 @@ CONFIG_SCHEMA = {
                     {'$ref': '#/definitions/influx-check'},
                 'eumetsat-multicast-check':
                     {'$ref': '#/definitions/influx-check'},
+                'interface-error-report': {
+                    '$ref': '#/definitions/interface-error-report'
+                    }
             },
             'required': [
                 'api-base', 'api-key',
diff --git a/brian_polling_manager/error_report/check.py b/brian_polling_manager/error_report/check.py
new file mode 100644
index 0000000..7266981
--- /dev/null
+++ b/brian_polling_manager/error_report/check.py
@@ -0,0 +1,46 @@
+from brian_polling_manager import sensu
+
+ERROR_REPORT_CHECK_NAME = "interface-error-report"
+
+
+def load_checks(sensu_params, filter_by):
+    checks = filter(filter_by, sensu.load_all_checks(sensu_params))
+    return {c["metadata"]["name"]: c for c in checks}
+
+
+def load_error_report_checks(sensu_params):
+    return load_checks(
+        sensu_params, lambda c: c["metadata"]["name"] == ERROR_REPORT_CHECK_NAME
+    )
+
+
+class ErrorReportCheck(sensu.AbstractCheck):
+    INTERVAL_S = 3600 * 24  # daily
+    METRIC_FORMAT = ""
+    METRIC_HANDLERS = None
+
+    COMMAND = "{script} --config {config}"
+
+    def __init__(self, ifc_check_params):
+        super().__init__()
+        self.ifc_check_params = ifc_check_params
+
+    @sensu.AbstractCheck.name.getter
+    def name(self):
+        return ERROR_REPORT_CHECK_NAME
+
+    @sensu.AbstractCheck.command.getter
+    def command(self):
+        return self.COMMAND.format(**self.ifc_check_params)
+
+    @sensu.AbstractCheck.proxy_entity_name.getter
+    def proxy_entity_name(self):
+        return ERROR_REPORT_CHECK_NAME
+
+
+def refresh(sensu_params):
+    return sensu.refresh(
+        sensu_params,
+        [ErrorReportCheck(sensu_params["interface-error-report"])],
+        load_error_report_checks(sensu_params),
+    )
diff --git a/brian_polling_manager/main.py b/brian_polling_manager/main.py
index 1228b23..2e4f657 100644
--- a/brian_polling_manager/main.py
+++ b/brian_polling_manager/main.py
@@ -29,7 +29,7 @@ import click
 import jsonschema
 from statsd import StatsClient
 
-from brian_polling_manager import inventory, configuration, \
+from brian_polling_manager import error_report, inventory, configuration, \
     interfaces, gws_direct, gws_indirect, eumetsat_multicast, sensu
 
 logger = logging.getLogger(__name__)
@@ -56,6 +56,7 @@ REFRESH_RESULT_SCHEMA = {
         'gws_direct': {'$ref': '#/definitions/refresh-result'},
         'gws_indirect': {'$ref': '#/definitions/refresh-result'},
         'eumetsat_multicast': {'$ref': '#/definitions/refresh-result'},
+        'interface_error_report': {'$ref': '#/definitions/refresh-result'},
     },
     'required': ['interfaces'],
     'additionalProperties': False
@@ -96,6 +97,7 @@ def refresh(config, force=False):
             config['sensu'], state.gws_indirect),
         'eumetsat_multicast': eumetsat_multicast.refresh(
             config['sensu'], state.eumetsat_multicast),
+        'interface_error_report': error_report.check.refresh(config['sensu'])
 
     }
     jsonschema.validate(result, REFRESH_RESULT_SCHEMA)  # sanity
diff --git a/brian_polling_manager/sensu.py b/brian_polling_manager/sensu.py
index 87592d2..5e0d8e7 100644
--- a/brian_polling_manager/sensu.py
+++ b/brian_polling_manager/sensu.py
@@ -212,29 +212,21 @@ class AbstractCheck(object):
 
     @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"] or []) != sorted(b["subscriptions"] or []):
-            return False
-        if sorted(a["output_metric_handlers"] or []) != sorted(
-            b["output_metric_handlers"] or []
-        ):
-            return False
-        if a["metadata"]["name"] != b["metadata"]["name"]:
-            return False
-        if a["metadata"]["namespace"] != b["metadata"]["namespace"]:
-            return False
-        return True
+        return (
+            a["publish"] == b["publish"]
+            and a["command"] == b["command"]
+            and a["interval"] == b["interval"]
+            and a["proxy_entity_name"] == b["proxy_entity_name"]
+            and a["round_robin"] == b["round_robin"]
+            and a["output_metric_format"] == b["output_metric_format"]
+            and set(a["subscriptions"] or []) == set(b["subscriptions"] or [])
+            and set(a["output_metric_handlers"] or [])
+            == set(b["output_metric_handlers"] or [])
+            and a["metadata"]["name"] == b["metadata"]["name"]
+            and a["metadata"]["namespace"] == b["metadata"]["namespace"]
+            and a["interval"] == b["interval"]
+            and a["proxy_entity_name"] == b["proxy_entity_name"]
+        )
 
 
 def refresh(sensu_params, required_checks, current_checks):
diff --git a/test/conftest.py b/test/conftest.py
index e2bd49b..310aa21 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -63,6 +63,10 @@ def config():
                     " --measurement {measurement}"
                     " --hostname {hostname}",
                 },
+                "interface-error-report": {
+                    "script": "/path/to/report-interface-errors",
+                    "config": "/path/to/config",
+                },
             },
             "statedir": state_dir_name,
             "statsd": {"hostname": "localhost", "port": 11119, "prefix": "zzzzz"},
diff --git a/test/data/checks.json b/test/data/checks.json
index 5e0232b..71d3a8a 100644
--- a/test/data/checks.json
+++ b/test/data/checks.json
@@ -179,6 +179,32 @@
       "created_by": "admin"
     },
     "secrets": null
+  },
+  {
+    "command": "/path/to/report-interface-errors --config /path/to/config",
+    "handlers": [],
+    "high_flap_threshold": 0,
+    "interval": 300,
+    "low_flap_threshold": 0,
+    "publish": false,
+    "runtime_assets": null,
+    "subscriptions": [],
+    "proxy_entity_name": "error-report",
+    "check_hooks": null,
+    "stdin": false,
+    "subdue": null,
+    "ttl": 0,
+    "timeout": 0,
+    "round_robin": true,
+    "output_metric_format": "",
+    "output_metric_handlers": [],
+    "env_vars": null,
+    "metadata": {
+      "name": "interface-error-report",
+      "namespace": "default",
+      "created_by": "admin"
+    },
+    "secrets": null
   }
 ]
 
diff --git a/test/error_report/test_check.py b/test/error_report/test_check.py
new file mode 100644
index 0000000..2e11f34
--- /dev/null
+++ b/test/error_report/test_check.py
@@ -0,0 +1,40 @@
+from brian_polling_manager import sensu
+from brian_polling_manager.error_report.check import ErrorReportCheck, refresh
+import pytest
+import responses
+
+
+@pytest.fixture
+def config():
+    return {
+        "api-base": [
+            "https://bogus-sensu01.xxx.yyy:12345",
+            "https://bogus-sensu02.xxx.yyy:12345",
+            "https://bogus-sensu03.xxx.yyy:12345",
+        ],
+        "api-key": "abc-sensu-key-blah-blah",
+        "interface-error-report": {
+            "script": "/path/to/report-interface-errors",
+            "config": "/path/to/config",
+        },
+    }
+
+
+def test_error_report_check(config):
+    check = ErrorReportCheck(config["interface-error-report"]).to_dict()
+    assert check["command"] == (
+        "/path/to/report-interface-errors --config /path/to/config"
+    )
+    assert check["proxy_entity_name"] == "interface-error-report"
+    assert check["metadata"]["name"] == "interface-error-report"
+    assert check["output_metric_format"] == ""
+    assert check["output_metric_handlers"] == []
+
+
+@responses.activate
+def test_error_report_refresh(config, mocked_sensu, mocked_inventory):
+    result = refresh(config)
+    assert result == {"checks": 1, "input": 1, "created": 0, "updated": 1, "deleted": 0}
+    sensu.clear_cached_values()
+    result = refresh(config)
+    assert result == {"checks": 1, "input": 1, "created": 0, "updated": 0, "deleted": 0}
diff --git a/test/test_api.py b/test/test_api.py
index 0a647fe..2b75390 100644
--- a/test/test_api.py
+++ b/test/test_api.py
@@ -12,8 +12,10 @@ from brian_polling_manager.main import REFRESH_RESULT_SCHEMA
 
 @pytest.fixture
 def client(config_filename, mocked_sensu, mocked_inventory):
-    os.environ['CONFIG_FILENAME'] = config_filename
-    with brian_polling_manager.create_app().test_client() as c:
+    os.environ["CONFIG_FILENAME"] = config_filename
+    app = brian_polling_manager.create_app()
+    app.testing = True
+    with app.test_client() as c:
         yield c
 
 
-- 
GitLab