First implementation
This commit is contained in:
commit
15bb93d8a4
14 changed files with 765 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
.env
|
||||
coverage.xml
|
||||
junit.xml
|
22
.pre-commit-config.yaml
Normal file
22
.pre-commit-config.yaml
Normal file
|
@ -0,0 +1,22 @@
|
|||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0 # Use the ref you want to point at
|
||||
hooks:
|
||||
- id: check-yaml
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
|
||||
- repo: https://github.com/pycqa/flake8
|
||||
rev: 6.1.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
|
||||
- repo: https://github.com/ambv/black
|
||||
rev: 23.7.0
|
||||
hooks:
|
||||
- id: black
|
||||
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.12.0
|
||||
hooks:
|
||||
- id: isort
|
33
README.md
Normal file
33
README.md
Normal file
|
@ -0,0 +1,33 @@
|
|||
# OPNSense Prometheus exporter
|
||||
|
||||
I've configures OPNSense with High Availability settings using 2 servers.
|
||||
|
||||
* https://docs.opnsense.org/manual/hacarp.html
|
||||
* https://docs.opnsense.org/manual/how-tos/carp.html
|
||||
|
||||
So I've 2 servers: *MAIN* and *BACKUP*, in normal situation *MAIN* server
|
||||
is expected to be `active` and the *BACKUP* server to be in `hot_standby` state.
|
||||
|
||||
|
||||
The initial needs was to be able to make sure that *BACKUP* server is ready (hot standby)
|
||||
to get the main server role with the `active` state at any time.
|
||||
|
||||
> Unfortunately I've not found a proper configuration to call OPNSense HTTP API over
|
||||
> opnvpn on backup server using blackbox configuratoin. That why I've started to develop
|
||||
> this exporter install on a server on the LAN to be able to resquest both OPNSense servers.
|
||||
|
||||
## Metrics
|
||||
|
||||
This exporter gives following metrics:
|
||||
|
||||
* ``:
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
> *Note*: Most updated documentation from command line !
|
||||
|
||||
```
|
||||
opnsense-exporter --help
|
||||
```
|
9
opnsense_exporter/__init__.py
Normal file
9
opnsense_exporter/__init__.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
# this is a namespace package
|
||||
try:
|
||||
import pkg_resources
|
||||
|
||||
pkg_resources.declare_namespace(__name__)
|
||||
except ImportError:
|
||||
import pkgutil
|
||||
|
||||
__path__ = pkgutil.extend_path(__path__, __name__)
|
90
opnsense_exporter/opnsense_api.py
Normal file
90
opnsense_exporter/opnsense_api.py
Normal file
|
@ -0,0 +1,90 @@
|
|||
import logging
|
||||
|
||||
import requests
|
||||
from requests import RequestException
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OPNSenseAPI:
|
||||
host: str = None
|
||||
login: str = None
|
||||
password: str = None
|
||||
|
||||
def __init__(self, host, login, password):
|
||||
self.host = host
|
||||
self.login = login
|
||||
self.password = password
|
||||
|
||||
def prepare_url(self, path):
|
||||
return f"https://{self.host}{path}"
|
||||
|
||||
def get(self, path):
|
||||
response = requests.get(
|
||||
self.prepare_url(path),
|
||||
auth=(self.login, self.password),
|
||||
timeout=0.5,
|
||||
# # as today I'm using the opnsense selfsigned certificat
|
||||
# # but we should avoid this instead trust any certificat
|
||||
verify=False,
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
def get_interface_vip_status(self):
|
||||
try:
|
||||
data = self.get("/api/diagnostics/interface/get_vip_status/")
|
||||
except RequestException as ex:
|
||||
logger.error(
|
||||
"Get VIP STATUS on %s failed with the following error %r", self.host, ex
|
||||
)
|
||||
return "unavailable"
|
||||
if data["carp"]["maintenancemode"]:
|
||||
return "maintenancemode"
|
||||
is_active = all([row["status"] == "MASTER" for row in data["rows"]])
|
||||
if is_active:
|
||||
return "active"
|
||||
is_backup = all([row["status"] == "BACKUP" for row in data["rows"]])
|
||||
if is_backup:
|
||||
return "hot_standby"
|
||||
logger.warning(
|
||||
"this host %s is no active nor backup received payload %s", self.host, data
|
||||
)
|
||||
return "unavailable"
|
||||
|
||||
def get_wan_trafic(self):
|
||||
try:
|
||||
data = self.get("/api/diagnostics/traffic/interface")
|
||||
except RequestException as ex:
|
||||
logger.error(
|
||||
"Get diagnostics traffic on WAN interface for %s host failed with the following error %r",
|
||||
self.host,
|
||||
ex,
|
||||
)
|
||||
return None, None
|
||||
return (
|
||||
int(data["interfaces"]["wan"]["bytes received"]),
|
||||
int(data["interfaces"]["wan"]["bytes transmitted"]),
|
||||
)
|
||||
|
||||
|
||||
# def get_server_system_status(self):
|
||||
# # https://192.168.200.1/api/core/system/status
|
||||
# return {
|
||||
# "CrashReporter":
|
||||
# {
|
||||
# "statusCode":2,
|
||||
# "message":"No problems were detected.",
|
||||
# "logLocation":"/crash_reporter.php",
|
||||
# "timestamp":"0",
|
||||
# "status":"OK"
|
||||
# },
|
||||
# "Firewall":{
|
||||
# "statusCode":2,
|
||||
# "message":"No problems were detected.",
|
||||
# "logLocation":"/ui/diagnostics/log/core/firewall",
|
||||
# "timestamp":"0",
|
||||
# "status":"OK"
|
||||
# },
|
||||
# "System":{"status":"OK"}
|
||||
# }
|
118
opnsense_exporter/server.py
Normal file
118
opnsense_exporter/server.py
Normal file
|
@ -0,0 +1,118 @@
|
|||
import argparse
|
||||
import os
|
||||
import socket
|
||||
import time
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from prometheus_client import Enum, Gauge, start_http_server
|
||||
|
||||
from opnsense_exporter.opnsense_api import OPNSenseAPI
|
||||
|
||||
load_dotenv()
|
||||
|
||||
HA_STATES = ["active", "hot_standby", "unavailable", "maintenancemode"]
|
||||
main_ha_state = Enum(
|
||||
"opnsense_main_ha_state", "OPNSense HA state of the MAIN server", states=HA_STATES
|
||||
)
|
||||
backup_ha_state = Enum(
|
||||
"opnsense_backup_ha_state",
|
||||
"OPNSense HA state of the BACKUP server",
|
||||
states=HA_STATES,
|
||||
)
|
||||
active_server_bytes_received = Gauge(
|
||||
"opnsense_active_server_bytes_received",
|
||||
"Active OPNSense server bytes received on WAN interface",
|
||||
)
|
||||
active_server_bytes_transmitted = Gauge(
|
||||
"opnsense_active_server_bytes_transmitted",
|
||||
"Active OPNSense server bytes transmitted on WAN interface",
|
||||
)
|
||||
|
||||
|
||||
def process_requests(main, backup):
|
||||
"""A dummy function that takes some time."""
|
||||
main_state = main.get_interface_vip_status()
|
||||
backup_sate = backup.get_interface_vip_status()
|
||||
main_ha_state.state(main_state)
|
||||
backup_ha_state.state(backup_sate)
|
||||
bytes_received = None
|
||||
bytes_transmitted = None
|
||||
if main_state == "active":
|
||||
bytes_received, bytes_transmitted = main.get_wan_trafic()
|
||||
if backup_sate == "active":
|
||||
bytes_received, bytes_transmitted = backup.get_wan_trafic()
|
||||
active_server_bytes_received.set(bytes_received or -1)
|
||||
active_server_bytes_transmitted.set(bytes_transmitted or -1)
|
||||
|
||||
|
||||
def start_server(main: OPNSenseAPI, backup: OPNSenseAPI, check_frequency: int = 1):
|
||||
# Start up the server to expose the metrics.
|
||||
start_http_server(8000)
|
||||
# Generate some requests.
|
||||
while True:
|
||||
process_requests(main, backup)
|
||||
time.sleep(check_frequency)
|
||||
|
||||
|
||||
def run():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="OPNSense prometheus exporter",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--check-frequency-seconds",
|
||||
"-c",
|
||||
type=int,
|
||||
dest="frequency",
|
||||
default=int(os.environ.get("CHECK_FREQUENCY_SECONDS", 2)),
|
||||
help="How often (in seconds) this server requests OPNSense servers",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--main-host",
|
||||
"-m",
|
||||
type=str,
|
||||
dest="main",
|
||||
default=os.environ.get("OPNSENSE_MAIN_HOST", None),
|
||||
help="MAIN OPNsense server that should be in `active` state in normal configuration.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--backup-host",
|
||||
"-b",
|
||||
type=str,
|
||||
dest="backup",
|
||||
default=os.environ.get("OPNSENSE_BACKUP_HOST", None),
|
||||
help="BACKUP OPNsense server that should be `hot_standby` state in normal configuration.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--opnsense-user",
|
||||
"-u",
|
||||
type=str,
|
||||
dest="user",
|
||||
default=os.environ.get("OPNSENSE_USERNAME", None),
|
||||
help="OPNsense user. Expect to be the same on MAIN and BACKUP servers",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--opnsense-password",
|
||||
"-p",
|
||||
type=str,
|
||||
dest="password",
|
||||
default=os.environ.get("OPNSENSE_PASSWORD", None),
|
||||
help="OPNsense password. Expect to be the same on MAIN and BACKUP servers",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--prometheus-instance",
|
||||
dest="prom_instance",
|
||||
type=str,
|
||||
default=socket.gethostname(),
|
||||
help=(
|
||||
"Instance name, default value computed with hostname "
|
||||
"where the server is running. Use to set the instance label."
|
||||
),
|
||||
)
|
||||
|
||||
arguments = parser.parse_args()
|
||||
start_server(
|
||||
OPNSenseAPI(arguments.main, arguments.user, arguments.password),
|
||||
OPNSenseAPI(arguments.backup, arguments.user, arguments.password),
|
||||
check_frequency=arguments.frequency,
|
||||
)
|
3
requirements.tests.txt
Normal file
3
requirements.tests.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
pytest
|
||||
pytest-cov
|
||||
responses
|
3
requirements.txt
Normal file
3
requirements.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
prometheus-client
|
||||
python-dotenv
|
||||
requests
|
25
setup.cfg
Normal file
25
setup.cfg
Normal file
|
@ -0,0 +1,25 @@
|
|||
[isort]
|
||||
profile = black
|
||||
|
||||
[flake8]
|
||||
max-line-length = 92
|
||||
exclude = log/*,doc/*,*.egg-info
|
||||
max-complexity = 12
|
||||
ignore =
|
||||
# line length is handled by black
|
||||
E501
|
||||
# line break before binary operator (black move the line breaker before)
|
||||
W503
|
||||
per-file-ignores =
|
||||
# tests doesn't require doctrings
|
||||
test_*: D103, W605
|
||||
# empty init doesn't need a docstring
|
||||
# ignore unused imported in init files
|
||||
__init__.py:
|
||||
D104
|
||||
F401
|
||||
|
||||
[tool:pytest]
|
||||
addopts = -v -s --junit-xml junit.xml --cov ./opnsense_exporter/ --cov-report term --cov-report xml --cov-report html
|
||||
testpaths =
|
||||
tests
|
39
setup.py
Normal file
39
setup.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
from urllib.parse import urlparse
|
||||
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
version = "0.1"
|
||||
|
||||
|
||||
def parse_requirements(file):
|
||||
required = []
|
||||
with open(file) as f:
|
||||
for req in f.read().splitlines():
|
||||
if req.strip().startswith("git"):
|
||||
req = urlparse(req.strip()).fragment.split("=")[1]
|
||||
if req.strip().startswith("-e"):
|
||||
req = urlparse(req.strip().split()[1]).fragment.split("=")[1]
|
||||
if not req.strip().startswith("#") and not req.strip().startswith("--"):
|
||||
required.append(req)
|
||||
return required
|
||||
|
||||
|
||||
requires = parse_requirements("requirements.txt")
|
||||
tests_requires = parse_requirements("requirements.tests.txt")
|
||||
|
||||
setup(
|
||||
name="opnsense-prom-exporter",
|
||||
version=version,
|
||||
description="OPNSense Prometheus exporter",
|
||||
author="Pierre Verkest",
|
||||
author_email="pierreverkest84@gmail.com",
|
||||
license="GNU GPL v3",
|
||||
namespace_packages=["opnsense_exporter"],
|
||||
packages=find_packages(exclude=["ez_setup", "examples", "tests"]),
|
||||
install_requires=requires,
|
||||
tests_require=requires + tests_requires,
|
||||
entry_points="""
|
||||
[console_scripts]
|
||||
opnsense-exporter=opnsense_exporter.server:run
|
||||
""",
|
||||
)
|
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
149
tests/common.py
Normal file
149
tests/common.py
Normal file
|
@ -0,0 +1,149 @@
|
|||
import json
|
||||
|
||||
MAIN_HOST = "192.168.1.1"
|
||||
BACKUP_HOST = "192.168.1.2"
|
||||
LOGIN = "user"
|
||||
PASSWORD = "pwd"
|
||||
|
||||
|
||||
def generate_get_vip_status_paylaod(state_wan, state_lan, maintenance_mode):
|
||||
return json.dumps(
|
||||
{
|
||||
"total": 2,
|
||||
"rowCount": 2,
|
||||
"current": 1,
|
||||
"rows": [
|
||||
{
|
||||
"interface": "wan",
|
||||
"vhid": "1",
|
||||
"advbase": "1",
|
||||
"advskew": "0",
|
||||
"subnet": "176.149.171.241",
|
||||
"status": state_wan,
|
||||
"mode": "carp",
|
||||
"status_txt": state_wan,
|
||||
"vhid_txt": "1 (freq. 1/0)",
|
||||
},
|
||||
{
|
||||
"interface": "lan",
|
||||
"vhid": "3",
|
||||
"advbase": "1",
|
||||
"advskew": "0",
|
||||
"subnet": "192.168.200.1",
|
||||
"status": state_lan,
|
||||
"mode": "carp",
|
||||
"status_txt": state_lan,
|
||||
"vhid_txt": "3 (freq. 1/0)",
|
||||
},
|
||||
],
|
||||
"carp": {
|
||||
"demotion": "0",
|
||||
"allow": "1",
|
||||
"maintenancemode": maintenance_mode,
|
||||
"status_msg": "",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def generate_diagnostics_traffic_interface_paylaod():
|
||||
return json.dumps(
|
||||
{
|
||||
"interfaces": {
|
||||
"lan": {
|
||||
"index": "2",
|
||||
"flags": "8963",
|
||||
"promiscuous listeners": "1",
|
||||
"send queue length": "0",
|
||||
"send queue max length": "50",
|
||||
"send queue drops": "0",
|
||||
"type": "Ethernet",
|
||||
"address length": "6",
|
||||
"header length": "18",
|
||||
"link state": "2",
|
||||
"vhid": "0",
|
||||
"datalen": "152",
|
||||
"mtu": "1500",
|
||||
"metric": "0",
|
||||
"line rate": "10000000000 bit/s",
|
||||
"packets received": "3699327747",
|
||||
"input errors": "0",
|
||||
"packets transmitted": "8972963403",
|
||||
"output errors": "0",
|
||||
"collisions": "0",
|
||||
"bytes received": "2474843996609",
|
||||
"bytes transmitted": "11711737078752",
|
||||
"multicasts received": "29274204",
|
||||
"multicasts transmitted": "0",
|
||||
"input queue drops": "0",
|
||||
"packets for unknown protocol": "0",
|
||||
"HW offload capabilities": "0x0",
|
||||
"uptime at attach or stat reset": "1",
|
||||
"name": "LAN",
|
||||
},
|
||||
"opt1": {
|
||||
"index": "3",
|
||||
"flags": "8863",
|
||||
"promiscuous listeners": "0",
|
||||
"send queue length": "0",
|
||||
"send queue max length": "50",
|
||||
"send queue drops": "0",
|
||||
"type": "Ethernet",
|
||||
"address length": "6",
|
||||
"header length": "18",
|
||||
"link state": "2",
|
||||
"vhid": "0",
|
||||
"datalen": "152",
|
||||
"mtu": "1500",
|
||||
"metric": "0",
|
||||
"line rate": "10000000000 bit/s",
|
||||
"packets received": "1120457",
|
||||
"input errors": "0",
|
||||
"packets transmitted": "178007891",
|
||||
"output errors": "0",
|
||||
"collisions": "0",
|
||||
"bytes received": "221674964",
|
||||
"bytes transmitted": "55808795987",
|
||||
"multicasts received": "393767",
|
||||
"multicasts transmitted": "0",
|
||||
"input queue drops": "0",
|
||||
"packets for unknown protocol": "0",
|
||||
"HW offload capabilities": "0x0",
|
||||
"uptime at attach or stat reset": "1",
|
||||
"name": "LANOPNSYNC",
|
||||
},
|
||||
"wan": {
|
||||
"index": "1",
|
||||
"flags": "8963",
|
||||
"promiscuous listeners": "1",
|
||||
"send queue length": "0",
|
||||
"send queue max length": "50",
|
||||
"send queue drops": "0",
|
||||
"type": "Ethernet",
|
||||
"address length": "6",
|
||||
"header length": "18",
|
||||
"link state": "2",
|
||||
"vhid": "0",
|
||||
"datalen": "152",
|
||||
"mtu": "1500",
|
||||
"metric": "0",
|
||||
"line rate": "10000000000 bit/s",
|
||||
"packets received": "9008014155",
|
||||
"input errors": "0",
|
||||
"packets transmitted": "3724630846",
|
||||
"output errors": "0",
|
||||
"collisions": "0",
|
||||
"bytes received": "11725192686820",
|
||||
"bytes transmitted": "2489262014203",
|
||||
"multicasts received": "2288794",
|
||||
"multicasts transmitted": "0",
|
||||
"input queue drops": "0",
|
||||
"packets for unknown protocol": "0",
|
||||
"HW offload capabilities": "0x0",
|
||||
"uptime at attach or stat reset": "1",
|
||||
"name": "WAN",
|
||||
},
|
||||
},
|
||||
"time": 1693404742.796736,
|
||||
}
|
||||
)
|
106
tests/test_opnsense_api.py
Normal file
106
tests/test_opnsense_api.py
Normal file
|
@ -0,0 +1,106 @@
|
|||
import responses
|
||||
|
||||
from opnsense_exporter.opnsense_api import OPNSenseAPI
|
||||
|
||||
from .common import (
|
||||
LOGIN,
|
||||
MAIN_HOST,
|
||||
PASSWORD,
|
||||
generate_diagnostics_traffic_interface_paylaod,
|
||||
generate_get_vip_status_paylaod,
|
||||
)
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_get_interface_vip_status_active():
|
||||
responses.add(
|
||||
responses.GET,
|
||||
f"https://{MAIN_HOST}/api/diagnostics/interface/get_vip_status/",
|
||||
body=generate_get_vip_status_paylaod("MASTER", "MASTER", False),
|
||||
)
|
||||
|
||||
assert (
|
||||
OPNSenseAPI(MAIN_HOST, LOGIN, PASSWORD).get_interface_vip_status() == "active"
|
||||
)
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_get_interface_vip_status_backup():
|
||||
responses.add(
|
||||
responses.GET,
|
||||
f"https://{MAIN_HOST}/api/diagnostics/interface/get_vip_status/",
|
||||
body=generate_get_vip_status_paylaod("BACKUP", "BACKUP", False),
|
||||
)
|
||||
|
||||
assert (
|
||||
OPNSenseAPI(MAIN_HOST, LOGIN, PASSWORD).get_interface_vip_status()
|
||||
== "hot_standby"
|
||||
)
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_get_interface_vip_status_mainteance_mode():
|
||||
responses.add(
|
||||
responses.GET,
|
||||
f"https://{MAIN_HOST}/api/diagnostics/interface/get_vip_status/",
|
||||
body=generate_get_vip_status_paylaod("MASTER", "MASTER", True),
|
||||
)
|
||||
|
||||
assert (
|
||||
OPNSenseAPI(MAIN_HOST, LOGIN, PASSWORD).get_interface_vip_status()
|
||||
== "maintenancemode"
|
||||
)
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_get_interface_vip_status_unavailable_weird_case():
|
||||
responses.add(
|
||||
responses.GET,
|
||||
f"https://{MAIN_HOST}/api/diagnostics/interface/get_vip_status/",
|
||||
body=generate_get_vip_status_paylaod("MASTER", "BACKUP", False),
|
||||
)
|
||||
assert (
|
||||
OPNSenseAPI(MAIN_HOST, LOGIN, PASSWORD).get_interface_vip_status()
|
||||
== "unavailable"
|
||||
)
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_get_interface_vip_status_unavailable_rest_api_error():
|
||||
responses.add(
|
||||
responses.GET,
|
||||
f"https://{MAIN_HOST}/api/diagnostics/interface/get_vip_status/",
|
||||
json={"error": "not found"},
|
||||
status=404,
|
||||
)
|
||||
assert (
|
||||
OPNSenseAPI(MAIN_HOST, LOGIN, PASSWORD).get_interface_vip_status()
|
||||
== "unavailable"
|
||||
)
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_get_wan_traffic():
|
||||
responses.add(
|
||||
responses.GET,
|
||||
f"https://{MAIN_HOST}/api/diagnostics/traffic/interface",
|
||||
body=generate_diagnostics_traffic_interface_paylaod(),
|
||||
)
|
||||
assert OPNSenseAPI(MAIN_HOST, LOGIN, PASSWORD).get_wan_trafic() == (
|
||||
11725192686820,
|
||||
2489262014203,
|
||||
)
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_get_wan_traffic_none():
|
||||
responses.add(
|
||||
responses.GET,
|
||||
f"https://{MAIN_HOST}/api/diagnostics/traffic/interface",
|
||||
json={"error": "not found"},
|
||||
status=404,
|
||||
)
|
||||
assert OPNSenseAPI(MAIN_HOST, LOGIN, PASSWORD).get_wan_trafic() == (
|
||||
None,
|
||||
None,
|
||||
)
|
165
tests/test_server.py
Normal file
165
tests/test_server.py
Normal file
|
@ -0,0 +1,165 @@
|
|||
from unittest import mock
|
||||
|
||||
import responses
|
||||
|
||||
from opnsense_exporter.opnsense_api import OPNSenseAPI
|
||||
from opnsense_exporter.server import process_requests, run
|
||||
|
||||
from .common import (
|
||||
BACKUP_HOST,
|
||||
LOGIN,
|
||||
MAIN_HOST,
|
||||
PASSWORD,
|
||||
generate_diagnostics_traffic_interface_paylaod,
|
||||
generate_get_vip_status_paylaod,
|
||||
)
|
||||
|
||||
|
||||
@mock.patch("opnsense_exporter.server.start_server")
|
||||
def test_parser(server_mock):
|
||||
with mock.patch(
|
||||
"sys.argv",
|
||||
[
|
||||
"opnsense-exporter",
|
||||
"-c",
|
||||
"15",
|
||||
"-m",
|
||||
"main.host",
|
||||
"-b",
|
||||
"backup.host",
|
||||
"-u",
|
||||
"user-test",
|
||||
"-p",
|
||||
"pwd-test",
|
||||
"--prometheus-instance",
|
||||
"server-hostname-instance",
|
||||
],
|
||||
):
|
||||
run()
|
||||
server_mock.assert_called_once()
|
||||
main, bck = server_mock.call_args.args
|
||||
assert main.login == "user-test"
|
||||
assert bck.login == "user-test"
|
||||
assert main.password == "pwd-test"
|
||||
assert bck.password == "pwd-test"
|
||||
assert main.host == "main.host"
|
||||
assert bck.host == "backup.host"
|
||||
assert server_mock.call_args.kwargs["check_frequency"] == 15
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_process_requests():
|
||||
responses.add(
|
||||
responses.GET,
|
||||
f"https://{MAIN_HOST}/api/diagnostics/interface/get_vip_status/",
|
||||
body=generate_get_vip_status_paylaod("MASTER", "MASTER", False),
|
||||
)
|
||||
responses.add(
|
||||
responses.GET,
|
||||
f"https://{BACKUP_HOST}/api/diagnostics/interface/get_vip_status/",
|
||||
body=generate_get_vip_status_paylaod("BACKUP", "BACKUP", False),
|
||||
)
|
||||
responses.add(
|
||||
responses.GET,
|
||||
f"https://{MAIN_HOST}/api/diagnostics/traffic/interface",
|
||||
body=generate_diagnostics_traffic_interface_paylaod(),
|
||||
)
|
||||
with mock.patch(
|
||||
"opnsense_exporter.server.main_ha_state.state"
|
||||
) as main_ha_state_mock:
|
||||
with mock.patch(
|
||||
"opnsense_exporter.server.backup_ha_state.state"
|
||||
) as backup_ha_state_mock:
|
||||
with mock.patch(
|
||||
"opnsense_exporter.server.active_server_bytes_received.set"
|
||||
) as active_server_bytes_received_mock:
|
||||
with mock.patch(
|
||||
"opnsense_exporter.server.active_server_bytes_transmitted.set"
|
||||
) as active_server_bytes_transmitted_mock:
|
||||
process_requests(
|
||||
OPNSenseAPI(MAIN_HOST, LOGIN, PASSWORD),
|
||||
OPNSenseAPI(BACKUP_HOST, LOGIN, PASSWORD),
|
||||
)
|
||||
main_ha_state_mock.assert_called_once_with("active")
|
||||
backup_ha_state_mock.assert_called_once_with("hot_standby")
|
||||
active_server_bytes_received_mock.assert_called_once_with(11725192686820)
|
||||
active_server_bytes_transmitted_mock.assert_called_once_with(2489262014203)
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_process_requests_backend_active():
|
||||
responses.add(
|
||||
responses.GET,
|
||||
f"https://{MAIN_HOST}/api/diagnostics/interface/get_vip_status/",
|
||||
body=generate_get_vip_status_paylaod("MASTER", "MASTER", True),
|
||||
)
|
||||
responses.add(
|
||||
responses.GET,
|
||||
f"https://{BACKUP_HOST}/api/diagnostics/interface/get_vip_status/",
|
||||
body=generate_get_vip_status_paylaod("MASTER", "MASTER", False),
|
||||
)
|
||||
responses.add(
|
||||
responses.GET,
|
||||
f"https://{BACKUP_HOST}/api/diagnostics/traffic/interface",
|
||||
body=generate_diagnostics_traffic_interface_paylaod(),
|
||||
)
|
||||
with mock.patch(
|
||||
"opnsense_exporter.server.main_ha_state.state"
|
||||
) as main_ha_state_mock:
|
||||
with mock.patch(
|
||||
"opnsense_exporter.server.backup_ha_state.state"
|
||||
) as backup_ha_state_mock:
|
||||
with mock.patch(
|
||||
"opnsense_exporter.server.active_server_bytes_received.set"
|
||||
) as active_server_bytes_received_mock:
|
||||
with mock.patch(
|
||||
"opnsense_exporter.server.active_server_bytes_transmitted.set"
|
||||
) as active_server_bytes_transmitted_mock:
|
||||
process_requests(
|
||||
OPNSenseAPI(MAIN_HOST, LOGIN, PASSWORD),
|
||||
OPNSenseAPI(BACKUP_HOST, LOGIN, PASSWORD),
|
||||
)
|
||||
main_ha_state_mock.assert_called_once_with("maintenancemode")
|
||||
backup_ha_state_mock.assert_called_once_with("active")
|
||||
active_server_bytes_received_mock.assert_called_once_with(11725192686820)
|
||||
active_server_bytes_transmitted_mock.assert_called_once_with(2489262014203)
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_process_no_active():
|
||||
responses.add(
|
||||
responses.GET,
|
||||
f"https://{MAIN_HOST}/api/diagnostics/interface/get_vip_status/",
|
||||
body=generate_get_vip_status_paylaod("MASTER", "MASTER", True),
|
||||
)
|
||||
responses.add(
|
||||
responses.GET,
|
||||
f"https://{BACKUP_HOST}/api/diagnostics/interface/get_vip_status/",
|
||||
body=generate_get_vip_status_paylaod("MASTER", "MASTER", True),
|
||||
status=404,
|
||||
)
|
||||
responses.add(
|
||||
responses.GET,
|
||||
f"https://{BACKUP_HOST}/api/diagnostics/traffic/interface",
|
||||
body=generate_diagnostics_traffic_interface_paylaod(),
|
||||
)
|
||||
with mock.patch(
|
||||
"opnsense_exporter.server.main_ha_state.state"
|
||||
) as main_ha_state_mock:
|
||||
with mock.patch(
|
||||
"opnsense_exporter.server.backup_ha_state.state"
|
||||
) as backup_ha_state_mock:
|
||||
with mock.patch(
|
||||
"opnsense_exporter.server.active_server_bytes_received.set"
|
||||
) as active_server_bytes_received_mock:
|
||||
with mock.patch(
|
||||
"opnsense_exporter.server.active_server_bytes_transmitted.set"
|
||||
) as active_server_bytes_transmitted_mock:
|
||||
process_requests(
|
||||
OPNSenseAPI(MAIN_HOST, LOGIN, PASSWORD),
|
||||
OPNSenseAPI(BACKUP_HOST, LOGIN, PASSWORD),
|
||||
)
|
||||
main_ha_state_mock.assert_called_once_with("maintenancemode")
|
||||
backup_ha_state_mock.assert_called_once_with("unavailable")
|
||||
active_server_bytes_received_mock.assert_called_once_with(-1)
|
||||
active_server_bytes_transmitted_mock.assert_called_once_with(-1)
|
Loading…
Reference in a new issue