diff --git a/README.md b/README.md index a2320e0..45b6550 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ This exporter gives following metrics, all metrics received following labels: - `instance`: by default this is set with the hostname where is running this exporter service - `host`: the host of the OPNSense -- `role`: `main` or `backup` +- `role`: `main` or `backup` to determine the OPNSense server role. ### Enums @@ -35,8 +35,10 @@ This exporter gives following metrics, all metrics received following labels: ### Gauges -- `opnsense_active_server_bytes_received`: Active OPNSense server bytes received on WAN interface -- `opnsense_active_server_bytes_transmitted`: Active OPNSense server bytes transmitted on WAN interface +- `opnsense_active_server_traffic_rate`: Active OPNSense server traffic rate per interfaces bits/s + add following labels: + - **interface**: the interface to export (values given using `--opnsense-interfaces`) + - **metric**: the metric name (as today one of `rate_bits_in`, `rate_bits_in`) ## Usage @@ -47,6 +49,7 @@ opnsense-exporter --help usage: opnsense-exporter [-h] [--check-frequency-seconds FREQUENCY] [--main-host MAIN] [--backup-host BACKUP] [--opnsense-user USER] + [--opnsense-interfaces INTERFACES] [--opnsense-password PASSWORD] [--prometheus-instance PROM_INSTANCE] @@ -61,17 +64,21 @@ optional arguments: MAIN OPNsense server that should be in `active` state in normal configuration. --backup-host BACKUP, -b BACKUP - BACKUP OPNsense server that should be - `hot_standby` state in normal configuration. + BACKUP OPNsense server that should be `hot_standby` + state in normal configuration. --opnsense-user USER, -u USER OPNsense user. Expect to be the same on MAIN and BACKUP servers + --opnsense-interfaces INTERFACES, -i INTERFACES + OPNsense interfaces (coma separated) list to export + trafic rates (bytes/s) (default: wan,lan) --opnsense-password PASSWORD, -p PASSWORD OPNsense password. Expect to be the same on MAIN and BACKUP servers --prometheus-instance PROM_INSTANCE - Exporter Instance name, default value computed - with hostname where the server is running. Use to + Exporter Instance name, default value computed with + hostname where the server is running. Use to set + the instance label. (default: my-opnsense-prom-exporter-server) ``` You can setup env through `.env` file or environment variables with defined as default values @@ -86,8 +93,6 @@ You can setup env through `.env` file or environment variables with defined as d ## Roadmap -- merge `opnsense_active_server_bytes_received` and `opnsense_active_server_bytes_transmitted` - metrics adding labels to distinguish rates transmitted and rate received - 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`) @@ -96,22 +101,26 @@ You can setup env through `.env` file or environment variables with defined as d ## Changelog -### Version 0.0.5 (UNRELEASED) +### Version 0.5.0 (UNRELEASED) -* add role label in metrics +- add role label in metrics +- all to configure supervised interfaces using `--opnsense-interfaces` +- replace `active_server_bytes_received` and + `active_server_bytes_transmitted` by + `opnsense_active_server_traffic_rate` ### Version 0.4.0 (2023-09-02) -* Higher timeout while getting WAN traffic info +- Higher timeout while getting WAN traffic info ### Version 0.3.0 (2023-09-02) -* Use proper method to compute WAN traffic +- Use proper method to compute WAN traffic ### Version 0.2.0 (2023-09-01) -* Setup automatic release from gitlab while pushing new tag +- Setup automatic release from gitlab while pushing new tag ### Version 0.1.0 (2023-09-01) -* Initial version +- Initial version diff --git a/opnsense_exporter/opnsense_api.py b/opnsense_exporter/opnsense_api.py index 9af7d74..f4e6aae 100644 --- a/opnsense_exporter/opnsense_api.py +++ b/opnsense_exporter/opnsense_api.py @@ -7,6 +7,37 @@ from requests import RequestException logger = logging.getLogger(__name__) +class OPNSenseTrafficMetric(Enum): + IN = "rate_bits_in" + OUT = "rate_bits_out" + + +class OPNSenseTraffic: + interface: str = None + metric: OPNSenseTrafficMetric = None + value: int = 0 + + def __init__(self, interface: str, metric: OPNSenseTrafficMetric, value: int = 0): + self.value = value + self.interface = interface + self.metric = metric + + @property + def labels(self): + return {"metric": self.metric.value, "interface": self.interface} + + def __eq__(self, opn_traffic): + """Used by unittest to assert expected values""" + return ( + self.interface == opn_traffic.interface + and self.metric == opn_traffic.metric + and self.value == opn_traffic.value + ) + + def __repr__(self): + return f"{self.interface} - {self.metric} = {self.value}" + + class OPNSenseRole(Enum): MAIN = "main" BACKUP = "backup" @@ -67,20 +98,23 @@ class OPNSenseAPI: ) return "unavailable" - def get_wan_trafic(self): + def get_traffic(self, interfaces): try: - data = self.get("/api/diagnostics/traffic/top/wan", timeout=15) + data = self.get(f"/api/diagnostics/traffic/top/{interfaces}", timeout=15) except RequestException as ex: logger.error( - "Get diagnostics traffic on WAN interface for %s host failed with the following error %r", + "Get diagnostics traffic on %s interface(s) for %s host failed with the following error %r", + interfaces, self.host, ex, ) - return None, None - - received = 0 - transmitted = 0 - for record in data.get("wan", []).get("records", []): - received += record.get("rate_bits_in", 0) - transmitted += record.get("rate_bits_out", 0) - return received, transmitted + return [] + traffics = [] + for interface in interfaces.split(","): + traffic_in = OPNSenseTraffic(interface, OPNSenseTrafficMetric.IN) + traffic_out = OPNSenseTraffic(interface, OPNSenseTrafficMetric.OUT) + for record in data.get(interface, []).get("records", []): + traffic_in.value += record.get(OPNSenseTrafficMetric.IN.value, 0) + traffic_out.value += record.get(OPNSenseTrafficMetric.OUT.value, 0) + traffics.extend([traffic_in, traffic_out]) + return traffics diff --git a/opnsense_exporter/server.py b/opnsense_exporter/server.py index 5bb5122..fee85f3 100644 --- a/opnsense_exporter/server.py +++ b/opnsense_exporter/server.py @@ -31,22 +31,15 @@ backup_ha_state = Enum( ], states=HA_STATES, ) -active_server_bytes_received = Gauge( - "opnsense_active_server_bytes_received", - "Active OPNSense server bytes received on WAN interface", - [ - "instance", - "host", - "role", - ], -) -active_server_bytes_transmitted = Gauge( - "opnsense_active_server_bytes_transmitted", - "Active OPNSense server bytes transmitted on WAN interface", +opnsense_active_server_traffic_rate = Gauge( + "opnsense_active_server_traffic_rate", + "Active OPNSense server bytes in/out per interface", [ "instance", "host", "role", + "interface", + "metric", ], ) @@ -56,11 +49,13 @@ class OPNSensePrometheusExporter: self, main: OPNSenseAPI, backup: OPNSenseAPI, + interfaces, exporter_instance: str = "", check_frequency: int = 1, ): self.main = main self.backup = backup + self.interfaces = interfaces self.exporter_instance = exporter_instance self.check_frequency = check_frequency @@ -80,15 +75,13 @@ class OPNSensePrometheusExporter: if backup_sate == "active": active_opnsense = self.backup if active_opnsense: - bytes_received, bytes_transmitted = active_opnsense.get_wan_trafic() - if bytes_received or bytes_received == 0: - active_server_bytes_received.labels( - instance=self.exporter_instance, **active_opnsense.labels - ).set(bytes_received) - if bytes_transmitted or bytes_transmitted == 0: - active_server_bytes_transmitted.labels( - instance=self.exporter_instance, **active_opnsense.labels - ).set(bytes_transmitted) + for traffic in active_opnsense.get_traffic(self.interfaces): + if traffic.value: + opnsense_active_server_traffic_rate.labels( + instance=self.exporter_instance, + **active_opnsense.labels, + **traffic.labels + ).set(traffic.value) def start_server(self): # Start up the server to expose the metrics. @@ -136,6 +129,14 @@ def run(): default=os.environ.get("OPNSENSE_USERNAME", None), help="OPNsense user. Expect to be the same on MAIN and BACKUP servers", ) + parser.add_argument( + "--opnsense-interfaces", + "-i", + type=str, + dest="interfaces", + default=os.environ.get("OPNSENSE_INTERFACES", "wan,lan"), + help="OPNsense interfaces (coma separated) list to export trafic rates (bytes/s)", + ) parser.add_argument( "--opnsense-password", "-p", @@ -164,6 +165,7 @@ def run(): OPNSenseAPI( OPNSenseRole.BACKUP, arguments.backup, arguments.user, arguments.password ), + arguments.interfaces, check_frequency=arguments.frequency, exporter_instance=arguments.prom_instance, ) diff --git a/tests/common.py b/tests/common.py index 7efffef..89d25e9 100644 --- a/tests/common.py +++ b/tests/common.py @@ -47,126 +47,131 @@ def generate_get_vip_status_paylaod(state_wan, state_lan, maintenance_mode): def generate_diagnostics_traffic_interface_paylaod(): + # wan - rate_bits_in: 101026 + # wan - rate_bits_out: 86020 + # lan - rate_bits_in: 188490 + # lan - rate_bits_out: 952 + return json.dumps( { "wan": { "records": [ { "address": "0.1.2.3", - "rate_bits_in": 15300, - "rate_bits_out": 1720, - "rate_bits": 17020, - "cumulative_bytes_in": 3830, - "cumulative_bytes_out": 441, - "cumulative_bytes": 4271, + "rate_bits_in": 62300, + "rate_bits_out": 66100, + "rate_bits": 128400, + "cumulative_bytes_in": 15600, + "cumulative_bytes_out": 16500, + "cumulative_bytes": 32100, "tags": [], "details": [ { "address": "0.1.2.3", - "rate": "15.3Kb", - "rate_bits": 15300, - "cumulative": "3.83KB", - "cumulative_bytes": 3830, + "rate": "62.3Kb", + "rate_bits": 62300, + "cumulative": "15.6KB", + "cumulative_bytes": 15600, "tags": ["local"], } ], - "rname": "fake value", - "rate_in": "15.3 kb", - "rate_out": "1.72 kb", - "rate": "17.02 kb", - "cumulative_in": "3.83 kb", - "cumulative_out": "441.0 b", - "cumulative": "4.27 kb", + "rname": "fake rname value", + "rate_in": "62.3 kb", + "rate_out": "66.1 kb", + "rate": "128.4 kb", + "cumulative_in": "15.6 kb", + "cumulative_out": "16.5 kb", + "cumulative": "32.1 kb", }, { "address": "0.1.2.3", - "rate_bits_in": 4470, - "rate_bits_out": 7290, - "rate_bits": 11760, - "cumulative_bytes_in": 1120, - "cumulative_bytes_out": 1820, - "cumulative_bytes": 2940, + "rate_bits_in": 36200, + "rate_bits_out": 16100, + "rate_bits": 52300, + "cumulative_bytes_in": 9060, + "cumulative_bytes_out": 4020, + "cumulative_bytes": 13080, "tags": [], "details": [ { "address": "0.1.2.3", - "rate": "4.47Kb", - "rate_bits": 4470, - "cumulative": "1.12KB", - "cumulative_bytes": 1120, + "rate": "36.2Kb", + "rate_bits": 36200, + "cumulative": "9.06KB", + "cumulative_bytes": 9060, "tags": ["local"], } ], - "rname": "fake value", - "rate_in": "4.47 kb", - "rate_out": "7.29 kb", - "rate": "11.76 kb", - "cumulative_in": "1.12 kb", - "cumulative_out": "1.82 kb", - "cumulative": "2.94 kb", + "rname": "fake rname value", + "rate_in": "36.2 kb", + "rate_out": "16.1 kb", + "rate": "52.3 kb", + "cumulative_in": "9.06 kb", + "cumulative_out": "4.02 kb", + "cumulative": "13.08 kb", }, { "address": "0.1.2.3", - "rate_bits_in": 272, - "rate_bits_out": 272, - "rate_bits": 544, - "cumulative_bytes_in": 68, - "cumulative_bytes_out": 68, - "cumulative_bytes": 136, + "rate_bits_in": 1790, + "rate_bits_out": 1520, + "rate_bits": 3310, + "cumulative_bytes_in": 459, + "cumulative_bytes_out": 389, + "cumulative_bytes": 848, "tags": [], "details": [ { "address": "0.1.2.3", - "rate": "272b", - "rate_bits": 272, - "cumulative": "68B", - "cumulative_bytes": 68, + "rate": "1.79Kb", + "rate_bits": 1790, + "cumulative": "459B", + "cumulative_bytes": 459, "tags": ["local"], } ], - "rname": "fake value", - "rate_in": "272.0 b", - "rate_out": "272.0 b", - "rate": "544.0 b", - "cumulative_in": "68.0 b", - "cumulative_out": "68.0 b", - "cumulative": "136.0 b", + "rname": "fake rname value", + "rate_in": "1.79 kb", + "rate_out": "1.52 kb", + "rate": "3.31 kb", + "cumulative_in": "459.0 b", + "cumulative_out": "389.0 b", + "cumulative": "848.0 b", }, { "address": "0.1.2.3", - "rate_bits_in": 272, - "rate_bits_out": 272, - "rate_bits": 544, - "cumulative_bytes_in": 68, - "cumulative_bytes_out": 68, - "cumulative_bytes": 136, + "rate_bits_in": 512, + "rate_bits_out": 1580, + "rate_bits": 2092, + "cumulative_bytes_in": 128, + "cumulative_bytes_out": 405, + "cumulative_bytes": 533, "tags": [], "details": [ { "address": "0.1.2.3", - "rate": "272b", - "rate_bits": 272, - "cumulative": "68B", - "cumulative_bytes": 68, + "rate": "512b", + "rate_bits": 512, + "cumulative": "128B", + "cumulative_bytes": 128, "tags": ["local"], } ], - "rname": "fake value", - "rate_in": "272.0 b", - "rate_out": "272.0 b", - "rate": "544.0 b", - "cumulative_in": "68.0 b", - "cumulative_out": "68.0 b", - "cumulative": "136.0 b", + "rname": "fake rname value", + "rate_in": "512.0 b", + "rate_out": "1.58 kb", + "rate": "2.09 kb", + "cumulative_in": "128.0 b", + "cumulative_out": "405.0 b", + "cumulative": "533.0 b", }, { "address": "0.1.2.3", "rate_bits_in": 0, - "rate_bits_out": 480, - "rate_bits": 480, + "rate_bits_out": 448, + "rate_bits": 448, "cumulative_bytes_in": 0, - "cumulative_bytes_out": 120, - "cumulative_bytes": 120, + "cumulative_bytes_out": 112, + "cumulative_bytes": 112, "tags": [], "details": [ { @@ -176,7 +181,26 @@ def generate_diagnostics_traffic_interface_paylaod(): "cumulative": "0B", "cumulative_bytes": 0, "tags": ["local"], - }, + } + ], + "rname": "fake rname value", + "rate_in": "0.0 b", + "rate_out": "448.0 b", + "rate": "448.0 b", + "cumulative_in": "0.0 b", + "cumulative_out": "112.0 b", + "cumulative": "112.0 b", + }, + { + "address": "0.1.2.3", + "rate_bits_in": 0, + "rate_bits_out": 272, + "rate_bits": 272, + "cumulative_bytes_in": 0, + "cumulative_bytes_out": 68, + "cumulative_bytes": 68, + "tags": [], + "details": [ { "address": "0.1.2.3", "rate": "0b", @@ -184,15 +208,15 @@ def generate_diagnostics_traffic_interface_paylaod(): "cumulative": "0B", "cumulative_bytes": 0, "tags": ["local"], - }, + } ], - "rname": "fake value", + "rname": "fake rname value", "rate_in": "0.0 b", - "rate_out": "480.0 b", - "rate": "480.0 b", + "rate_out": "272.0 b", + "rate": "272.0 b", "cumulative_in": "0.0 b", - "cumulative_out": "120.0 b", - "cumulative": "120.0 b", + "cumulative_out": "68.0 b", + "cumulative": "68.0 b", }, { "address": "0.1.2.3", @@ -213,7 +237,7 @@ def generate_diagnostics_traffic_interface_paylaod(): "tags": ["local"], } ], - "rname": "fake value", + "rname": "fake rname value", "rate_in": "224.0 b", "rate_out": "0.0 b", "rate": "224.0 b", @@ -223,6 +247,224 @@ def generate_diagnostics_traffic_interface_paylaod(): }, ], "status": "ok", - } + }, + "lan": { + "records": [ + { + "address": "0.1.2.3", + "rate_bits_in": 65200, + "rate_bits_out": 0, + "rate_bits": 65200, + "cumulative_bytes_in": 16270, + "cumulative_bytes_out": 0, + "cumulative_bytes": 16270, + "tags": [], + "details": [ + { + "address": "0.1.2.3", + "rate": "45.3Kb", + "rate_bits": 45300, + "cumulative": "11.3KB", + "cumulative_bytes": 11300, + "tags": ["private"], + }, + { + "address": "0.1.2.3", + "rate": "19.9Kb", + "rate_bits": 19900, + "cumulative": "4.97KB", + "cumulative_bytes": 4970, + "tags": ["private"], + }, + ], + "rname": "fake rname value", + "rate_in": "65.2 kb", + "rate_out": "0.0 b", + "rate": "65.2 kb", + "cumulative_in": "16.27 kb", + "cumulative_out": "0.0 b", + "cumulative": "16.27 kb", + }, + { + "address": "0.1.2.3", + "rate_bits_in": 47900, + "rate_bits_out": 0, + "rate_bits": 47900, + "cumulative_bytes_in": 12000, + "cumulative_bytes_out": 0, + "cumulative_bytes": 12000, + "tags": ["private"], + "details": [ + { + "address": "0.1.2.3", + "rate": "47.9Kb", + "rate_bits": 47900, + "cumulative": "12.0KB", + "cumulative_bytes": 12000, + "tags": [], + } + ], + "rname": "fake rname value", + "rate_in": "47.9 kb", + "rate_out": "0.0 b", + "rate": "47.9 kb", + "cumulative_in": "12.0 kb", + "cumulative_out": "0.0 b", + "cumulative": "12.0 kb", + }, + { + "address": "0.1.2.3", + "rate_bits_in": 36200, + "rate_bits_out": 0, + "rate_bits": 36200, + "cumulative_bytes_in": 9060, + "cumulative_bytes_out": 0, + "cumulative_bytes": 9060, + "tags": [], + "details": [ + { + "address": "0.1.2.3", + "rate": "36.2Kb", + "rate_bits": 36200, + "cumulative": "9.06KB", + "cumulative_bytes": 9060, + "tags": ["private"], + } + ], + "rname": "fake rname value", + "rate_in": "36.2 kb", + "rate_out": "0.0 b", + "rate": "36.2 kb", + "cumulative_in": "9.06 kb", + "cumulative_out": "0.0 b", + "cumulative": "9.06 kb", + }, + { + "address": "0.1.2.3", + "rate_bits_in": 19200, + "rate_bits_out": 0, + "rate_bits": 19200, + "cumulative_bytes_in": 4814, + "cumulative_bytes_out": 0, + "cumulative_bytes": 4814, + "tags": ["private"], + "details": [ + { + "address": "0.1.2.3", + "rate": "16.1Kb", + "rate_bits": 16100, + "cumulative": "4.02KB", + "cumulative_bytes": 4020, + "tags": [], + }, + { + "address": "0.1.2.3", + "rate": "1.58Kb", + "rate_bits": 1580, + "cumulative": "405B", + "cumulative_bytes": 405, + "tags": [], + }, + { + "address": "0.1.2.3", + "rate": "1.52Kb", + "rate_bits": 1520, + "cumulative": "389B", + "cumulative_bytes": 389, + "tags": [], + }, + ], + "rname": "fake rname value", + "rate_in": "19.2 kb", + "rate_out": "0.0 b", + "rate": "19.2 kb", + "cumulative_in": "4.81 kb", + "cumulative_out": "0.0 b", + "cumulative": "4.81 kb", + }, + { + "address": "0.1.2.3", + "rate_bits_in": 18200, + "rate_bits_out": 0, + "rate_bits": 18200, + "cumulative_bytes_in": 4550, + "cumulative_bytes_out": 0, + "cumulative_bytes": 4550, + "tags": ["private"], + "details": [ + { + "address": "0.1.2.3", + "rate": "18.2Kb", + "rate_bits": 18200, + "cumulative": "4.55KB", + "cumulative_bytes": 4550, + "tags": [], + } + ], + "rname": "fake rname value", + "rate_in": "18.2 kb", + "rate_out": "0.0 b", + "rate": "18.2 kb", + "cumulative_in": "4.55 kb", + "cumulative_out": "0.0 b", + "cumulative": "4.55 kb", + }, + { + "address": "0.1.2.3", + "rate_bits_in": 1790, + "rate_bits_out": 0, + "rate_bits": 1790, + "cumulative_bytes_in": 459, + "cumulative_bytes_out": 0, + "cumulative_bytes": 459, + "tags": [], + "details": [ + { + "address": "0.1.2.3", + "rate": "1.79Kb", + "rate_bits": 1790, + "cumulative": "459B", + "cumulative_bytes": 459, + "tags": ["private"], + } + ], + "rname": "fake rname value", + "rate_in": "1.79 kb", + "rate_out": "0.0 b", + "rate": "1.79 kb", + "cumulative_in": "459.0 b", + "cumulative_out": "0.0 b", + "cumulative": "459.0 b", + }, + { + "address": "0.1.2.3", + "rate_bits_in": 0, + "rate_bits_out": 952, + "rate_bits": 952, + "cumulative_bytes_in": 0, + "cumulative_bytes_out": 238, + "cumulative_bytes": 238, + "tags": ["private"], + "details": [ + { + "address": "0.1.2.3", + "rate": "0b", + "rate_bits": 0, + "cumulative": "0B", + "cumulative_bytes": 0, + "tags": ["private"], + } + ], + "rname": "fake rname value", + "rate_in": "0.0 b", + "rate_out": "952.0 b", + "rate": "952.0 b", + "cumulative_in": "0.0 b", + "cumulative_out": "238.0 b", + "cumulative": "238.0 b", + }, + ], + "status": "ok", + }, } ) diff --git a/tests/test_opnsense_api.py b/tests/test_opnsense_api.py index 6ec0693..6ada13d 100644 --- a/tests/test_opnsense_api.py +++ b/tests/test_opnsense_api.py @@ -1,6 +1,11 @@ import responses -from opnsense_exporter.opnsense_api import OPNSenseAPI, OPNSenseRole +from opnsense_exporter.opnsense_api import ( + OPNSenseAPI, + OPNSenseRole, + OPNSenseTraffic, + OPNSenseTrafficMetric, +) from .common import ( BACKUP_HOST, @@ -92,33 +97,35 @@ def test_get_interface_vip_status_unavailable_rest_api_error(): @responses.activate -def test_get_wan_traffic(): +def test_get_traffic(): responses.add( responses.GET, - f"https://{MAIN_HOST}/api/diagnostics/traffic/top/wan", + f"https://{MAIN_HOST}/api/diagnostics/traffic/top/wan,lan", body=generate_diagnostics_traffic_interface_paylaod(), ) - assert OPNSenseAPI( - OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD - ).get_wan_trafic() == ( - 20538, - 10034, - ) + assert OPNSenseAPI(OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD).get_traffic( + "wan,lan" + ) == [ + OPNSenseTraffic("wan", OPNSenseTrafficMetric.IN, value=101026), + OPNSenseTraffic("wan", OPNSenseTrafficMetric.OUT, value=86020), + OPNSenseTraffic("lan", OPNSenseTrafficMetric.IN, value=188490), + OPNSenseTraffic("lan", OPNSenseTrafficMetric.OUT, value=952), + ] @responses.activate -def test_get_wan_traffic_none(): +def test_get_traffic_none(): responses.add( responses.GET, - f"https://{MAIN_HOST}/api/diagnostics/traffic/top/wan", + f"https://{MAIN_HOST}/api/diagnostics/traffic/top/test-not-found", json={"error": "not found"}, status=404, ) - assert OPNSenseAPI( - OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD - ).get_wan_trafic() == ( - None, - None, + assert ( + OPNSenseAPI(OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD).get_traffic( + "test-not-found" + ) + == [] ) diff --git a/tests/test_server.py b/tests/test_server.py index 3219055..f14c941 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -1,3 +1,4 @@ +from typing import List from unittest import mock import responses @@ -17,28 +18,54 @@ from .common import ( class FakePromMetric: _labels = {} + _labels_calls = None + + def __init__(self): + self._labels = {} + self._labels_calls = [] + + @property + def count_labels_calls(self) -> int: + return len(self._labels_calls) def labels(self, *args, **kwargs): self._labels = kwargs + self._labels_calls.append(kwargs) return self class FakePromEnum(FakePromMetric): - _state = None - count_state_calls = 0 + _state: str = None + _state_calls: List[str] = [] - def state(self, state): - self.count_state_calls += 1 + def __init__(self): + super().__init__() + self._state_calls = [] + + @property + def count_state_calls(self) -> int: + return len(self._state_calls) + + def state(self, state: str): self._state = state + self._state_calls.append(state) class FakePromGauge(FakePromMetric): - value = None - count_set_calls = 0 + _value: int = None + _set_calls: List[int] = [] - def set(self, value): - self.count_set_calls += 1 - self.value = value + def __init__(self): + super().__init__() + self._set_calls = [] + + @property + def count_set_calls(self) -> int: + return len(self._set_calls) + + def set(self, value: int): + self._value = value + self._set_calls.append(value) @mock.patch("opnsense_exporter.server.OPNSensePrometheusExporter.start_server") @@ -57,6 +84,8 @@ def test_parser(server_mock): "user-test", "-p", "pwd-test", + "-i", + "efg,hij", "--prometheus-instance", "server-hostname-instance", ], @@ -73,6 +102,7 @@ def test_parser(server_mock): assert server.backup.login == "user-test" assert server.backup.password == "pwd-test" assert server.check_frequency == 15 + assert server.interfaces == "efg,hij" @responses.activate @@ -95,28 +125,24 @@ def test_process_requests(): main_ha_state_mock = FakePromEnum() backup_ha_state_mock = FakePromEnum() - active_server_bytes_received_mock = FakePromGauge() - active_server_bytes_transmitted_mock = FakePromGauge() + opnsense_active_server_traffic_rate_mock = FakePromGauge() with mock.patch("opnsense_exporter.server.main_ha_state", new=main_ha_state_mock): with mock.patch( "opnsense_exporter.server.backup_ha_state", new=backup_ha_state_mock ): with mock.patch( - "opnsense_exporter.server.active_server_bytes_received", - new=active_server_bytes_received_mock, + "opnsense_exporter.server.opnsense_active_server_traffic_rate", + new=opnsense_active_server_traffic_rate_mock, ): - with mock.patch( - "opnsense_exporter.server.active_server_bytes_transmitted", - new=active_server_bytes_transmitted_mock, - ): - OPNSensePrometheusExporter( - OPNSenseAPI(OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD), - OPNSenseAPI(OPNSenseRole.BACKUP, BACKUP_HOST, LOGIN, PASSWORD), - ).process_requests() + OPNSensePrometheusExporter( + OPNSenseAPI(OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD), + OPNSenseAPI(OPNSenseRole.BACKUP, BACKUP_HOST, LOGIN, PASSWORD), + "wan", + ).process_requests() assert main_ha_state_mock._state == "active" - assert main_ha_state_mock.count_state_calls == 1 + assert main_ha_state_mock.count_state_calls == 1, main_ha_state_mock._state_calls assert main_ha_state_mock._labels == { "instance": "", "host": MAIN_HOST, @@ -131,21 +157,24 @@ def test_process_requests(): "role": "backup", } - assert active_server_bytes_received_mock.value == 20538 - assert active_server_bytes_received_mock.count_set_calls == 1 - assert active_server_bytes_received_mock._labels == { - "instance": "", - "host": MAIN_HOST, - "role": "main", - } - - assert active_server_bytes_transmitted_mock.value == 10034 - assert active_server_bytes_transmitted_mock.count_set_calls == 1 - assert active_server_bytes_transmitted_mock._labels == { - "instance": "", - "host": MAIN_HOST, - "role": "main", - } + assert opnsense_active_server_traffic_rate_mock.count_set_calls == 2 + assert opnsense_active_server_traffic_rate_mock._labels_calls == [ + { + "instance": "", + "host": MAIN_HOST, + "role": "main", + "interface": "wan", + "metric": "rate_bits_in", + }, + { + "instance": "", + "host": MAIN_HOST, + "role": "main", + "interface": "wan", + "metric": "rate_bits_out", + }, + ] + assert opnsense_active_server_traffic_rate_mock._set_calls == [101026, 86020] @responses.activate @@ -168,25 +197,21 @@ def test_process_requests_backup_active(): main_ha_state_mock = FakePromEnum() backup_ha_state_mock = FakePromEnum() - active_server_bytes_received_mock = FakePromGauge() - active_server_bytes_transmitted_mock = FakePromGauge() + opnsense_active_server_traffic_rate_mock = FakePromGauge() with mock.patch("opnsense_exporter.server.main_ha_state", new=main_ha_state_mock): with mock.patch( "opnsense_exporter.server.backup_ha_state", new=backup_ha_state_mock ): with mock.patch( - "opnsense_exporter.server.active_server_bytes_received", - new=active_server_bytes_received_mock, + "opnsense_exporter.server.opnsense_active_server_traffic_rate", + new=opnsense_active_server_traffic_rate_mock, ): - with mock.patch( - "opnsense_exporter.server.active_server_bytes_transmitted", - new=active_server_bytes_transmitted_mock, - ): - OPNSensePrometheusExporter( - OPNSenseAPI(OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD), - OPNSenseAPI(OPNSenseRole.BACKUP, BACKUP_HOST, LOGIN, PASSWORD), - ).process_requests() + OPNSensePrometheusExporter( + OPNSenseAPI(OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD), + OPNSenseAPI(OPNSenseRole.BACKUP, BACKUP_HOST, LOGIN, PASSWORD), + "wan", + ).process_requests() assert main_ha_state_mock._state == "maintenancemode" assert main_ha_state_mock.count_state_calls == 1 assert main_ha_state_mock._labels == { @@ -203,21 +228,24 @@ def test_process_requests_backup_active(): "role": "backup", } - assert active_server_bytes_received_mock.value == 20538 - assert active_server_bytes_received_mock.count_set_calls == 1 - assert active_server_bytes_received_mock._labels == { - "instance": "", - "host": BACKUP_HOST, - "role": "backup", - } - - assert active_server_bytes_transmitted_mock.value == 10034 - assert active_server_bytes_transmitted_mock.count_set_calls == 1 - assert active_server_bytes_transmitted_mock._labels == { - "instance": "", - "host": BACKUP_HOST, - "role": "backup", - } + assert opnsense_active_server_traffic_rate_mock.count_set_calls == 2 + opnsense_active_server_traffic_rate_mock._labels_calls == [ + { + "instance": "", + "host": BACKUP_HOST, + "role": "backup", + "interface": "wan", + "metric": "rate_bits_in", + }, + { + "instance": "", + "host": BACKUP_HOST, + "role": "backup", + "interface": "wan", + "metric": "rate_bits_out", + }, + ] + assert opnsense_active_server_traffic_rate_mock._set_calls == [101026, 86020] @responses.activate @@ -241,25 +269,21 @@ def test_process_no_active(): main_ha_state_mock = FakePromEnum() backup_ha_state_mock = FakePromEnum() - active_server_bytes_received_mock = FakePromGauge() - active_server_bytes_transmitted_mock = FakePromGauge() + opnsense_active_server_traffic_rate_mock = FakePromGauge() with mock.patch("opnsense_exporter.server.main_ha_state", new=main_ha_state_mock): with mock.patch( "opnsense_exporter.server.backup_ha_state", new=backup_ha_state_mock ): with mock.patch( - "opnsense_exporter.server.active_server_bytes_received", - new=active_server_bytes_received_mock, + "opnsense_exporter.server.opnsense_active_server_traffic_rate", + new=opnsense_active_server_traffic_rate_mock, ): - with mock.patch( - "opnsense_exporter.server.active_server_bytes_transmitted", - new=active_server_bytes_transmitted_mock, - ): - OPNSensePrometheusExporter( - OPNSenseAPI(OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD), - OPNSenseAPI(OPNSenseRole.BACKUP, BACKUP_HOST, LOGIN, PASSWORD), - ).process_requests() + OPNSensePrometheusExporter( + OPNSenseAPI(OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD), + OPNSenseAPI(OPNSenseRole.BACKUP, BACKUP_HOST, LOGIN, PASSWORD), + "wan", + ).process_requests() assert main_ha_state_mock._state == "maintenancemode" assert main_ha_state_mock.count_state_calls == 1 @@ -277,8 +301,7 @@ def test_process_no_active(): "role": "backup", } - assert active_server_bytes_received_mock.count_set_calls == 0 - assert active_server_bytes_transmitted_mock.count_set_calls == 0 + assert opnsense_active_server_traffic_rate_mock.count_set_calls == 0 @responses.activate @@ -302,25 +325,21 @@ def test_process_with_falsy_value(): main_ha_state_mock = FakePromEnum() backup_ha_state_mock = FakePromEnum() - active_server_bytes_received_mock = FakePromGauge() - active_server_bytes_transmitted_mock = FakePromGauge() + opnsense_active_server_traffic_rate_mock = FakePromGauge() with mock.patch("opnsense_exporter.server.main_ha_state", new=main_ha_state_mock): with mock.patch( "opnsense_exporter.server.backup_ha_state", new=backup_ha_state_mock ): with mock.patch( - "opnsense_exporter.server.active_server_bytes_received", - new=active_server_bytes_received_mock, + "opnsense_exporter.server.opnsense_active_server_traffic_rate", + new=opnsense_active_server_traffic_rate_mock, ): - with mock.patch( - "opnsense_exporter.server.active_server_bytes_transmitted", - new=active_server_bytes_transmitted_mock, - ): - OPNSensePrometheusExporter( - OPNSenseAPI(OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD), - OPNSenseAPI(OPNSenseRole.BACKUP, BACKUP_HOST, LOGIN, PASSWORD), - ).process_requests() + OPNSensePrometheusExporter( + OPNSenseAPI(OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD), + OPNSenseAPI(OPNSenseRole.BACKUP, BACKUP_HOST, LOGIN, PASSWORD), + "wan", + ).process_requests() assert main_ha_state_mock._state == "active" assert main_ha_state_mock.count_state_calls == 1 assert main_ha_state_mock._labels == { @@ -337,5 +356,4 @@ def test_process_with_falsy_value(): "role": "backup", } - assert active_server_bytes_received_mock.count_set_calls == 0 - assert active_server_bytes_transmitted_mock.count_set_calls == 0 + assert opnsense_active_server_traffic_rate_mock.count_set_calls == 0