Compare commits

...

10 commits

Author SHA1 Message Date
b59a6368c9 add Dockerfile and docker-compose.yml with environmets 2023-10-20 19:59:19 +02:00
Pierre Verkest
d56f8dba6d Allow empty string interface to not call the OPNsense traffic diagnostic REST API endpoint 2023-09-06 09:09:41 +02:00
Pierre Verkest
4fbd6c94c7 remove main_ha_state and backup_ha_state
those metrics have been replaced by opnsense_server_ha_state in v0.5.1
2023-09-04 00:32:02 +02:00
Pierre Verkest
57d2ff9485 Bump version: 0.5.0 → 0.5.1 2023-09-04 00:22:03 +02:00
Pierre Verkest
bea721a275 FIX opnsense_server_ha_state metric
I've forgot to implement the main part calling state() on metric
2023-09-04 00:21:56 +02:00
Pierre Verkest
98f832ec3b Bump version: 0.4.0 → 0.5.0 2023-09-04 00:07:36 +02:00
Pierre Verkest
76b1338ae6 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
2023-09-04 00:06:17 +02:00
Pierre Verkest
7501a0d9b8 Allow to monitor multiple interfaces 2023-09-03 23:38:34 +02:00
Pierre Verkest
334be5b4c2 refactor create OPNSensePrometheusExporter class
goals is to avoid to transmit all params over sub calls
2023-09-03 22:08:24 +02:00
Pierre Verkest
091ee429f1 Add role label in metrics
I expecte to refactor and merge metrics together so
later those labels will be required
2023-09-03 21:52:19 +02:00
11 changed files with 861 additions and 348 deletions

View file

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 0.4.0 current_version = 0.5.1
commit = True commit = True
tag = True tag = True

6
.env.example Normal file
View file

@ -0,0 +1,6 @@
CHECK_FREQUENCY_SECONDS: default value for --check-frequency-seconds param
OPNSENSE_MAIN_HOST: default value for --main-host param
OPNSENSE_BACKUP_HOST: default value for --backup-host param
OPNSENSE_USERNAME: default value for --opnsense-user param
OPNSENSE_PASSWORD: default value for --opnsense-password param
OPNSENSE_INTERFACES: default value for --opnsense-interfaces param

22
Dockerfile Normal file
View file

@ -0,0 +1,22 @@
FROM python:latest
#WORKDIR /usr/app/src
# Install package
WORKDIR /code
COPY . .
#COPY prometheus-ssh-exporter.py ./
#COPY requirements.txt ./
RUN pip3 install -r requirements.txt
RUN python -u setup.py install
# Set this to the port you want to expose
EXPOSE 8000
# Set the -p option to the port you exposed above, defaults to 8000
#CMD ["python", "-u", "opnsense-exporter"]
#CMD ["sleep", "60"]
CMD ["opnsense-exporter"]

View file

@ -26,16 +26,24 @@ 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 - `instance`: by default this is set with the hostname where is running this exporter service
- `host`: the host of the OPNSense - `host`: the host of the OPNSense
- `role`: `main` or `backup` to determine the OPNSense server role.
### Enums ### Enums
- `opnsense_main_ha_state`: OPNSense HA state of the MAIN server - `opnsense_main_ha_state`: (deprecated) OPNSense HA state of the MAIN server
- `opnsense_backup_ha_state`: OPNSense HA state of the BACKUP 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 ### Gauges
- `opnsense_active_server_bytes_received`: Active OPNSense server bytes received on WAN interface - `opnsense_active_server_traffic_rate`: Active OPNSense server traffic rate per interfaces bits/s
- `opnsense_active_server_bytes_transmitted`: Active OPNSense server bytes transmitted on WAN interface 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 ## Usage
@ -46,6 +54,7 @@ opnsense-exporter --help
usage: opnsense-exporter [-h] [--check-frequency-seconds FREQUENCY] usage: opnsense-exporter [-h] [--check-frequency-seconds FREQUENCY]
[--main-host MAIN] [--backup-host BACKUP] [--main-host MAIN] [--backup-host BACKUP]
[--opnsense-user USER] [--opnsense-user USER]
[--opnsense-interfaces INTERFACES]
[--opnsense-password PASSWORD] [--opnsense-password PASSWORD]
[--prometheus-instance PROM_INSTANCE] [--prometheus-instance PROM_INSTANCE]
@ -60,17 +69,24 @@ optional arguments:
MAIN OPNsense server that should be in `active` MAIN OPNsense server that should be in `active`
state in normal configuration. state in normal configuration.
--backup-host BACKUP, -b BACKUP --backup-host BACKUP, -b BACKUP
BACKUP OPNsense server that should be BACKUP OPNsense server that should be `hot_standby`
`hot_standby` state in normal configuration. state in normal configuration.
--opnsense-user USER, -u USER --opnsense-user USER, -u USER
OPNsense user. Expect to be the same on MAIN and OPNsense user. Expect to be the same on MAIN and
BACKUP servers BACKUP servers
--opnsense-interfaces INTERFACES, -i INTERFACES
OPNsense interfaces (coma separated) list to
export trafic rates (bytes/s). An empty string ''
means not calling the traffic diagnostic REST API
so no `opnsense_active_server_traffic_rate`
metric. (default: wan,lan)
--opnsense-password PASSWORD, -p PASSWORD --opnsense-password PASSWORD, -p PASSWORD
OPNsense password. Expect to be the same on MAIN OPNsense password. Expect to be the same on MAIN
and BACKUP servers and BACKUP servers
--prometheus-instance PROM_INSTANCE --prometheus-instance PROM_INSTANCE
Exporter Instance name, default value computed Exporter Instance name, default value computed with
with hostname where the server is running. Use to 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 You can setup env through `.env` file or environment variables with defined as default values
@ -81,25 +97,52 @@ You can setup env through `.env` file or environment variables with defined as d
- **OPNSENSE_BACKUP_HOST**: default value for `--backup-host` param - **OPNSENSE_BACKUP_HOST**: default value for `--backup-host` param
- **OPNSENSE_USERNAME**: default value for `--opnsense-user` param - **OPNSENSE_USERNAME**: default value for `--opnsense-user` param
- **OPNSENSE_PASSWORD**: default value for `--opnsense-password` param - **OPNSENSE_PASSWORD**: default value for `--opnsense-password` param
- **OPNSENSE_INTERFACES**: default value for `--opnsense-interfaces` param
## Roadmap ## Roadmap
- allow to change the listening port (today it force using `8000`) - allow to change the listening port (today it force using `8000`)
- allow to configure timeouts using environment variables
- improves logging to get a debug mode to understand errors based on unexpected payloads
## Changelog ## Changelog
### Version 0.4.0 ### Version 1.0.0 (UNRELEASED)
Higher timeout while getting WAN traffic info - remove `opnsense_main_ha_state` and `opnsense_backup_ha_state`
metrics marked as deprecated on version 0.5.0 and replace
by `opnsense_server_ha_state` and `role` label
- allow empty string interfaces to **not** call diagnostic
traffic REST API
### Version 0.3.0
Use proper method to compute WAN traffic ### Version 0.5.1 (2023-09-04)
### Version 0.2.0 - FIX `opnsense_server_ha_state` calls were not
implemented
Setup automatic release from gitlab while pushing new tag ### Version 0.5.0 (2023-09-04)
### Version 0.1.0 - 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`
- add `opnsense_server_ha_state` and mark `opnsense_main_ha_state`
and `opnsense_backup_ha_state` as deprecated.
Initial version ### Version 0.4.0 (2023-09-02)
- Higher timeout while getting WAN traffic info
### Version 0.3.0 (2023-09-02)
- Use proper method to compute WAN traffic
### Version 0.2.0 (2023-09-01)
- Setup automatic release from gitlab while pushing new tag
### Version 0.1.0 (2023-09-01)
- Initial version

