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
@@ -61,6 +61,13 @@ class RedfishError(Exception):
|
||||
class RedfishLicenseError(RedfishError):
|
||||
"""The service refuses Redfish until a vendor license is activated."""
|
||||
|
||||
def __init__(self, message: str, *, licenses: str = "", detail: str = ""):
|
||||
super().__init__(message)
|
||||
#: The licenses the service names, e.g. "SUM DCMS OOB"; empty when it names none.
|
||||
self.licenses = licenses
|
||||
#: The service's own message.
|
||||
self.detail = detail
|
||||
|
||||
|
||||
class RedfishAuthError(RedfishError):
|
||||
"""Login failed, or the account may not read the resource (HTTP 401/403)."""
|
||||
@@ -74,6 +81,19 @@ class RedfishNotFound(RedfishHTTPError):
|
||||
"""The resource does not exist (HTTP 404)."""
|
||||
|
||||
|
||||
def _message_args(body: Any) -> list[str]:
|
||||
"""The MessageArgs of a Redfish error body's extended info, in order."""
|
||||
error = body.get("error") if isinstance(body, Mapping) else None
|
||||
if not isinstance(error, Mapping):
|
||||
return []
|
||||
return [
|
||||
str(arg)
|
||||
for info in error.get("@Message.ExtendedInfo") or []
|
||||
if isinstance(info, Mapping)
|
||||
for arg in info.get("MessageArgs") or []
|
||||
]
|
||||
|
||||
|
||||
def _message_ids(body: Any) -> list[str]:
|
||||
"""The MessageIds of a Redfish error body's extended info."""
|
||||
error = body.get("error") if isinstance(body, Mapping) else None
|
||||
@@ -202,7 +222,9 @@ class RedfishTransport:
|
||||
detail = _error_message(body)
|
||||
if any(message_id.endswith(LICENSE_REFUSED) for message_id in _message_ids(body)):
|
||||
return RedfishLicenseError(
|
||||
f"{self.base_url}: Redfish needs a vendor license on this BMC: {detail}"
|
||||
f"{self.base_url}: Redfish needs a vendor license on this BMC: {detail}",
|
||||
licenses=" ".join(" ".join(_message_args(body)).split()),
|
||||
detail=detail,
|
||||
)
|
||||
return RedfishError(f"{self.base_url}: {reason}" + (f": {detail}" if detail else ""))
|
||||
|
||||
|
||||
Reference in new issue
Block a user