2023-09-01 13:27:55 +02:00
|
|
|
import logging
|
2023-09-03 21:52:19 +02:00
|
|
|
from enum import Enum
|
2023-09-01 13:27:55 +02:00
|
|
|
|
|
|
|
import requests
|
|
|
|
from requests import RequestException
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2023-09-04 00:06:17 +02:00
|
|
|
class OPNSenseHAState(Enum):
|
|
|
|
ACTIVE = "active"
|
|
|
|
HOT_STANDBY = "hot_standby"
|
|
|
|
UNAVAILABLE = "unavailable"
|
|
|
|
MAINTENANCE_MODE = "maintenancemode"
|
|
|
|
|
|
|
|
|
2023-09-03 23:38:34 +02:00
|
|
|
class OPNSenseTrafficMetric(Enum):
|
|
|
|
IN = "rate_bits_in"
|
|
|
|
OUT = "rate_bits_out"
|
|
|
|
|
|
|
|
|
|
|
|
class OPNSenseTraffic:
|
|
|
|
interface: str = None
|
|
|
|
metric: OPNSenseTrafficMetric = None
|
|
|
|
value: int = 0
|
|
|
|
|
2023-09-06 10:39:54 +02:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
interface: str,
|
|
|
|
metric: OPNSenseTrafficMetric,
|
|
|
|
value: int = 0,
|
|
|
|
):
|
2023-09-03 23:38:34 +02:00
|
|
|
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}"
|
|
|
|
|
|
|
|
|
2023-09-03 21:52:19 +02:00
|
|
|
class OPNSenseRole(Enum):
|
|
|
|
MAIN = "main"
|
|
|
|
BACKUP = "backup"
|
|
|
|
|
|
|
|
|
2023-09-01 13:27:55 +02:00
|
|
|
class OPNSenseAPI:
|
|
|
|
host: str = None
|
|
|
|
login: str = None
|
|
|
|
password: str = None
|
2023-09-03 21:52:19 +02:00
|
|
|
role: OPNSenseRole = None
|
2023-09-06 10:39:54 +02:00
|
|
|
get_vip_status_timeout_sec: int = 5
|
|
|
|
get_traffic_timeout_sec: int = 15
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
role,
|
|
|
|
host,
|
|
|
|
login,
|
|
|
|
password,
|
|
|
|
get_vip_status_timeout_sec: int = 5,
|
|
|
|
get_traffic_timeout_sec: int = 5,
|
|
|
|
):
|
2023-09-03 21:52:19 +02:00
|
|
|
self.role = role
|
2023-09-01 13:27:55 +02:00
|
|
|
self.host = host
|
|
|
|
self.login = login
|
|
|
|
self.password = password
|
2023-09-06 10:39:54 +02:00
|
|
|
self.get_vip_status_timeout_sec = get_vip_status_timeout_sec
|
|
|
|
self.get_traffic_timeout_sec = get_traffic_timeout_sec
|
2023-09-01 13:27:55 +02:00
|
|
|
|
2023-09-03 21:52:19 +02:00
|
|
|
@property
|
|
|
|
def labels(self):
|
|
|
|
return {
|
|
|
|
"host": self.host,
|
|
|
|
"role": self.role.value,
|
|
|
|
}
|
|
|
|
|
2023-09-01 13:27:55 +02:00
|
|
|
def prepare_url(self, path):
|
|
|
|
return f"https://{self.host}{path}"
|
|
|
|
|
2023-09-02 02:35:02 +02:00
|
|
|
def get(self, path, timeout=2):
|
2023-09-01 13:27:55 +02:00
|
|
|
response = requests.get(
|
|
|
|
self.prepare_url(path),
|
|
|
|
auth=(self.login, self.password),
|
2023-09-02 02:35:02 +02:00
|
|
|
timeout=timeout,
|
2023-09-01 13:27:55 +02:00
|
|
|
# # 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()
|
|
|
|
|
2023-09-04 00:06:17 +02:00
|
|
|
def get_interface_vip_status(self) -> OPNSenseHAState:
|
2023-09-01 13:27:55 +02:00
|
|
|
try:
|
2023-09-06 10:39:54 +02:00
|
|
|
data = self.get(
|
|
|
|
"/api/diagnostics/interface/get_vip_status/",
|
|
|
|
timeout=self.get_vip_status_timeout_sec,
|
|
|
|
)
|
2023-09-01 13:27:55 +02:00
|
|
|
except RequestException as ex:
|
|
|
|
logger.error(
|
|
|
|
"Get VIP STATUS on %s failed with the following error %r", self.host, ex
|
|
|
|
)
|
2023-09-04 00:06:17 +02:00
|
|
|
return OPNSenseHAState.UNAVAILABLE
|
2023-09-01 13:27:55 +02:00
|
|
|
if data["carp"]["maintenancemode"]:
|
2023-09-04 00:06:17 +02:00
|
|
|
return OPNSenseHAState.MAINTENANCE_MODE
|
2023-09-01 13:27:55 +02:00
|
|
|
is_active = all([row["status"] == "MASTER" for row in data["rows"]])
|
|
|
|
if is_active:
|
2023-09-04 00:06:17 +02:00
|
|
|
return OPNSenseHAState.ACTIVE
|
2023-09-01 13:27:55 +02:00
|
|
|
is_backup = all([row["status"] == "BACKUP" for row in data["rows"]])
|
|
|
|
if is_backup:
|
2023-09-04 00:06:17 +02:00
|
|
|
return OPNSenseHAState.HOT_STANDBY
|
2023-09-01 13:27:55 +02:00
|
|
|
logger.warning(
|
|
|
|
"this host %s is no active nor backup received payload %s", self.host, data
|
|
|
|
)
|
2023-09-04 00:06:17 +02:00
|
|
|
return OPNSenseHAState.UNAVAILABLE
|
2023-09-01 13:27:55 +02:00
|
|
|
|
2023-09-03 23:38:34 +02:00
|
|
|
def get_traffic(self, interfaces):
|
2023-09-06 09:09:41 +02:00
|
|
|
if not interfaces:
|
|
|
|
return []
|
2023-09-01 13:27:55 +02:00
|
|
|
try:
|
2023-09-06 10:39:54 +02:00
|
|
|
data = self.get(
|
|
|
|
f"/api/diagnostics/traffic/top/{interfaces}",
|
|
|
|
timeout=self.get_traffic_timeout_sec,
|
|
|
|
)
|
2023-09-01 13:27:55 +02:00
|
|
|
except RequestException as ex:
|
|
|
|
logger.error(
|
2023-09-03 23:38:34 +02:00
|
|
|
"Get diagnostics traffic on %s interface(s) for %s host failed with the following error %r",
|
|
|
|
interfaces,
|
2023-09-01 13:27:55 +02:00
|
|
|
self.host,
|
|
|
|
ex,
|
|
|
|
)
|
2023-09-03 23:38:34 +02:00
|
|
|
return []
|
|
|
|
traffics = []
|
|
|
|
for interface in interfaces.split(","):
|
|
|
|
traffic_in = OPNSenseTraffic(interface, OPNSenseTrafficMetric.IN)
|
|
|
|
traffic_out = OPNSenseTraffic(interface, OPNSenseTrafficMetric.OUT)
|
2023-09-06 09:09:41 +02:00
|
|
|
for record in data.get(interface, {}).get("records", []):
|
2023-09-03 23:38:34 +02:00
|
|
|
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
|