20
docker-compose.yml Normal file
View file

@ -0,0 +1,20 @@
version: '3.4'
services:
opnsense-exporter:
image: opnsense-exporter
build:
context: .
dockerfile: ./Dockerfile
restart: always
container_name: opnsense-exporter
#network_mode: "host"
ports:
- 8000:8000
env_file:
- .env
logging:
driver: "json-file"
options:
max-file: "3"
max-size: 10m

View file

@ -1,4 +1,5 @@
import logging import logging
from enum import Enum
import requests import requests
from requests import RequestException from requests import RequestException
@ -6,16 +7,68 @@ from requests import RequestException
logger = logging.getLogger(__name__) 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"
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"
class OPNSenseAPI: class OPNSenseAPI:
host: str = None host: str = None
login: str = None login: str = None
password: str = None password: str = None
role: OPNSenseRole = None
def __init__(self, host, login, password): def __init__(self, role, host, login, password):
self.role = role
self.host = host self.host = host
self.login = login self.login = login
self.password = password self.password = password
@property
def labels(self):
return {
"host": self.host,
"role": self.role.value,
}
def prepare_url(self, path): def prepare_url(self, path):
return f"https://{self.host}{path}" return f"https://{self.host}{path}"
@ -31,41 +84,46 @@ class OPNSenseAPI:
response.raise_for_status() response.raise_for_status()
return response.json() return response.json()
def get_interface_vip_status(self): def get_interface_vip_status(self) -> OPNSenseHAState:
try: try:
data = self.get("/api/diagnostics/interface/get_vip_status/") data = self.get("/api/diagnostics/interface/get_vip_status/")
except RequestException as ex: except RequestException as ex:
logger.error( logger.error(
"Get VIP STATUS on %s failed with the following error %r", self.host, ex "Get VIP STATUS on %s failed with the following error %r", self.host, ex
) )
return "unavailable" return OPNSenseHAState.UNAVAILABLE
if data["carp"]["maintenancemode"]: if data["carp"]["maintenancemode"]:
return "maintenancemode" return OPNSenseHAState.MAINTENANCE_MODE
is_active = all([row["status"] == "MASTER" for row in data["rows"]]) is_active = all([row["status"] == "MASTER" for row in data["rows"]])
if is_active: if is_active:
return "active" return OPNSenseHAState.ACTIVE
is_backup = all([row["status"] == "BACKUP" for row in data["rows"]]) is_backup = all([row["status"] == "BACKUP" for row in data["rows"]])
if is_backup: if is_backup:
return "hot_standby" return OPNSenseHAState.HOT_STANDBY
logger.warning( logger.warning(
"this host %s is no active nor backup received payload %s", self.host, data "this host %s is no active nor backup received payload %s", self.host, data
) )
return "unavailable" return OPNSenseHAState.UNAVAILABLE
def get_wan_trafic(self): def get_traffic(self, interfaces):
if not interfaces:
return []
try: 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: except RequestException as ex:
logger.error( 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, self.host,
ex, ex,
) )
return None, None return []
traffics = []
received = 0 for interface in interfaces.split(","):
transmitted = 0 traffic_in = OPNSenseTraffic(interface, OPNSenseTrafficMetric.IN)
for record in data["wan"]["records"]: traffic_out = OPNSenseTraffic(interface, OPNSenseTrafficMetric.OUT)
received += record["rate_bits_in"] for record in data.get(interface, {}).get("records", []):
transmitted += record["rate_bits_out"] traffic_in.value += record.get(OPNSenseTrafficMetric.IN.value, 0)
return received, transmitted traffic_out.value += record.get(OPNSenseTrafficMetric.OUT.value, 0)
traffics.extend([traffic_in, traffic_out])
return traffics

