From 76b1338ae6bd567e60b24745070951d171e0fdcb Mon Sep 17 00:00:00 2001 From: Pierre Verkest Date: Mon, 4 Sep 2023 00:06:17 +0200 Subject: [PATCH] Create opnsense_server_ha_state metric in order to replace and that will be removed in version 1.0.0. Also use Enum to store possible OPNsense HA states to avoid typo --- README.md | 15 ++++++++---- opnsense_exporter/opnsense_api.py | 19 +++++++++++----- opnsense_exporter/server.py | 38 ++++++++++++++++++++++++------- tests/test_opnsense_api.py | 30 ++++++++++++------------ 4 files changed, 68 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 45b6550..6e93df2 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,13 @@ This exporter gives following metrics, all metrics received following labels: ### Enums -- `opnsense_main_ha_state`: OPNSense HA state of the MAIN server -- `opnsense_backup_ha_state`: OPNSense HA state of the BACKUP server +- `opnsense_main_ha_state`: (deprecated) OPNSense HA state of the MAIN server +- `opnsense_backup_ha_state`: (deprecated) OPNSense HA state of the BACKUP server +- `opnsense_server_ha_state`: OPNSense HA state, on of following value: + - **active**: that OPNSense server is receiving traffic + - **hot_standby**: the OPNSense server is ready to be promote as active server + - **maintenancemode**: the OPNSense server was turned into maintenance mode + - **unavailable**: the OPNSense server wasn't accessible or return unexpected value ### Gauges @@ -93,10 +98,8 @@ You can setup env through `.env` file or environment variables with defined as d ## Roadmap -- allow to configure interfaces to get traffic rates for lan,wan and/or other names -- refactor server in a class to avoid transmitted params over methods - allow to change the listening port (today it force using `8000`) -- allow to configure timeouts using environemnt variables +- allow to configure timeouts using environment variables - improves logging to get a debug mode to understand errors based on unexpected payloads ## Changelog @@ -108,6 +111,8 @@ You can setup env through `.env` file or environment variables with defined as d - replace `active_server_bytes_received` and `active_server_bytes_transmitted` by `opnsense_active_server_traffic_rate` +- add `opnsense_server_ha_state` and mark `opnsense_main_ha_state` + and `opnsense_backup_ha_state` as deprecated. ### Version 0.4.0 (2023-09-02) diff --git a/opnsense_exporter/opnsense_api.py b/opnsense_exporter/opnsense_api.py index f4e6aae..85d8644 100644 --- a/opnsense_exporter/opnsense_api.py +++ b/opnsense_exporter/opnsense_api.py @@ -7,6 +7,13 @@ from requests import RequestException logger = logging.getLogger(__name__) +class OPNSenseHAState(Enum): + ACTIVE = "active" + HOT_STANDBY = "hot_standby" + UNAVAILABLE = "unavailable" + MAINTENANCE_MODE = "maintenancemode" + + class OPNSenseTrafficMetric(Enum): IN = "rate_bits_in" OUT = "rate_bits_out" @@ -77,26 +84,26 @@ class OPNSenseAPI: response.raise_for_status() return response.json() - def get_interface_vip_status(self): + def get_interface_vip_status(self) -> OPNSenseHAState: try: data = self.get("/api/diagnostics/interface/get_vip_status/") except RequestException as ex: logger.error( "Get VIP STATUS on %s failed with the following error %r", self.host, ex ) - return "unavailable" + return OPNSenseHAState.UNAVAILABLE if data["carp"]["maintenancemode"]: - return "maintenancemode" + return OPNSenseHAState.MAINTENANCE_MODE is_active = all([row["status"] == "MASTER" for row in data["rows"]]) if is_active: - return "active" + return OPNSenseHAState.ACTIVE is_backup = all([row["status"] == "BACKUP" for row in data["rows"]]) if is_backup: - return "hot_standby" + return OPNSenseHAState.HOT_STANDBY logger.warning( "this host %s is no active nor backup received payload %s", self.host, data ) - return "unavailable" + return OPNSenseHAState.UNAVAILABLE def get_traffic(self, interfaces): try: diff --git a/opnsense_exporter/server.py b/opnsense_exporter/server.py index fee85f3..08d40cc 100644 --- a/opnsense_exporter/server.py +++ b/opnsense_exporter/server.py @@ -1,4 +1,5 @@ import argparse +import logging import os import socket import time @@ -6,12 +7,21 @@ import time from dotenv import load_dotenv from prometheus_client import Enum, Gauge, start_http_server -from opnsense_exporter.opnsense_api import OPNSenseAPI, OPNSenseRole +from opnsense_exporter.opnsense_api import OPNSenseAPI, OPNSenseHAState, OPNSenseRole +logger = logging.getLogger(__name__) load_dotenv() -HA_STATES = ["active", "hot_standby", "unavailable", "maintenancemode"] -main_ha_state = Enum( +HA_STATES = [enum.value for enum in list(OPNSenseHAState)] + + +class DeprecatedPromEnum(Enum, DeprecationWarning): + def state(self, *args, **kwargs): + super().state(*args, **kwargs) + logger.warning("This metric %s will be removed in v1.0.0", self._name) + + +main_ha_state = DeprecatedPromEnum( "opnsense_main_ha_state", "OPNSense HA state of the MAIN server", [ @@ -21,7 +31,7 @@ main_ha_state = Enum( ], states=HA_STATES, ) -backup_ha_state = Enum( +backup_ha_state = DeprecatedPromEnum( "opnsense_backup_ha_state", "OPNSense HA state of the BACKUP server", [ @@ -31,6 +41,18 @@ backup_ha_state = Enum( ], states=HA_STATES, ) + +opnsense_server_ha_state = Enum( + "opnsense_server_ha_state", + "OPNSense server HA state", + [ + "instance", + "host", + "role", + ], + states=HA_STATES, +) + opnsense_active_server_traffic_rate = Gauge( "opnsense_active_server_traffic_rate", "Active OPNSense server bytes in/out per interface", @@ -64,15 +86,15 @@ class OPNSensePrometheusExporter: main_state = self.main.get_interface_vip_status() backup_sate = self.backup.get_interface_vip_status() main_ha_state.labels(instance=self.exporter_instance, **self.main.labels).state( - main_state + main_state.value ) backup_ha_state.labels( instance=self.exporter_instance, **self.backup.labels - ).state(backup_sate) + ).state(backup_sate.value) active_opnsense = None - if main_state == "active": + if main_state == OPNSenseHAState.ACTIVE: active_opnsense = self.main - if backup_sate == "active": + if backup_sate == OPNSenseHAState.ACTIVE: active_opnsense = self.backup if active_opnsense: for traffic in active_opnsense.get_traffic(self.interfaces): diff --git a/tests/test_opnsense_api.py b/tests/test_opnsense_api.py index 6ada13d..6ef2f45 100644 --- a/tests/test_opnsense_api.py +++ b/tests/test_opnsense_api.py @@ -26,9 +26,9 @@ def test_get_interface_vip_status_active(): ) assert ( - OPNSenseAPI( - OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD - ).get_interface_vip_status() + OPNSenseAPI(OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD) + .get_interface_vip_status() + .value == "active" ) @@ -42,9 +42,9 @@ def test_get_interface_vip_status_backup(): ) assert ( - OPNSenseAPI( - OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD - ).get_interface_vip_status() + OPNSenseAPI(OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD) + .get_interface_vip_status() + .value == "hot_standby" ) @@ -58,9 +58,9 @@ def test_get_interface_vip_status_mainteance_mode(): ) assert ( - OPNSenseAPI( - OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD - ).get_interface_vip_status() + OPNSenseAPI(OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD) + .get_interface_vip_status() + .value == "maintenancemode" ) @@ -73,9 +73,9 @@ def test_get_interface_vip_status_unavailable_weird_case(): body=generate_get_vip_status_paylaod("MASTER", "BACKUP", False), ) assert ( - OPNSenseAPI( - OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD - ).get_interface_vip_status() + OPNSenseAPI(OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD) + .get_interface_vip_status() + .value == "unavailable" ) @@ -89,9 +89,9 @@ def test_get_interface_vip_status_unavailable_rest_api_error(): status=404, ) assert ( - OPNSenseAPI( - OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD - ).get_interface_vip_status() + OPNSenseAPI(OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD) + .get_interface_vip_status() + .value == "unavailable" )