feat: read-only IPMI driver; report a missing Redfish license (0.2.0)
A Supermicro X10DRL-i refuses Redfish without a license (SUM, DCMS or
OOB), even the service root. IPMI answers without one.
- New entry point `ipmi` (napalm_redfish.ipmi.IpmiDriver) on
ManagementControllerDriver, over pyghmi: facts and inventory from the
FRU (serial number, system UUID), the BMC's LAN interface, sensors
(temperatures, fans, power supplies), the power state, the event log
and the BMC firmware. Read-only: no power control, no BMC reset.
Tested against recordings of the X10 with serials, the UUID, MACs and
IPs replaced.
- RedfishDriver.open() no longer fails on a license refusal: get_facts
returns what is known without Redfish, get_device_warnings reports
bmc_license_required with the licenses the BMC names, every other
getter raises. netOrk then shows the reason instead of an offline BMC.
- IpmiDriver is not exported from the package: NAPALM's own
get_network_driver("redfish") takes the first driver class of
napalm_redfish.
This commit is contained in:
1 parent
bfbeb19255
commit
332e6090a7
17 files changed
+1194
-19
No files matched your search
@@ -65,7 +65,12 @@ from napalm_redfish.parsers import (
|
||||
to_napalm_users,
|
||||
)
|
||||
from napalm_redfish.parsers.common import link, link_list, odata_id
|
||||
from napalm_redfish.transport import RedfishError, RedfishNotFound, RedfishTransport
|
||||
from napalm_redfish.transport import (
|
||||
RedfishError,
|
||||
RedfishLicenseError,
|
||||
RedfishNotFound,
|
||||
RedfishTransport,
|
||||
)
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
@@ -152,12 +157,20 @@ class RedfishDriver(ManagementControllerDriver):
|
||||
retries=self.optional_args.get("retries", 1),
|
||||
)
|
||||
self._cache: _JsonDict = {}
|
||||
self._license_refused: RedfishLicenseError | None = None
|
||||
|
||||
# --- connection ----------------------------------------------------------------------
|
||||
|
||||
def open(self) -> None:
|
||||
"""Connect. A BMC that refuses Redfish for want of a license is reachable all the
|
||||
same: ``open`` succeeds, ``get_facts`` returns what is known without Redfish, and
|
||||
``get_device_warnings`` says which license is missing. Every other getter raises.
|
||||
"""
|
||||
self._license_refused = None
|
||||
try:
|
||||
self.transport.open()
|
||||
except RedfishLicenseError as exc:
|
||||
self._license_refused = exc
|
||||
except RedfishError as exc:
|
||||
raise ConnectionException(f"Cannot connect to Redfish at {self.hostname}: {exc}") \
|
||||
from exc
|
||||
@@ -167,6 +180,8 @@ class RedfishDriver(ManagementControllerDriver):
|
||||
self._cache = {}
|
||||
|
||||
def is_alive(self) -> _JsonDict:
|
||||
if self._license_refused is not None:
|
||||
return {"is_alive": True}
|
||||
try:
|
||||
self.transport.get("/redfish/v1/")
|
||||
except Exception:
|
||||
@@ -181,6 +196,8 @@ class RedfishDriver(ManagementControllerDriver):
|
||||
return self._cache[key]
|
||||
|
||||
def _root(self) -> _JsonDict:
|
||||
if self._license_refused is not None:
|
||||
raise ConnectionException(f"{self.hostname}: {self._license_refused}")
|
||||
return self._cached("root", lambda: parse_service_root(self.transport.root))
|
||||
|
||||
def _first(self, collection: str) -> _JsonDict:
|
||||
@@ -233,6 +250,10 @@ class RedfishDriver(ManagementControllerDriver):
|
||||
# --- NAPALM getters ----------------------------------------------------------------
|
||||
|
||||
def get_facts(self) -> _JsonDict:
|
||||
if self._license_refused is not None:
|
||||
return {"uptime": -1.0, "vendor": "", "model": "", "hostname": self.hostname,
|
||||
"fqdn": self.hostname, "os_version": "", "serial_number": "",
|
||||
"interface_list": []}
|
||||
system = parse_system(self._system_raw())
|
||||
manager = parse_manager(self._manager_raw())
|
||||
interfaces = self._manager_interfaces()
|
||||
@@ -329,6 +350,14 @@ class RedfishDriver(ManagementControllerDriver):
|
||||
communities[community["name"]] = {"acl": "", "mode": mode}
|
||||
return {"chassis_id": "", "community": communities, "contact": "", "location": ""}
|
||||
|
||||
def get_device_warnings(self) -> list[_JsonDict]:
|
||||
"""Raw signals for netOrk's warning catalog; presentation is netOrk's."""
|
||||
refused = self._license_refused
|
||||
if refused is None:
|
||||
return []
|
||||
return [{"code": "bmc_license_required",
|
||||
"meta": {"licenses": refused.licenses, "message": refused.detail}}]
|
||||
|
||||
# --- role: power -------------------------------------------------------------------
|
||||
|
||||
def _accepted_actions(self, system: _JsonDict) -> list[str]:
|
||||
|
||||
Reference in new issue
Block a user