View file

@ -1,4 +1,5 @@
import argparse import argparse
import logging
import os import os
import socket import socket
import time import time
@ -6,84 +7,85 @@ import time
from dotenv import load_dotenv from dotenv import load_dotenv
from prometheus_client import Enum, Gauge, start_http_server from prometheus_client import Enum, Gauge, start_http_server
from opnsense_exporter.opnsense_api import OPNSenseAPI from opnsense_exporter.opnsense_api import OPNSenseAPI, OPNSenseHAState, OPNSenseRole
logger = logging.getLogger(__name__)
load_dotenv() load_dotenv()
HA_STATES = ["active", "hot_standby", "unavailable", "maintenancemode"] HA_STATES = [enum.value for enum in list(OPNSenseHAState)]
main_ha_state = Enum(
"opnsense_main_ha_state",
"OPNSense HA state of the MAIN server", opnsense_server_ha_state = Enum(
"opnsense_server_ha_state",
"OPNSense server HA state",
[ [
"instance", "instance",
"host", "host",
"role",
], ],
states=HA_STATES, states=HA_STATES,
) )
backup_ha_state = Enum(
"opnsense_backup_ha_state", opnsense_active_server_traffic_rate = Gauge(
"OPNSense HA state of the BACKUP server", "opnsense_active_server_traffic_rate",
[ "Active OPNSense server bytes in/out per interface",
"instance",
"host",
],
states=HA_STATES,
)
active_server_bytes_received = Gauge(
"opnsense_active_server_bytes_received",
"Active OPNSense server bytes received on WAN interface",
[
"instance",
"host",
],
)
active_server_bytes_transmitted = Gauge(
"opnsense_active_server_bytes_transmitted",
"Active OPNSense server bytes transmitted on WAN interface",
[ [
"instance", "instance",
"host", "host",
"role",
"interface",
"metric",
], ],
) )
def process_requests(main, backup, exporter_instance: str = ""): class OPNSensePrometheusExporter:
"""A dummy function that takes some time.""" def __init__(
main_state = main.get_interface_vip_status() self,
backup_sate = backup.get_interface_vip_status()
main_ha_state.labels(instance=exporter_instance, host=main.host).state(main_state)
backup_ha_state.labels(instance=exporter_instance, host=backup.host).state(
backup_sate
)
active_opnsense = None
if main_state == "active":
active_opnsense = main
if backup_sate == "active":
active_opnsense = 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=exporter_instance, host=active_opnsense.host
).set(bytes_received)
if bytes_transmitted or bytes_transmitted == 0:
active_server_bytes_transmitted.labels(
instance=exporter_instance, host=active_opnsense.host
).set(bytes_transmitted)
def start_server(
main: OPNSenseAPI, main: OPNSenseAPI,
backup: OPNSenseAPI, backup: OPNSenseAPI,
check_frequency: int = 1, interfaces,
exporter_instance: str = "", 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
def process_requests(self):
"""A dummy function that takes some time."""
main_state = self.main.get_interface_vip_status()
backup_sate = self.backup.get_interface_vip_status()
opnsense_server_ha_state.labels(
instance=self.exporter_instance, **self.main.labels
).state(main_state.value)
opnsense_server_ha_state.labels(
instance=self.exporter_instance, **self.backup.labels
).state(backup_sate.value)
active_opnsense = None
if main_state == OPNSenseHAState.ACTIVE:
active_opnsense = self.main
if backup_sate == OPNSenseHAState.ACTIVE:
active_opnsense = self.backup
if active_opnsense:
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, port=8000):
# Start up the server to expose the metrics. # Start up the server to expose the metrics.
start_http_server(8000) start_http_server(port)
logger.info("listen port %s", port)
# Generate some requests. # Generate some requests.
while True: while True:
process_requests(main, backup, exporter_instance=exporter_instance) self.process_requests()
time.sleep(check_frequency) time.sleep(self.check_frequency)
def run(): def run():
@ -123,6 +125,16 @@ def run():
default=os.environ.get("OPNSENSE_USERNAME", None), default=os.environ.get("OPNSENSE_USERNAME", None),
help="OPNsense user. Expect to be the same on MAIN and BACKUP servers", 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). "
"An empty string '' means not calling the traffic diagnostic REST API so no "
"`opnsense_active_server_traffic_rate` metric.",
)
parser.add_argument( parser.add_argument(
"--opnsense-password", "--opnsense-password",
"-p", "-p",
@ -143,9 +155,19 @@ def run():
) )
arguments = parser.parse_args() arguments = parser.parse_args()
start_server(
OPNSenseAPI(arguments.main, arguments.user, arguments.password), server = OPNSensePrometheusExporter(
OPNSenseAPI(arguments.backup, arguments.user, arguments.password), OPNSenseAPI(
OPNSenseRole.MAIN, arguments.main, arguments.user, arguments.password
),
OPNSenseAPI(
OPNSenseRole.BACKUP, arguments.backup, arguments.user, arguments.password
),
arguments.interfaces,
check_frequency=arguments.frequency, check_frequency=arguments.frequency,
exporter_instance=arguments.prom_instance, exporter_instance=arguments.prom_instance,
) )
server.start_server()
# return the server instance mainly for test purpose
return server

