fix: IPMI polls after the first failed; BMC drivers collect SNMP health (0.2.1)

The second IPMI poll of a BMC in the same worker process failed with
"'<' not supported between instances of 'float' and 'NoneType'".
pyghmi files a session in Session.initting_sessions under the
credentials as str and deletes it only under bytes, so the entry stays
and the next Command got the old, logged-out session back. The driver
drops that entry itself before open() and after close(). Reproduced and
verified live against the Supermicro X10DRL-i: three polls in one
process now succeed.

IpmiDriver and RedfishDriver mix in HealthMetricsMixin, so netOrk's
SNMP health poll reads the BMC's own load, memory and interfaces where
the BMC runs net-snmp with the UCD-MIB, as the X10's does.
This commit is contained in:
christianmanivong committed 2026-10-13 11:24:29 +02:00
1 parent 332e6090a7
commit 0e38e18e26
6 files changed
+81 -10

No files matched your search

+1 -1
View File
@@ -7,5 +7,5 @@ takes the first driver class of this package. ``IpmiDriver`` lives in
from napalm_redfish.driver import RedfishDriver
__version__ = "0.2.0"
__version__ = "0.2.1"
__all__ = ["RedfishDriver"]
+7 -3
View File
@@ -30,7 +30,7 @@ from datetime import datetime, timezone
from typing import Any
from napalm.base.exceptions import ConnectionException
from napalm_device_types import ManagementControllerDriver
from napalm_device_types import HealthMetricsMixin, ManagementControllerDriver
from napalm_device_types.base import FingerprintRule, PortSpec
from napalm_device_types.models import POWER_ACTIONS
@@ -109,8 +109,12 @@ def _status(item: _JsonDict) -> str:
return item.get("health") or ""
class RedfishDriver(ManagementControllerDriver):
"""NAPALM driver for Redfish BMCs (Supermicro, iDRAC, iLO, XCC and others)."""
class RedfishDriver(HealthMetricsMixin, ManagementControllerDriver):
"""NAPALM driver for Redfish BMCs (Supermicro, iDRAC, iLO, XCC and others).
``HealthMetricsMixin``: the BMC's own load, memory and interfaces over SNMP (UCD-MIB),
where the BMC runs net-snmp. The managed server's sensors come from ``get_environment``.
"""
platform = "redfish"
DRIVER_NAME = "redfish"
+22 -4
View File
@@ -11,8 +11,9 @@ from __future__ import annotations
from typing import Any
from napalm.base.exceptions import ConnectionException
from napalm_device_types import ManagementControllerDriver
from napalm_device_types import HealthMetricsMixin, ManagementControllerDriver
from pyghmi.ipmi.command import Command
from pyghmi.ipmi.private.session import Session
_JsonDict = dict[str, Any]
@@ -34,8 +35,12 @@ def _supply_present(reading: Any) -> bool:
return "Present" in (getattr(reading, "states", None) or [])
class IpmiDriver(ManagementControllerDriver):
"""NAPALM driver for BMCs over IPMI 2.0 (lanplus), read-only."""
class IpmiDriver(HealthMetricsMixin, ManagementControllerDriver):
"""NAPALM driver for BMCs over IPMI 2.0 (lanplus), read-only.
``HealthMetricsMixin``: the BMC's own load, memory and interfaces over SNMP (UCD-MIB),
where the BMC runs net-snmp. The managed server's sensors come from ``get_environment``.
"""
platform = "ipmi"
DRIVER_NAME = "ipmi"
@@ -61,13 +66,13 @@ class IpmiDriver(ManagementControllerDriver):
# --- connection ----------------------------------------------------------------------
def open(self) -> None:
self._forget_session()
try:
self._ipmi = Command(
bmc=self.hostname,
userid=self.username,
password=self.password,
port=self.port,
keepalive=False,
)
except Exception as exc:
raise ConnectionException(f"Cannot connect to IPMI at {self.hostname}: {exc}") \
@@ -80,6 +85,19 @@ class IpmiDriver(ManagementControllerDriver):
ipmi.ipmi_session.logout()
except Exception: # a session left open times out on the BMC
pass
self._forget_session()
def _forget_session(self) -> None:
"""Drop this BMC's session from pyghmi's cache of sessions being set up.
pyghmi files a new session there under ``(bmc, userid, password, port, kg)`` with
the credentials as str, and only ever deletes it under the credentials as bytes, so
the entry stays. The next ``Command`` for the same BMC in this worker process then
got the old, logged-out session back and failed with ``'<' not supported between
instances of 'float' and 'NoneType'``.
"""
key = (self.hostname, self.username, self.password, self.port, None)
Session.initting_sessions.pop(key, None)
def is_alive(self) -> _JsonDict:
return {"is_alive": self._ipmi is not None}