style: ruff format and the lint rules of the driver standard
Mechanical: ruff format, and ruff check --fix with the rule set every NAPALM driver uses (E, F, I, UP, B at line length 100, Python 3.10). Mapping and Iterable come from collections.abc, imports are sorted. What ruff cannot fix comes in the next commit.
This commit is contained in:
1 parent
5b24675a12
commit
09029ca0a4
29 files changed
+985
-456
No files matched your search
+144
-87
@@ -190,8 +190,9 @@ class RedfishDriver(HealthMetricsMixin, LicenseMixin, ManagementControllerDriver
|
||||
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
|
||||
raise ConnectionException(
|
||||
f"Cannot connect to Redfish at {self.hostname}: {exc}"
|
||||
) from exc
|
||||
|
||||
def close(self) -> None:
|
||||
self.transport.close()
|
||||
@@ -271,26 +272,37 @@ class RedfishDriver(HealthMetricsMixin, LicenseMixin, ManagementControllerDriver
|
||||
return parse_network_protocol(raw or {})
|
||||
|
||||
def _manager_interfaces(self) -> list[_JsonDict]:
|
||||
return [parse_ethernet_interface(raw)
|
||||
for raw in self._members_of(self._manager_raw(), "EthernetInterfaces")]
|
||||
return [
|
||||
parse_ethernet_interface(raw)
|
||||
for raw in self._members_of(self._manager_raw(), "EthernetInterfaces")
|
||||
]
|
||||
|
||||
# --- 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": []}
|
||||
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()
|
||||
protocol = self._network_protocol()
|
||||
hostname = (protocol["hostname"]
|
||||
or next((i["hostname"] for i in interfaces if i["hostname"]), None)
|
||||
or self.hostname)
|
||||
fqdn = (protocol["fqdn"]
|
||||
or next((i["fqdn"] for i in interfaces if i["fqdn"]), None)
|
||||
or hostname)
|
||||
hostname = (
|
||||
protocol["hostname"]
|
||||
or next((i["hostname"] for i in interfaces if i["hostname"]), None)
|
||||
or self.hostname
|
||||
)
|
||||
fqdn = (
|
||||
protocol["fqdn"] or next((i["fqdn"] for i in interfaces if i["fqdn"]), None) or hostname
|
||||
)
|
||||
return {
|
||||
"uptime": -1.0, # Redfish has no uptime
|
||||
"vendor": system["manufacturer"] or "",
|
||||
@@ -327,11 +339,13 @@ class RedfishDriver(HealthMetricsMixin, LicenseMixin, ManagementControllerDriver
|
||||
fans, temperature = legacy["fans"], legacy["temperature"]
|
||||
elif thermal:
|
||||
subsystem = parse_thermal_subsystem(self.transport.get(thermal["path"]))
|
||||
parsed_fans = [parse_fan(raw) for raw in self.transport.members(
|
||||
subsystem["fans_path"])] if subsystem["fans_path"] else []
|
||||
parsed_fans = (
|
||||
[parse_fan(raw) for raw in self.transport.members(subsystem["fans_path"])]
|
||||
if subsystem["fans_path"]
|
||||
else []
|
||||
)
|
||||
if sources["sensors"]:
|
||||
sensors = [parse_sensor(raw)
|
||||
for raw in self.transport.members(sources["sensors"])]
|
||||
sensors = [parse_sensor(raw) for raw in self.transport.members(sources["sensors"])]
|
||||
temperature = build_environment(sensors=sensors)["temperature"]
|
||||
elif subsystem["thermal_metrics_path"]:
|
||||
metrics = self.transport.get_optional(subsystem["thermal_metrics_path"])
|
||||
@@ -342,12 +356,13 @@ class RedfishDriver(HealthMetricsMixin, LicenseMixin, ManagementControllerDriver
|
||||
if supply and supply["schema"] == "Power":
|
||||
power = parse_power(self.transport.get(supply["path"]))["power"]
|
||||
elif supply:
|
||||
psus = [parse_power_supply(raw, self.transport.follow(raw, "Metrics"))
|
||||
for raw in self._power_supplies_raw(supply["path"])]
|
||||
psus = [
|
||||
parse_power_supply(raw, self.transport.follow(raw, "Metrics"))
|
||||
for raw in self._power_supplies_raw(supply["path"])
|
||||
]
|
||||
power = build_environment(power_supplies=psus)["power"]
|
||||
|
||||
return {"fans": fans, "temperature": temperature, "power": power,
|
||||
"cpu": {}, "memory": {}}
|
||||
return {"fans": fans, "temperature": temperature, "power": power, "cpu": {}, "memory": {}}
|
||||
|
||||
def _power_supplies_raw(self, subsystem_path: str) -> list[_JsonDict]:
|
||||
subsystem = parse_power_subsystem(self.transport.get(subsystem_path))
|
||||
@@ -358,8 +373,7 @@ class RedfishDriver(HealthMetricsMixin, LicenseMixin, ManagementControllerDriver
|
||||
service = self._service("AccountService")
|
||||
if service is None:
|
||||
return {}
|
||||
return to_napalm_users(parse_account(raw) for raw in self._members_of(service,
|
||||
"Accounts"))
|
||||
return to_napalm_users(parse_account(raw) for raw in self._members_of(service, "Accounts"))
|
||||
|
||||
def get_ntp_servers(self) -> _JsonDict:
|
||||
return {server: {} for server in self._network_protocol()["ntp"]["servers"]}
|
||||
@@ -387,15 +401,23 @@ class RedfishDriver(HealthMetricsMixin, LicenseMixin, ManagementControllerDriver
|
||||
"""
|
||||
refused = self._license_refused
|
||||
if refused is not None:
|
||||
return [{"code": "bmc_license_required",
|
||||
"meta": {"licenses": refused.licenses, "message": refused.detail}}]
|
||||
return [
|
||||
{
|
||||
"code": "bmc_license_required",
|
||||
"meta": {"licenses": refused.licenses, "message": refused.detail},
|
||||
}
|
||||
]
|
||||
self._probe_withheld()
|
||||
if not self._withheld:
|
||||
return []
|
||||
licenses = sorted({name for names in self._withheld.values() for name in names.split()})
|
||||
parts = [part for part in _WITHHELD_ORDER if part in self._withheld]
|
||||
return [{"code": "bmc_license_partial",
|
||||
"meta": {"licenses": " ".join(licenses), "parts": parts}}]
|
||||
return [
|
||||
{
|
||||
"code": "bmc_license_partial",
|
||||
"meta": {"licenses": " ".join(licenses), "parts": parts},
|
||||
}
|
||||
]
|
||||
|
||||
def get_licenses(self) -> list[_JsonDict]:
|
||||
"""The licenses the BMC asks for and does not have (``status`` "missing").
|
||||
@@ -407,8 +429,10 @@ class RedfishDriver(HealthMetricsMixin, LicenseMixin, ManagementControllerDriver
|
||||
"""
|
||||
refused = self._license_refused
|
||||
if refused is not None:
|
||||
return [{"name": name, "status": "missing", "detail": refused.detail}
|
||||
for name in refused.licenses.split() or [""]]
|
||||
return [
|
||||
{"name": name, "status": "missing", "detail": refused.detail}
|
||||
for name in refused.licenses.split() or [""]
|
||||
]
|
||||
self._probe_withheld()
|
||||
withheld: dict[str, list[str]] = {}
|
||||
for part in _WITHHELD_ORDER:
|
||||
@@ -416,8 +440,10 @@ class RedfishDriver(HealthMetricsMixin, LicenseMixin, ManagementControllerDriver
|
||||
continue
|
||||
for name in self._withheld[part].split() or [""]:
|
||||
withheld.setdefault(name, []).append(part)
|
||||
return [{"name": name, "status": "missing", "withholds": parts}
|
||||
for name, parts in withheld.items()]
|
||||
return [
|
||||
{"name": name, "status": "missing", "withholds": parts}
|
||||
for name, parts in withheld.items()
|
||||
]
|
||||
|
||||
def _withhold(self, part: str, exc: RedfishFeatureLicenseError) -> None:
|
||||
LOG.info("%s: %s withheld by the BMC: %s", self.hostname, part, exc)
|
||||
@@ -460,8 +486,7 @@ class RedfishDriver(HealthMetricsMixin, LicenseMixin, ManagementControllerDriver
|
||||
|
||||
def get_power_state(self) -> _JsonDict:
|
||||
system = self.transport.get(self._system_path())
|
||||
return {"state": parse_power_state(system) or "",
|
||||
"actions": self._accepted_actions(system)}
|
||||
return {"state": parse_power_state(system) or "", "actions": self._accepted_actions(system)}
|
||||
|
||||
def set_power_state(self, action: str) -> None:
|
||||
if action not in POWER_ACTIONS:
|
||||
@@ -475,7 +500,8 @@ class RedfishDriver(HealthMetricsMixin, LicenseMixin, ManagementControllerDriver
|
||||
raise RuntimeError(
|
||||
f"{self.hostname}: {action} not accepted while the server is "
|
||||
f"{parse_power_state(system) or 'in an unknown state'} "
|
||||
f"(accepted: {', '.join(accepted) or 'none'})")
|
||||
f"(accepted: {', '.join(accepted) or 'none'})"
|
||||
)
|
||||
try:
|
||||
self.transport.post(target, {"ResetType": action})
|
||||
except RedfishError as exc:
|
||||
@@ -490,11 +516,13 @@ class RedfishDriver(HealthMetricsMixin, LicenseMixin, ManagementControllerDriver
|
||||
raise RuntimeError(f"{self.hostname}: the BMC offers no reset action")
|
||||
body: _JsonDict = {}
|
||||
if reset["allowed"] is not None:
|
||||
reset_type = next((t for t in ("GracefulRestart", "ForceRestart")
|
||||
if t in reset["allowed"]), None)
|
||||
reset_type = next(
|
||||
(t for t in ("GracefulRestart", "ForceRestart") if t in reset["allowed"]), None
|
||||
)
|
||||
if reset_type is None:
|
||||
raise RuntimeError(f"{self.hostname}: BMC reset types "
|
||||
f"{reset['allowed']} include no restart")
|
||||
raise RuntimeError(
|
||||
f"{self.hostname}: BMC reset types {reset['allowed']} include no restart"
|
||||
)
|
||||
body["ResetType"] = reset_type
|
||||
try:
|
||||
self.transport.post(reset["target"], body)
|
||||
@@ -524,9 +552,15 @@ class RedfishDriver(HealthMetricsMixin, LicenseMixin, ManagementControllerDriver
|
||||
cpu = parse_processor(raw)
|
||||
if cpu["processor_type"] not in (None, "CPU"):
|
||||
continue # GPUs, FPGAs, accelerators
|
||||
cpus.append({"socket": cpu["socket"] or cpu["id"] or "", "model": cpu["model"] or "",
|
||||
"cores": cpu["cores"], "threads": cpu["threads"],
|
||||
"status": _status(cpu)})
|
||||
cpus.append(
|
||||
{
|
||||
"socket": cpu["socket"] or cpu["id"] or "",
|
||||
"model": cpu["model"] or "",
|
||||
"cores": cpu["cores"],
|
||||
"threads": cpu["threads"],
|
||||
"status": _status(cpu),
|
||||
}
|
||||
)
|
||||
return cpus
|
||||
|
||||
def _dimms(self, system: _JsonDict) -> list[_JsonDict]:
|
||||
@@ -534,15 +568,17 @@ class RedfishDriver(HealthMetricsMixin, LicenseMixin, ManagementControllerDriver
|
||||
for raw in self._members_of(system, "Memory"):
|
||||
dimm = parse_memory(raw)
|
||||
size = dimm["capacity_mib"]
|
||||
dimms.append({
|
||||
"slot": dimm["location"] or dimm["id"] or "",
|
||||
"size_bytes": size * 1024 * 1024 if size else None,
|
||||
"type": dimm["memory_device_type"] or "",
|
||||
"speed_mts": dimm["speed_mhz"], # Redfish: MHz or MT/s "as appropriate"
|
||||
"part_number": dimm["part_number"] or "",
|
||||
"serial_number": dimm["serial_number"] or "",
|
||||
"status": _status(dimm),
|
||||
})
|
||||
dimms.append(
|
||||
{
|
||||
"slot": dimm["location"] or dimm["id"] or "",
|
||||
"size_bytes": size * 1024 * 1024 if size else None,
|
||||
"type": dimm["memory_device_type"] or "",
|
||||
"speed_mts": dimm["speed_mhz"], # Redfish: MHz or MT/s "as appropriate"
|
||||
"part_number": dimm["part_number"] or "",
|
||||
"serial_number": dimm["serial_number"] or "",
|
||||
"status": _status(dimm),
|
||||
}
|
||||
)
|
||||
return dimms
|
||||
|
||||
def _storage_drives(self, system: _JsonDict) -> list[_JsonDict] | None:
|
||||
@@ -572,15 +608,18 @@ class RedfishDriver(HealthMetricsMixin, LicenseMixin, ManagementControllerDriver
|
||||
parsed = []
|
||||
for storage in self._members_of(system, "SimpleStorage"):
|
||||
parsed.extend(parse_simple_storage(storage)["drives"])
|
||||
return [{
|
||||
"slot": d["name"] or d["id"] or "",
|
||||
"model": d["model"] or "",
|
||||
"serial_number": d["serial_number"] or "",
|
||||
"size_bytes": d["capacity_bytes"],
|
||||
"media": d["media_type"] or "",
|
||||
"protocol": d["protocol"] or "",
|
||||
"status": _status(d),
|
||||
} for d in parsed]
|
||||
return [
|
||||
{
|
||||
"slot": d["name"] or d["id"] or "",
|
||||
"model": d["model"] or "",
|
||||
"serial_number": d["serial_number"] or "",
|
||||
"size_bytes": d["capacity_bytes"],
|
||||
"media": d["media_type"] or "",
|
||||
"protocol": d["protocol"] or "",
|
||||
"status": _status(d),
|
||||
}
|
||||
for d in parsed
|
||||
]
|
||||
|
||||
def _psus(self) -> list[_JsonDict]:
|
||||
source = environment_sources(self._chassis_raw())["power"]
|
||||
@@ -595,13 +634,15 @@ class RedfishDriver(HealthMetricsMixin, LicenseMixin, ManagementControllerDriver
|
||||
if not isinstance(raw, dict):
|
||||
continue
|
||||
psu = parse_power_supply(raw)
|
||||
psus.append({
|
||||
"slot": psu["name"],
|
||||
"model": psu["model"] or "",
|
||||
"serial_number": psu["serial_number"] or "",
|
||||
"capacity_watts": int(psu["capacity"]) if psu["capacity"] >= 0 else None,
|
||||
"status": _status(psu),
|
||||
})
|
||||
psus.append(
|
||||
{
|
||||
"slot": psu["name"],
|
||||
"model": psu["model"] or "",
|
||||
"serial_number": psu["serial_number"] or "",
|
||||
"capacity_watts": int(psu["capacity"]) if psu["capacity"] >= 0 else None,
|
||||
"status": _status(psu),
|
||||
}
|
||||
)
|
||||
return psus
|
||||
|
||||
def _nics(self, system: _JsonDict) -> list[_JsonDict]:
|
||||
@@ -610,13 +651,16 @@ class RedfishDriver(HealthMetricsMixin, LicenseMixin, ManagementControllerDriver
|
||||
nic = parse_ethernet_interface(raw)
|
||||
speed = nic["interface"]["speed"]
|
||||
link_up = {"LinkUp": True, "LinkDown": False, "NoLink": False}.get(
|
||||
nic["link_status"] or "")
|
||||
nics.append({
|
||||
"name": nic["name"] or "",
|
||||
"mac_address": nic["interface"]["mac_address"].lower(),
|
||||
"speed_mbps": int(speed) if speed >= 0 else None,
|
||||
"link_up": link_up,
|
||||
})
|
||||
nic["link_status"] or ""
|
||||
)
|
||||
nics.append(
|
||||
{
|
||||
"name": nic["name"] or "",
|
||||
"mac_address": nic["interface"]["mac_address"].lower(),
|
||||
"speed_mbps": int(speed) if speed >= 0 else None,
|
||||
"link_up": link_up,
|
||||
}
|
||||
)
|
||||
return nics
|
||||
|
||||
# --- role: event log, firmware, protocols --------------------------------------------
|
||||
@@ -640,20 +684,24 @@ class RedfishDriver(HealthMetricsMixin, LicenseMixin, ManagementControllerDriver
|
||||
try:
|
||||
raw_entries = self.transport.members(service["entries_path"])
|
||||
except RedfishNotFound:
|
||||
LOG.warning("%s: log %s has no entries collection", self.hostname,
|
||||
service["id"])
|
||||
LOG.warning(
|
||||
"%s: log %s has no entries collection", self.hostname, service["id"]
|
||||
)
|
||||
continue
|
||||
for raw in raw_entries:
|
||||
entry = parse_log_entry(raw)
|
||||
severity = entry["severity"]
|
||||
entries.append({
|
||||
"id": odata_id(raw) or f"{service['id']}/{entry['id']}",
|
||||
"timestamp": _timestamp(entry["created"]),
|
||||
"severity": severity if severity in _SEVERITIES else "OK",
|
||||
"message": entry["message"] or "",
|
||||
})
|
||||
entries.sort(key=lambda e: (e["timestamp"] is not None, e["timestamp"] or 0.0),
|
||||
reverse=True)
|
||||
entries.append(
|
||||
{
|
||||
"id": odata_id(raw) or f"{service['id']}/{entry['id']}",
|
||||
"timestamp": _timestamp(entry["created"]),
|
||||
"severity": severity if severity in _SEVERITIES else "OK",
|
||||
"message": entry["message"] or "",
|
||||
}
|
||||
)
|
||||
entries.sort(
|
||||
key=lambda e: (e["timestamp"] is not None, e["timestamp"] or 0.0), reverse=True
|
||||
)
|
||||
return entries[:limit]
|
||||
|
||||
def _update_service(self) -> _JsonDict | None:
|
||||
@@ -673,11 +721,20 @@ class RedfishDriver(HealthMetricsMixin, LicenseMixin, ManagementControllerDriver
|
||||
return []
|
||||
bmc = parse_manager(self._manager_raw())["firmware_version"]
|
||||
bios = self._system_raw().get("BiosVersion")
|
||||
return [{"name": name, "version": version, "updateable": None}
|
||||
for name, version in (("BMC", bmc), ("BIOS", bios)) if version]
|
||||
return [
|
||||
{"name": name, "version": version, "updateable": None}
|
||||
for name, version in (("BMC", bmc), ("BIOS", bios))
|
||||
if version
|
||||
]
|
||||
items = [parse_firmware(raw) for raw in self._members_of(service, "FirmwareInventory")]
|
||||
return [{"name": fw["name"] or fw["id"] or "", "version": fw["version"] or "",
|
||||
"updateable": fw["updateable"]} for fw in items]
|
||||
return [
|
||||
{
|
||||
"name": fw["name"] or fw["id"] or "",
|
||||
"version": fw["version"] or "",
|
||||
"updateable": fw["updateable"],
|
||||
}
|
||||
for fw in items
|
||||
]
|
||||
|
||||
def get_management_protocols(self) -> _JsonDict:
|
||||
"""What the BMC listens on. A protocol the BMC does not report counts as off."""
|
||||
|
||||
Reference in new issue
Block a user