View file

@ -3,7 +3,7 @@ from urllib.parse import urlparse
from setuptools import find_packages, setup from setuptools import find_packages, setup
version = "0.4.0" version = "0.5.1"
HERE = pathlib.Path(__file__).parent HERE = pathlib.Path(__file__).parent

View file

@ -47,126 +47,131 @@ def generate_get_vip_status_paylaod(state_wan, state_lan, maintenance_mode):
def generate_diagnostics_traffic_interface_paylaod(): 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( return json.dumps(
{ {
"wan": { "wan": {
"records": [ "records": [
{ {
"address": "0.1.2.3", "address": "0.1.2.3",
"rate_bits_in": 15300, "rate_bits_in": 62300,
"rate_bits_out": 1720, "rate_bits_out": 66100,
"rate_bits": 17020, "rate_bits": 128400,
"cumulative_bytes_in": 3830, "cumulative_bytes_in": 15600,
"cumulative_bytes_out": 441, "cumulative_bytes_out": 16500,
"cumulative_bytes": 4271, "cumulative_bytes": 32100,
"tags": [], "tags": [],
"details": [ "details": [
{ {
"address": "0.1.2.3", "address": "0.1.2.3",
"rate": "15.3Kb", "rate": "62.3Kb",
"rate_bits": 15300, "rate_bits": 62300,
"cumulative": "3.83KB", "cumulative": "15.6KB",
"cumulative_bytes": 3830, "cumulative_bytes": 15600,
"tags": ["local"], "tags": ["local"],
} }
], ],
"rname": "fake value", "rname": "fake rname value",
"rate_in": "15.3 kb", "rate_in": "62.3 kb",
"rate_out": "1.72 kb", "rate_out": "66.1 kb",
"rate": "17.02 kb", "rate": "128.4 kb",
"cumulative_in": "3.83 kb", "cumulative_in": "15.6 kb",
"cumulative_out": "441.0 b", "cumulative_out": "16.5 kb",
"cumulative": "4.27 kb", "cumulative": "32.1 kb",
}, },
{ {
"address": "0.1.2.3", "address": "0.1.2.3",
"rate_bits_in": 4470, "rate_bits_in": 36200,
"rate_bits_out": 7290, "rate_bits_out": 16100,
"rate_bits": 11760, "rate_bits": 52300,
"cumulative_bytes_in": 1120, "cumulative_bytes_in": 9060,
"cumulative_bytes_out": 1820, "cumulative_bytes_out": 4020,
"cumulative_bytes": 2940, "cumulative_bytes": 13080,
"tags": [], "tags": [],
"details": [ "details": [
{ {
"address": "0.1.2.3", "address": "0.1.2.3",
"rate": "4.47Kb", "rate": "36.2Kb",
"rate_bits": 4470, "rate_bits": 36200,
"cumulative": "1.12KB", "cumulative": "9.06KB",
"cumulative_bytes": 1120, "cumulative_bytes": 9060,
"tags": ["local"], "tags": ["local"],
} }
], ],
"rname": "fake value", "rname": "fake rname value",
"rate_in": "4.47 kb", "rate_in": "36.2 kb",
"rate_out": "7.29 kb", "rate_out": "16.1 kb",
"rate": "11.76 kb", "rate": "52.3 kb",
"cumulative_in": "1.12 kb", "cumulative_in": "9.06 kb",
"cumulative_out": "1.82 kb", "cumulative_out": "4.02 kb",
"cumulative": "2.94 kb", "cumulative": "13.08 kb",
}, },
{ {
"address": "0.1.2.3", "address": "0.1.2.3",
"rate_bits_in": 272, "rate_bits_in": 1790,
"rate_bits_out": 272, "rate_bits_out": 1520,
"rate_bits": 544, "rate_bits": 3310,
"cumulative_bytes_in": 68, "cumulative_bytes_in": 459,
"cumulative_bytes_out": 68, "cumulative_bytes_out": 389,
"cumulative_bytes": 136, "cumulative_bytes": 848,
"tags": [], "tags": [],
"details": [ "details": [
{ {
"address": "0.1.2.3", "address": "0.1.2.3",
"rate": "272b", "rate": "1.79Kb",
"rate_bits": 272, "rate_bits": 1790,
"cumulative": "68B", "cumulative": "459B",
"cumulative_bytes": 68, "cumulative_bytes": 459,
"tags": ["local"], "tags": ["local"],
} }
], ],
"rname": "fake value", "rname": "fake rname value",
"rate_in": "272.0 b", "rate_in": "1.79 kb",
"rate_out": "272.0 b", "rate_out": "1.52 kb",
"rate": "544.0 b", "rate": "3.31 kb",
"cumulative_in": "68.0 b", "cumulative_in": "459.0 b",
"cumulative_out": "68.0 b", "cumulative_out": "389.0 b",
"cumulative": "136.0 b", "cumulative": "848.0 b",
}, },
{ {
"address": "0.1.2.3", "address": "0.1.2.3",
"rate_bits_in": 272, "rate_bits_in": 512,
"rate_bits_out": 272, "rate_bits_out": 1580,
"rate_bits": 544, "rate_bits": 2092,
"cumulative_bytes_in": 68, "cumulative_bytes_in": 128,
"cumulative_bytes_out": 68, "cumulative_bytes_out": 405,
"cumulative_bytes": 136, "cumulative_bytes": 533,
"tags": [], "tags": [],
"details": [ "details": [
{ {
"address": "0.1.2.3", "address": "0.1.2.3",
"rate": "272b", "rate": "512b",
"rate_bits": 272, "rate_bits": 512,
"cumulative": "68B", "cumulative": "128B",
"cumulative_bytes": 68, "cumulative_bytes": 128,
"tags": ["local"], "tags": ["local"],
} }
], ],
"rname": "fake value", "rname": "fake rname value",
"rate_in": "272.0 b", "rate_in": "512.0 b",
"rate_out": "272.0 b", "rate_out": "1.58 kb",
"rate": "544.0 b", "rate": "2.09 kb",
"cumulative_in": "68.0 b", "cumulative_in": "128.0 b",
"cumulative_out": "68.0 b", "cumulative_out": "405.0 b",
"cumulative": "136.0 b", "cumulative": "533.0 b",
}, },
{ {
"address": "0.1.2.3", "address": "0.1.2.3",
"rate_bits_in": 0, "rate_bits_in": 0,
"rate_bits_out": 480, "rate_bits_out": 448,
"rate_bits": 480, "rate_bits": 448,
"cumulative_bytes_in": 0, "cumulative_bytes_in": 0,
"cumulative_bytes_out": 120, "cumulative_bytes_out": 112,
"cumulative_bytes": 120, "cumulative_bytes": 112,
"tags": [], "tags": [],
"details": [ "details": [
{ {
@ -176,7 +181,26 @@ def generate_diagnostics_traffic_interface_paylaod():
"cumulative": "0B", "cumulative": "0B",
"cumulative_bytes": 0, "cumulative_bytes": 0,
"tags": ["local"], "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", "address": "0.1.2.3",
"rate": "0b", "rate": "0b",
@ -184,15 +208,15 @@ def generate_diagnostics_traffic_interface_paylaod():
"cumulative": "0B", "cumulative": "0B",
"cumulative_bytes": 0, "cumulative_bytes": 0,
"tags": ["local"], "tags": ["local"],
}, }
], ],
"rname": "fake value", "rname": "fake rname value",
"rate_in": "0.0 b", "rate_in": "0.0 b",
"rate_out": "480.0 b", "rate_out": "272.0 b",
"rate": "480.0 b", "rate": "272.0 b",
"cumulative_in": "0.0 b", "cumulative_in": "0.0 b",
"cumulative_out": "120.0 b", "cumulative_out": "68.0 b",
"cumulative": "120.0 b", "cumulative": "68.0 b",
}, },
{ {
"address": "0.1.2.3", "address": "0.1.2.3",
@ -213,7 +237,7 @@ def generate_diagnostics_traffic_interface_paylaod():
"tags": ["local"], "tags": ["local"],
} }
], ],
"rname": "fake value", "rname": "fake rname value",
"rate_in": "224.0 b", "rate_in": "224.0 b",
"rate_out": "0.0 b", "rate_out": "0.0 b",
"rate": "224.0 b", "rate": "224.0 b",
@ -223,6 +247,224 @@ def generate_diagnostics_traffic_interface_paylaod():
}, },
], ],
"status": "ok", "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",
},
} }
) )

