feat: declare the SNMP health OIDs instead of collecting them (0.2.0) #10

Merged
christianmanivong merged 1 commits from feat/snmp-health-profile into master 2026-10-16 23:06:59 +00:00
3 changed files with 48 additions and 75 deletions

No files matched your search

+14 -74
View File
@@ -30,7 +30,7 @@ CLI notes
import re import re
import socket import socket
import time import time
from typing import Any, Dict, List, Optional, Union from typing import Any, ClassVar, Dict, List, Optional, Union
import netaddr import netaddr
from netmiko import ConnectHandler from netmiko import ConnectHandler
@@ -69,6 +69,19 @@ class NetgearSmartDriver(ConfigLifecycleMixin, SwitchDriver):
# Netmiko device type – handles prompt ``(hostname) >`` / ``(hostname) #`` # Netmiko device type – handles prompt ``(hostname) >`` / ``(hostname) #``
NETMIKO_DEVICE_TYPE = "netgear_prosafe" NETMIKO_DEVICE_TYPE = "netgear_prosafe"
#: Where the switch publishes its health values over SNMP
#: (napalm-device-types ``SnmpHealthProfile``); the application polls.
#: CPU is text ("5 Secs ( 3.2%) 60 Secs ( …"); the five-second figure counts.
SNMP_HEALTH_PROFILE: ClassVar[Dict[str, Any]] = {
"uptime": {"oids": ["1.3.6.1.2.1.1.3.0"]},
"cpu": {"oids": ["1.3.6.1.4.1.4526.100.1.1.4.9.0"], "pattern": r"5\s+Secs\s*\(\s*([\d.]+)\s*%\)"},
"memory": {
"total": {"oids": ["1.3.6.1.4.1.4526.100.1.1.4.2.0"]},
"free": {"oids": ["1.3.6.1.4.1.4526.100.1.1.4.1.0"]},
"unit": "KiB",
},
}
# PoE priority mapping between the CLI's "low/high/critical" and the # PoE priority mapping between the CLI's "low/high/critical" and the
# generic priority strings used across drivers. # generic priority strings used across drivers.
_POE_PRIORITY_MAP = {"low": "PPP_LOW", "high": "PPP_HIGH", "critical": "PPP_CRITICAL"} _POE_PRIORITY_MAP = {"low": "PPP_LOW", "high": "PPP_HIGH", "critical": "PPP_CRITICAL"}
@@ -2040,76 +2053,3 @@ class NetgearSmartDriver(ConfigLifecycleMixin, SwitchDriver):
finally: finally:
self._exit_config_mode() self._exit_config_mode()
self._save_config() self._save_config()
# ------------------------------------------------------------------
# Health metrics (SNMP)
# ------------------------------------------------------------------
@classmethod
async def get_health_metrics(cls, snmp_get, snmp_walk) -> dict:
import asyncio
import re
from napalm_device_types._ucd_metrics import build_if_metrics, ticks_to_seconds
_OID_SNMP_UPTIME = "1.3.6.1.2.1.1.3.0"
_NETGEAR_MEM_FREE = "1.3.6.1.4.1.4526.100.1.1.4.1.0"
_NETGEAR_MEM_TOTAL = "1.3.6.1.4.1.4526.100.1.1.4.2.0"
_NETGEAR_CPU_STR = "1.3.6.1.4.1.4526.100.1.1.4.9.0"
_OID_IF_DESCR = "1.3.6.1.2.1.2.2.1.2"
_OID_IF_SPEED = "1.3.6.1.2.1.2.2.1.5"
_OID_IF_IN_OCT = "1.3.6.1.2.1.2.2.1.10"
_OID_IF_OUT_OCT = "1.3.6.1.2.1.2.2.1.16"
_OID_IF_IN_ERR = "1.3.6.1.2.1.2.2.1.14"
_OID_IF_OUT_ERR = "1.3.6.1.2.1.2.2.1.20"
(
uptime_raw,
free_s,
total_s,
cpu_s,
descr,
speed,
in_oct,
out_oct,
in_err,
out_err,
) = await asyncio.gather(
snmp_get(_OID_SNMP_UPTIME),
snmp_get(_NETGEAR_MEM_FREE),
snmp_get(_NETGEAR_MEM_TOTAL),
snmp_get(_NETGEAR_CPU_STR),
snmp_walk(_OID_IF_DESCR),
snmp_walk(_OID_IF_SPEED),
snmp_walk(_OID_IF_IN_OCT),
snmp_walk(_OID_IF_OUT_OCT),
snmp_walk(_OID_IF_IN_ERR),
snmp_walk(_OID_IF_OUT_ERR),
)
metrics: dict = {}
secs = ticks_to_seconds(uptime_raw)
if secs is not None:
metrics["uptime_seconds"] = secs
if free_s and total_s:
try:
free_kb = int(free_s)
total_kb = int(total_s)
used_kb = max(0, total_kb - free_kb)
metrics["memory_total_bytes"] = total_kb * 1024
metrics["memory_used_bytes"] = used_kb * 1024
metrics["memory_percent"] = round(used_kb / total_kb * 100, 1) if total_kb else 0.0
except ValueError:
pass
if cpu_s:
m = re.search(r"5\s+Secs\s*\(\s*([\d.]+)\s*%\)", cpu_s)
if m:
try:
metrics["cpu_percent"] = float(m.group(1))
except ValueError:
pass
build_if_metrics(metrics, descr, speed, in_oct, out_oct, in_err, out_err)
return metrics
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "napalm-netgear" name = "napalm-netgear"
version = "0.1.0" version = "0.2.0"
description = "NAPALM driver for Netgear switches (Plus/Smart)" description = "NAPALM driver for Netgear switches (Plus/Smart)"
readme = "README.md" readme = "README.md"
license = { text = "Apache-2.0" } license = { text = "Apache-2.0" }
+33
View File
@@ -0,0 +1,33 @@
"""The switch's SNMP health OIDs, declared as data (napalm-device-types docs/STRUCTURE.md).
The driver does not collect over SNMP; the application polls with this profile.
"""
import re
from napalm_netgear.netgear_smart import NetgearSmartDriver
PROFILE = NetgearSmartDriver.SNMP_HEALTH_PROFILE
def test_the_driver_does_not_collect_itself():
assert "get_health_metrics" not in vars(NetgearSmartDriver)
def test_uptime_from_the_standard_sysuptime():
assert PROFILE["uptime"] == {"oids": ["1.3.6.1.2.1.1.3.0"]}
def test_memory_from_total_and_free_in_kib():
assert PROFILE["memory"] == {
"total": {"oids": ["1.3.6.1.4.1.4526.100.1.1.4.2.0"]},
"free": {"oids": ["1.3.6.1.4.1.4526.100.1.1.4.1.0"]},
"unit": "KiB",
}
def test_cpu_is_the_five_second_figure_in_a_text_value():
cpu = PROFILE["cpu"]
assert cpu["oids"] == ["1.3.6.1.4.1.4526.100.1.1.4.9.0"]
text = " 5 Secs ( 3.2%) 60 Secs ( 4.1%) 300 Secs ( 2.0%)"
assert re.search(cpu["pattern"], text).group(1) == "3.2"