feat: declare the SNMP health OIDs instead of collecting them (0.2.0)
CI / test (3.10) (push) Successful in 32s
CI / test (3.11) (push) Successful in 32s
CI / test (3.12) (push) Successful in 33s
CI / test (3.10) (pull_request) Successful in 32s
CI / test (3.11) (pull_request) Successful in 29s
CI / test (3.12) (pull_request) Successful in 33s

netgear_smart no longer collects over SNMP. SNMP_HEALTH_PROFILE states
where the switch publishes uptime (standard sysUpTime), memory (total
and free in KiB) and CPU (a text value whose five-second figure
counts); the application polls and computes (napalm-device-types
docs/STRUCTURE.md).

Interface counters now come from the 64-bit ifXTable where the switch
has it, as the application reads them for every device.
This commit is contained in:
Christian Manivong committed 2026-10-16 12:03:00 +02:00
1 parent fc6219cd59
commit 6b305c3d47
3 files changed
+48 -75

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"