Add role label in metrics

I expecte to refactor and merge metrics together so
later those labels will be required
This commit is contained in:
Pierre Verkest 2023-09-03 21:52:19 +02:00
parent a2a1384e9d
commit 091ee429f1
5 changed files with 142 additions and 44 deletions

View file

@ -26,6 +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 - `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`
### Enums ### Enums
@ -81,25 +82,36 @@ 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
- 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`) - allow to change the listening port (today it force using `8000`)
- allow to configure timeouts using environemnt variables
- improves logging to get a debug mode to understand errors based on unexpected payloads
## Changelog ## Changelog
### Version 0.4.0 ### Version 0.0.5 (UNRELEASED)
Higher timeout while getting WAN traffic info * add role label in metrics
### Version 0.3.0 ### Version 0.4.0 (2023-09-02)
Use proper method to compute WAN traffic * Higher timeout while getting WAN traffic info
### Version 0.2.0 ### Version 0.3.0 (2023-09-02)
Setup automatic release from gitlab while pushing new tag * Use proper method to compute WAN traffic
### Version 0.1.0 ### Version 0.2.0 (2023-09-01)
Initial version * Setup automatic release from gitlab while pushing new tag
### Version 0.1.0 (2023-09-01)
* Initial version

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,30 @@ from requests import RequestException
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
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}"
@ -65,7 +80,7 @@ class OPNSenseAPI:
received = 0 received = 0
transmitted = 0 transmitted = 0
for record in data["wan"]["records"]: for record in data.get("wan", []).get("records", []):
received += record["rate_bits_in"] received += record.get("rate_bits_in", 0)
transmitted += record["rate_bits_out"] transmitted += record.get("rate_bits_out", 0)
return received, transmitted return received, transmitted

View file

@ -6,7 +6,7 @@ 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, OPNSenseRole
load_dotenv() load_dotenv()
@ -17,6 +17,7 @@ main_ha_state = Enum(
[ [
"instance", "instance",
"host", "host",
"role",
], ],
states=HA_STATES, states=HA_STATES,
) )
@ -26,6 +27,7 @@ backup_ha_state = Enum(
[ [
"instance", "instance",
"host", "host",
"role",
], ],
states=HA_STATES, states=HA_STATES,
) )
@ -35,6 +37,7 @@ active_server_bytes_received = Gauge(
[ [
"instance", "instance",
"host", "host",
"role",
], ],
) )
active_server_bytes_transmitted = Gauge( active_server_bytes_transmitted = Gauge(
@ -43,6 +46,7 @@ active_server_bytes_transmitted = Gauge(
[ [
"instance", "instance",
"host", "host",
"role",
], ],
) )
@ -51,8 +55,8 @@ def process_requests(main, backup, exporter_instance: str = ""):
"""A dummy function that takes some time.""" """A dummy function that takes some time."""
main_state = main.get_interface_vip_status() main_state = main.get_interface_vip_status()
backup_sate = backup.get_interface_vip_status() backup_sate = backup.get_interface_vip_status()
main_ha_state.labels(instance=exporter_instance, host=main.host).state(main_state) main_ha_state.labels(instance=exporter_instance, **main.labels).state(main_state)
backup_ha_state.labels(instance=exporter_instance, host=backup.host).state( backup_ha_state.labels(instance=exporter_instance, **backup.labels).state(
backup_sate backup_sate
) )
active_opnsense = None active_opnsense = None
@ -64,11 +68,11 @@ def process_requests(main, backup, exporter_instance: str = ""):
bytes_received, bytes_transmitted = active_opnsense.get_wan_trafic() bytes_received, bytes_transmitted = active_opnsense.get_wan_trafic()
if bytes_received or bytes_received == 0: if bytes_received or bytes_received == 0:
active_server_bytes_received.labels( active_server_bytes_received.labels(
instance=exporter_instance, host=active_opnsense.host instance=exporter_instance, **active_opnsense.labels
).set(bytes_received) ).set(bytes_received)
if bytes_transmitted or bytes_transmitted == 0: if bytes_transmitted or bytes_transmitted == 0:
active_server_bytes_transmitted.labels( active_server_bytes_transmitted.labels(
instance=exporter_instance, host=active_opnsense.host instance=exporter_instance, **active_opnsense.labels
).set(bytes_transmitted) ).set(bytes_transmitted)
@ -144,8 +148,12 @@ def run():
arguments = parser.parse_args() arguments = parser.parse_args()
start_server( start_server(
OPNSenseAPI(arguments.main, arguments.user, arguments.password), OPNSenseAPI(
OPNSenseAPI(arguments.backup, arguments.user, arguments.password), OPNSenseRole.MAIN, arguments.main, arguments.user, arguments.password
),
OPNSenseAPI(
OPNSenseRole.BACKUP, arguments.backup, arguments.user, arguments.password
),
check_frequency=arguments.frequency, check_frequency=arguments.frequency,
exporter_instance=arguments.prom_instance, exporter_instance=arguments.prom_instance,
) )

View file

@ -1,8 +1,9 @@
import responses import responses
from opnsense_exporter.opnsense_api import OPNSenseAPI from opnsense_exporter.opnsense_api import OPNSenseAPI, OPNSenseRole
from .common import ( from .common import (
BACKUP_HOST,
LOGIN, LOGIN,
MAIN_HOST, MAIN_HOST,
PASSWORD, PASSWORD,
@ -20,7 +21,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()
== "active"
) )
@ -33,7 +37,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()
== "hot_standby" == "hot_standby"
) )
@ -47,7 +53,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()
== "maintenancemode" == "maintenancemode"
) )
@ -60,7 +68,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()
== "unavailable" == "unavailable"
) )
@ -74,7 +84,9 @@ 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()
== "unavailable" == "unavailable"
) )
@ -86,7 +98,9 @@ def test_get_wan_traffic():
f"https://{MAIN_HOST}/api/diagnostics/traffic/top/wan", f"https://{MAIN_HOST}/api/diagnostics/traffic/top/wan",
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_wan_trafic() == (
20538, 20538,
10034, 10034,
) )
@ -100,7 +114,20 @@ def test_get_wan_traffic_none():
json={"error": "not found"}, json={"error": "not found"},
status=404, status=404,
) )
assert OPNSenseAPI(MAIN_HOST, LOGIN, PASSWORD).get_wan_trafic() == ( assert OPNSenseAPI(
OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD
).get_wan_trafic() == (
None, None,
None, None,
) )
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

@ -2,7 +2,7 @@ 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 process_requests, run
from .common import ( from .common import (
@ -109,23 +109,32 @@ def test_process_requests():
new=active_server_bytes_transmitted_mock, new=active_server_bytes_transmitted_mock,
): ):
process_requests( process_requests(
OPNSenseAPI(MAIN_HOST, LOGIN, PASSWORD), OPNSenseAPI(OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD),
OPNSenseAPI(BACKUP_HOST, LOGIN, PASSWORD), OPNSenseAPI(OPNSenseRole.BACKUP, BACKUP_HOST, LOGIN, PASSWORD),
) )
assert main_ha_state_mock._state == "active" 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
assert main_ha_state_mock._labels == {"instance": "", "host": MAIN_HOST} assert main_ha_state_mock._labels == {
"instance": "",
"host": MAIN_HOST,
"role": "main",
}
assert backup_ha_state_mock._state == "hot_standby" assert backup_ha_state_mock._state == "hot_standby"
assert backup_ha_state_mock.count_state_calls == 1 assert backup_ha_state_mock.count_state_calls == 1
assert backup_ha_state_mock._labels == {"instance": "", "host": BACKUP_HOST} assert backup_ha_state_mock._labels == {
"instance": "",
"host": BACKUP_HOST,
"role": "backup",
}
assert active_server_bytes_received_mock.value == 20538 assert active_server_bytes_received_mock.value == 20538
assert active_server_bytes_received_mock.count_set_calls == 1 assert active_server_bytes_received_mock.count_set_calls == 1
assert active_server_bytes_received_mock._labels == { assert active_server_bytes_received_mock._labels == {
"instance": "", "instance": "",
"host": MAIN_HOST, "host": MAIN_HOST,
"role": "main",
} }
assert active_server_bytes_transmitted_mock.value == 10034 assert active_server_bytes_transmitted_mock.value == 10034
@ -133,6 +142,7 @@ def test_process_requests():
assert active_server_bytes_transmitted_mock._labels == { assert active_server_bytes_transmitted_mock._labels == {
"instance": "", "instance": "",
"host": MAIN_HOST, "host": MAIN_HOST,
"role": "main",
} }
@ -172,22 +182,31 @@ def test_process_requests_backup_active():
new=active_server_bytes_transmitted_mock, new=active_server_bytes_transmitted_mock,
): ):
process_requests( process_requests(
OPNSenseAPI(MAIN_HOST, LOGIN, PASSWORD), OPNSenseAPI(OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD),
OPNSenseAPI(BACKUP_HOST, LOGIN, PASSWORD), OPNSenseAPI(OPNSenseRole.BACKUP, BACKUP_HOST, LOGIN, PASSWORD),
) )
assert main_ha_state_mock._state == "maintenancemode" assert main_ha_state_mock._state == "maintenancemode"
assert main_ha_state_mock.count_state_calls == 1 assert main_ha_state_mock.count_state_calls == 1
assert main_ha_state_mock._labels == {"instance": "", "host": MAIN_HOST} assert main_ha_state_mock._labels == {
"instance": "",
"host": MAIN_HOST,
"role": "main",
}
assert backup_ha_state_mock._state == "active" assert backup_ha_state_mock._state == "active"
assert backup_ha_state_mock.count_state_calls == 1 assert backup_ha_state_mock.count_state_calls == 1
assert backup_ha_state_mock._labels == {"instance": "", "host": BACKUP_HOST} assert backup_ha_state_mock._labels == {
"instance": "",
"host": BACKUP_HOST,
"role": "backup",
}
assert active_server_bytes_received_mock.value == 20538 assert active_server_bytes_received_mock.value == 20538
assert active_server_bytes_received_mock.count_set_calls == 1 assert active_server_bytes_received_mock.count_set_calls == 1
assert active_server_bytes_received_mock._labels == { assert active_server_bytes_received_mock._labels == {
"instance": "", "instance": "",
"host": BACKUP_HOST, "host": BACKUP_HOST,
"role": "backup",
} }
assert active_server_bytes_transmitted_mock.value == 10034 assert active_server_bytes_transmitted_mock.value == 10034
@ -195,6 +214,7 @@ def test_process_requests_backup_active():
assert active_server_bytes_transmitted_mock._labels == { assert active_server_bytes_transmitted_mock._labels == {
"instance": "", "instance": "",
"host": BACKUP_HOST, "host": BACKUP_HOST,
"role": "backup",
} }
@ -235,17 +255,25 @@ def test_process_no_active():
new=active_server_bytes_transmitted_mock, new=active_server_bytes_transmitted_mock,
): ):
process_requests( process_requests(
OPNSenseAPI(MAIN_HOST, LOGIN, PASSWORD), OPNSenseAPI(OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD),
OPNSenseAPI(BACKUP_HOST, LOGIN, PASSWORD), OPNSenseAPI(OPNSenseRole.BACKUP, BACKUP_HOST, LOGIN, PASSWORD),
) )
assert main_ha_state_mock._state == "maintenancemode" assert main_ha_state_mock._state == "maintenancemode"
assert main_ha_state_mock.count_state_calls == 1 assert main_ha_state_mock.count_state_calls == 1
assert main_ha_state_mock._labels == {"instance": "", "host": MAIN_HOST} assert main_ha_state_mock._labels == {
"instance": "",
"host": MAIN_HOST,
"role": "main",
}
assert backup_ha_state_mock._state == "unavailable" assert backup_ha_state_mock._state == "unavailable"
assert backup_ha_state_mock.count_state_calls == 1 assert backup_ha_state_mock.count_state_calls == 1
assert backup_ha_state_mock._labels == {"instance": "", "host": BACKUP_HOST} assert backup_ha_state_mock._labels == {
"instance": "",
"host": BACKUP_HOST,
"role": "backup",
}
assert active_server_bytes_received_mock.count_set_calls == 0 assert active_server_bytes_received_mock.count_set_calls == 0
assert active_server_bytes_transmitted_mock.count_set_calls == 0 assert active_server_bytes_transmitted_mock.count_set_calls == 0
@ -288,16 +316,24 @@ def test_process_with_falsy_value():
new=active_server_bytes_transmitted_mock, new=active_server_bytes_transmitted_mock,
): ):
process_requests( process_requests(
OPNSenseAPI(MAIN_HOST, LOGIN, PASSWORD), OPNSenseAPI(OPNSenseRole.MAIN, MAIN_HOST, LOGIN, PASSWORD),
OPNSenseAPI(BACKUP_HOST, LOGIN, PASSWORD), OPNSenseAPI(OPNSenseRole.BACKUP, BACKUP_HOST, LOGIN, PASSWORD),
) )
assert main_ha_state_mock._state == "active" 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
assert main_ha_state_mock._labels == {"instance": "", "host": MAIN_HOST} assert main_ha_state_mock._labels == {
"instance": "",
"host": MAIN_HOST,
"role": "main",
}
assert backup_ha_state_mock.count_state_calls == 1 assert backup_ha_state_mock.count_state_calls == 1
assert backup_ha_state_mock._state == "hot_standby" assert backup_ha_state_mock._state == "hot_standby"
assert backup_ha_state_mock._labels == {"instance": "", "host": BACKUP_HOST} assert backup_ha_state_mock._labels == {
"instance": "",
"host": BACKUP_HOST,
"role": "backup",
}
assert active_server_bytes_received_mock.count_set_calls == 0 assert active_server_bytes_received_mock.count_set_calls == 0
assert active_server_bytes_transmitted_mock.count_set_calls == 0 assert active_server_bytes_transmitted_mock.count_set_calls == 0