View file

@ -1,8 +1,14 @@
import responses import responses
from opnsense_exporter.opnsense_api import OPNSenseAPI from opnsense_exporter.opnsense_api import (
OPNSenseAPI,
OPNSenseRole,
OPNSenseTraffic,
OPNSenseTrafficMetric,
)
from .common import ( from .common import (
BACKUP_HOST,
LOGIN, LOGIN,
MAIN_HOST, MAIN_HOST,
PASSWORD, PASSWORD,
@ -20,7 +26,10 @@ def test_get_interface_vip_status_active():
) )
assert ( assert (
OPNSenseAPI(MAIN_HOST, LOGIN, PASSWORD).get_interface_vip_status() == "active" OPNSenseAPI(OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD)
.get_interface_vip_status()
.value
== "active"
) )
@ -33,7 +42,9 @@ def test_get_interface_vip_status_backup():
) )
assert ( assert (
OPNSenseAPI(MAIN_HOST, LOGIN, PASSWORD).get_interface_vip_status() OPNSenseAPI(OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD)
.get_interface_vip_status()
.value
== "hot_standby" == "hot_standby"
) )
@ -47,7 +58,9 @@ def test_get_interface_vip_status_mainteance_mode():
) )
assert ( assert (
OPNSenseAPI(MAIN_HOST, LOGIN, PASSWORD).get_interface_vip_status() OPNSenseAPI(OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD)
.get_interface_vip_status()
.value
== "maintenancemode" == "maintenancemode"
) )
@ -60,7 +73,9 @@ def test_get_interface_vip_status_unavailable_weird_case():
body=generate_get_vip_status_paylaod("MASTER", "BACKUP", False), body=generate_get_vip_status_paylaod("MASTER", "BACKUP", False),
) )
assert ( assert (
OPNSenseAPI(MAIN_HOST, LOGIN, PASSWORD).get_interface_vip_status() OPNSenseAPI(OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD)
.get_interface_vip_status()
.value
== "unavailable" == "unavailable"
) )
@ -74,33 +89,65 @@ def test_get_interface_vip_status_unavailable_rest_api_error():
status=404, status=404,
) )
assert ( assert (
OPNSenseAPI(MAIN_HOST, LOGIN, PASSWORD).get_interface_vip_status() OPNSenseAPI(OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD)
.get_interface_vip_status()
.value
== "unavailable" == "unavailable"
) )
@responses.activate @responses.activate
def test_get_wan_traffic(): def test_get_traffic():
responses.add( responses.add(
responses.GET, 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(), body=generate_diagnostics_traffic_interface_paylaod(),
) )
assert OPNSenseAPI(MAIN_HOST, LOGIN, PASSWORD).get_wan_trafic() == ( assert OPNSenseAPI(OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD).get_traffic(
20538, "wan,lan"
10034, ) == [
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_traffic_none():
responses.add(
responses.GET,
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_traffic(
"test-not-found"
)
== []
) )
@responses.activate @responses.activate
def test_get_wan_traffic_none(): def test_get_traffic_empty_string():
responses.add( rsp = responses.add(
responses.GET, responses.GET,
f"https://{MAIN_HOST}/api/diagnostics/traffic/top/wan", f"https://{MAIN_HOST}/api/diagnostics/traffic/top/",
json={"error": "not found"}, json={"not": "called"},
status=404,
) )
assert OPNSenseAPI(MAIN_HOST, LOGIN, PASSWORD).get_wan_trafic() == ( assert (
None, OPNSenseAPI(OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD).get_traffic("") == []
None,
) )
assert rsp.call_count == 0
def test_labels():
assert OPNSenseAPI(OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD).labels == {
"role": "main",
"host": MAIN_HOST,
}
assert OPNSenseAPI(OPNSenseRole.BACKUP, BACKUP_HOST, LOGIN, PASSWORD).labels == {
"role": "backup",
"host": BACKUP_HOST,
}

View file

@ -1,9 +1,10 @@
from typing import List
from unittest import mock from unittest import mock
import responses import responses
from opnsense_exporter.opnsense_api import OPNSenseAPI from opnsense_exporter.opnsense_api import OPNSenseAPI, OPNSenseRole
from opnsense_exporter.server import process_requests, run from opnsense_exporter.server import OPNSensePrometheusExporter, run
from .common import ( from .common import (
BACKUP_HOST, BACKUP_HOST,
@ -17,31 +18,57 @@ from .common import (
class FakePromMetric: class FakePromMetric:
_labels = {} _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): def labels(self, *args, **kwargs):
self._labels = kwargs self._labels = kwargs
self._labels_calls.append(kwargs)
return self return self
class FakePromEnum(FakePromMetric): class FakePromEnum(FakePromMetric):
_state = None _state: str = None
count_state_calls = 0 _state_calls: List[str] = []
def state(self, state): def __init__(self):
self.count_state_calls += 1 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 = state
self._state_calls.append(state)
class FakePromGauge(FakePromMetric): class FakePromGauge(FakePromMetric):
value = None _value: int = None
count_set_calls = 0 _set_calls: List[int] = []
def set(self, value): def __init__(self):
self.count_set_calls += 1 super().__init__()
self.value = value 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.start_server") @mock.patch("opnsense_exporter.server.OPNSensePrometheusExporter.start_server")
def test_parser(server_mock): def test_parser(server_mock):
with mock.patch( with mock.patch(
"sys.argv", "sys.argv",
@ -57,20 +84,25 @@ def test_parser(server_mock):
"user-test", "user-test",
"-p", "-p",
"pwd-test", "pwd-test",
"-i",
"efg,hij",
"--prometheus-instance", "--prometheus-instance",
"server-hostname-instance", "server-hostname-instance",
], ],
): ):
run() server = run()
server_mock.assert_called_once() server_mock.assert_called_once()
main, bck = server_mock.call_args.args
assert main.login == "user-test" assert server.main.role == OPNSenseRole.MAIN
assert bck.login == "user-test" assert server.main.host == "main.host"
assert main.password == "pwd-test" assert server.main.login == "user-test"
assert bck.password == "pwd-test" assert server.main.password == "pwd-test"
assert main.host == "main.host" assert server.backup.role == OPNSenseRole.BACKUP
assert bck.host == "backup.host" assert server.backup.host == "backup.host"
assert server_mock.call_args.kwargs["check_frequency"] == 15 assert server.backup.login == "user-test"
assert server.backup.password == "pwd-test"
assert server.check_frequency == 15
assert server.interfaces == "efg,hij"
@responses.activate @responses.activate
@ -91,49 +123,56 @@ def test_process_requests():
body=generate_diagnostics_traffic_interface_paylaod(), body=generate_diagnostics_traffic_interface_paylaod(),
) )
main_ha_state_mock = FakePromEnum() opnsense_server_ha_state_mock = FakePromEnum()
backup_ha_state_mock = FakePromEnum() opnsense_active_server_traffic_rate_mock = FakePromGauge()
active_server_bytes_received_mock = FakePromGauge()
active_server_bytes_transmitted_mock = FakePromGauge()
with mock.patch("opnsense_exporter.server.main_ha_state", new=main_ha_state_mock):
with mock.patch( with mock.patch(
"opnsense_exporter.server.backup_ha_state", new=backup_ha_state_mock "opnsense_exporter.server.opnsense_server_ha_state",
new=opnsense_server_ha_state_mock,
): ):
with mock.patch( with mock.patch(
"opnsense_exporter.server.active_server_bytes_received", "opnsense_exporter.server.opnsense_active_server_traffic_rate",
new=active_server_bytes_received_mock, new=opnsense_active_server_traffic_rate_mock,
): ):
with mock.patch( OPNSensePrometheusExporter(
"opnsense_exporter.server.active_server_bytes_transmitted", OPNSenseAPI(OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD),
new=active_server_bytes_transmitted_mock, OPNSenseAPI(OPNSenseRole.BACKUP, BACKUP_HOST, LOGIN, PASSWORD),
): "wan",
process_requests( ).process_requests()
OPNSenseAPI(MAIN_HOST, LOGIN, PASSWORD),
OPNSenseAPI(BACKUP_HOST, LOGIN, PASSWORD),
)
assert main_ha_state_mock._state == "active" assert opnsense_server_ha_state_mock.count_state_calls == 2
assert main_ha_state_mock.count_state_calls == 1 assert opnsense_server_ha_state_mock._labels_calls == [
assert main_ha_state_mock._labels == {"instance": "", "host": MAIN_HOST} {
assert backup_ha_state_mock._state == "hot_standby"
assert backup_ha_state_mock.count_state_calls == 1
assert backup_ha_state_mock._labels == {"instance": "", "host": BACKUP_HOST}
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": "", "instance": "",
"host": MAIN_HOST, "host": MAIN_HOST,
} "role": "main",
},
{
"instance": "",
"host": BACKUP_HOST,
"role": "backup",
},
]
assert opnsense_server_ha_state_mock._state_calls == ["active", "hot_standby"]
assert active_server_bytes_transmitted_mock.value == 10034 assert opnsense_active_server_traffic_rate_mock.count_set_calls == 2
assert active_server_bytes_transmitted_mock.count_set_calls == 1 assert opnsense_active_server_traffic_rate_mock._labels_calls == [
assert active_server_bytes_transmitted_mock._labels == { {
"instance": "", "instance": "",
"host": MAIN_HOST, "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 @responses.activate
@ -154,48 +193,56 @@ def test_process_requests_backup_active():
body=generate_diagnostics_traffic_interface_paylaod(), body=generate_diagnostics_traffic_interface_paylaod(),
) )
main_ha_state_mock = FakePromEnum() opnsense_server_ha_state_mock = FakePromEnum()
backup_ha_state_mock = FakePromEnum() opnsense_active_server_traffic_rate_mock = FakePromGauge()
active_server_bytes_received_mock = FakePromGauge()
active_server_bytes_transmitted_mock = FakePromGauge()
with mock.patch("opnsense_exporter.server.main_ha_state", new=main_ha_state_mock):
with mock.patch( with mock.patch(
"opnsense_exporter.server.backup_ha_state", new=backup_ha_state_mock "opnsense_exporter.server.opnsense_server_ha_state",
new=opnsense_server_ha_state_mock,
): ):
with mock.patch( with mock.patch(
"opnsense_exporter.server.active_server_bytes_received", "opnsense_exporter.server.opnsense_active_server_traffic_rate",
new=active_server_bytes_received_mock, new=opnsense_active_server_traffic_rate_mock,
): ):
with mock.patch( OPNSensePrometheusExporter(
"opnsense_exporter.server.active_server_bytes_transmitted", OPNSenseAPI(OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD),
new=active_server_bytes_transmitted_mock, OPNSenseAPI(OPNSenseRole.BACKUP, BACKUP_HOST, LOGIN, PASSWORD),
): "wan",
process_requests( ).process_requests()
OPNSenseAPI(MAIN_HOST, LOGIN, PASSWORD),
OPNSenseAPI(BACKUP_HOST, LOGIN, PASSWORD),
)
assert main_ha_state_mock._state == "maintenancemode"
assert main_ha_state_mock.count_state_calls == 1
assert main_ha_state_mock._labels == {"instance": "", "host": MAIN_HOST}
assert backup_ha_state_mock._state == "active" assert opnsense_server_ha_state_mock.count_state_calls == 2
assert backup_ha_state_mock.count_state_calls == 1 assert opnsense_server_ha_state_mock._labels_calls == [
assert backup_ha_state_mock._labels == {"instance": "", "host": BACKUP_HOST} {
"instance": "",
assert active_server_bytes_received_mock.value == 20538 "host": MAIN_HOST,
assert active_server_bytes_received_mock.count_set_calls == 1 "role": "main",
assert active_server_bytes_received_mock._labels == { },
{
"instance": "", "instance": "",
"host": BACKUP_HOST, "host": BACKUP_HOST,
} "role": "backup",
},
]
assert opnsense_server_ha_state_mock._state_calls == ["maintenancemode", "active"]
assert active_server_bytes_transmitted_mock.value == 10034 assert opnsense_active_server_traffic_rate_mock.count_set_calls == 2
assert active_server_bytes_transmitted_mock.count_set_calls == 1 opnsense_active_server_traffic_rate_mock._labels_calls == [
assert active_server_bytes_transmitted_mock._labels == { {
"instance": "", "instance": "",
"host": BACKUP_HOST, "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 @responses.activate
@ -217,38 +264,42 @@ def test_process_no_active():
body=generate_diagnostics_traffic_interface_paylaod(), body=generate_diagnostics_traffic_interface_paylaod(),
) )
main_ha_state_mock = FakePromEnum() opnsense_server_ha_state_mock = FakePromEnum()
backup_ha_state_mock = FakePromEnum() opnsense_active_server_traffic_rate_mock = FakePromGauge()
active_server_bytes_received_mock = FakePromGauge()
active_server_bytes_transmitted_mock = FakePromGauge()
with mock.patch("opnsense_exporter.server.main_ha_state", new=main_ha_state_mock):
with mock.patch( with mock.patch(
"opnsense_exporter.server.backup_ha_state", new=backup_ha_state_mock "opnsense_exporter.server.opnsense_server_ha_state",
new=opnsense_server_ha_state_mock,
): ):
with mock.patch( with mock.patch(
"opnsense_exporter.server.active_server_bytes_received", "opnsense_exporter.server.opnsense_active_server_traffic_rate",
new=active_server_bytes_received_mock, new=opnsense_active_server_traffic_rate_mock,
): ):
with mock.patch( OPNSensePrometheusExporter(
"opnsense_exporter.server.active_server_bytes_transmitted", OPNSenseAPI(OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD),
new=active_server_bytes_transmitted_mock, OPNSenseAPI(OPNSenseRole.BACKUP, BACKUP_HOST, LOGIN, PASSWORD),
): "wan",
process_requests( ).process_requests()
OPNSenseAPI(MAIN_HOST, LOGIN, PASSWORD),
OPNSenseAPI(BACKUP_HOST, LOGIN, PASSWORD),
)
assert main_ha_state_mock._state == "maintenancemode" assert opnsense_server_ha_state_mock.count_state_calls == 2
assert main_ha_state_mock.count_state_calls == 1 assert opnsense_server_ha_state_mock._labels_calls == [
assert main_ha_state_mock._labels == {"instance": "", "host": MAIN_HOST} {
"instance": "",
"host": MAIN_HOST,
"role": "main",
},
{
"instance": "",
"host": BACKUP_HOST,
"role": "backup",
},
]
assert opnsense_server_ha_state_mock._state_calls == [
"maintenancemode",
"unavailable",
]
assert backup_ha_state_mock._state == "unavailable" assert opnsense_active_server_traffic_rate_mock.count_set_calls == 0
assert backup_ha_state_mock.count_state_calls == 1
assert backup_ha_state_mock._labels == {"instance": "", "host": BACKUP_HOST}
assert active_server_bytes_received_mock.count_set_calls == 0
assert active_server_bytes_transmitted_mock.count_set_calls == 0
@responses.activate @responses.activate
@ -270,34 +321,36 @@ def test_process_with_falsy_value():
status=404, status=404,
) )
main_ha_state_mock = FakePromEnum() opnsense_server_ha_state_mock = FakePromEnum()
backup_ha_state_mock = FakePromEnum() opnsense_active_server_traffic_rate_mock = FakePromGauge()
active_server_bytes_received_mock = FakePromGauge()
active_server_bytes_transmitted_mock = FakePromGauge()
with mock.patch("opnsense_exporter.server.main_ha_state", new=main_ha_state_mock):
with mock.patch( with mock.patch(
"opnsense_exporter.server.backup_ha_state", new=backup_ha_state_mock "opnsense_exporter.server.opnsense_server_ha_state",
new=opnsense_server_ha_state_mock,
): ):
with mock.patch( with mock.patch(
"opnsense_exporter.server.active_server_bytes_received", "opnsense_exporter.server.opnsense_active_server_traffic_rate",
new=active_server_bytes_received_mock, new=opnsense_active_server_traffic_rate_mock,
): ):
with mock.patch( OPNSensePrometheusExporter(
"opnsense_exporter.server.active_server_bytes_transmitted", OPNSenseAPI(OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD),
new=active_server_bytes_transmitted_mock, OPNSenseAPI(OPNSenseRole.BACKUP, BACKUP_HOST, LOGIN, PASSWORD),
): "wan",
process_requests( ).process_requests()
OPNSenseAPI(MAIN_HOST, LOGIN, PASSWORD),
OPNSenseAPI(BACKUP_HOST, LOGIN, PASSWORD),
)
assert main_ha_state_mock._state == "active"
assert main_ha_state_mock.count_state_calls == 1
assert main_ha_state_mock._labels == {"instance": "", "host": MAIN_HOST}
assert backup_ha_state_mock.count_state_calls == 1 assert opnsense_server_ha_state_mock.count_state_calls == 2
assert backup_ha_state_mock._state == "hot_standby" assert opnsense_server_ha_state_mock._labels_calls == [
assert backup_ha_state_mock._labels == {"instance": "", "host": BACKUP_HOST} {
"instance": "",
"host": MAIN_HOST,
"role": "main",
},
{
"instance": "",
"host": BACKUP_HOST,
"role": "backup",
},
]
assert opnsense_server_ha_state_mock._state_calls == ["active", "hot_standby"]
assert active_server_bytes_received_mock.count_set_calls == 0 assert opnsense_active_server_traffic_rate_mock.count_set_calls == 0
assert active_server_bytes_transmitted_mock.count_set_calls == 0