Extend parsers for the driver

parse_power_supply also reads items of the legacy Power.PowerSupplies array, parse_ethernet_interface exposes the raw LinkStatus, and parse_network_protocol reports protocols that carry a Port without a ProtocolEnabled flag.
This commit is contained in:
Christian Manivong committed 2026-10-13 05:02:58 +00:00
1 parent 8b512b6e0d
commit 209537a793
4 files changed
+38 -14

No files matched your search

+13 -11
View File
@@ -134,16 +134,12 @@ def parse_power(power: Mapping[str, Any]) -> dict[str, Any]:
"""``Chassis/{id}/Power``: ``power`` (supplies), ``voltages``, ``consumed_watts``.""" """``Chassis/{id}/Power``: ``power`` (supplies), ``voltages``, ``consumed_watts``."""
supplies: dict[str, Any] = {} supplies: dict[str, Any] = {}
for index, item in _members(power, "PowerSupplies"): for index, item in _members(power, "PowerSupplies"):
if not present(item): psu = parse_power_supply(item)
if not psu["present"]:
continue continue
name = unique_name(supplies, _name(item, "Name"), item.get("MemberId", index)) name = unique_name(supplies, psu["name"], item.get("MemberId", index))
capacity = number(item.get("PowerCapacityWatts")) supplies[name] = {"status": psu["status"], "capacity": psu["capacity"],
output = number(item.get("LastPowerOutputWatts")) "output": psu["output"]}
supplies[name] = {
"status": _ok(item),
"capacity": capacity if capacity is not None else -1.0,
"output": output if output is not None else -1.0,
}
voltages: dict[str, Any] = {} voltages: dict[str, Any] = {}
for index, item in _members(power, "Voltages"): for index, item in _members(power, "Voltages"):
@@ -210,12 +206,18 @@ def parse_fan(fan: Mapping[str, Any]) -> dict[str, Any]:
def parse_power_supply(supply: Mapping[str, Any], def parse_power_supply(supply: Mapping[str, Any],
metrics: Mapping[str, Any] | None = None) -> dict[str, Any]: metrics: Mapping[str, Any] | None = None) -> dict[str, Any]:
"""One member of ``PowerSubsystem/PowerSupplies``, with its ``Metrics`` if read.""" """One member of ``PowerSubsystem/PowerSupplies``, with its ``Metrics`` if read.
An item of the legacy ``Power.PowerSupplies`` array parses too (``MemberId`` as id,
``LastPowerOutputWatts`` as output).
"""
state, health = status(supply) state, health = status(supply)
capacity = number(supply.get("PowerCapacityWatts")) capacity = number(supply.get("PowerCapacityWatts"))
output = _excerpt(metrics, "OutputPowerWatts") if metrics else None output = _excerpt(metrics, "OutputPowerWatts") if metrics else None
if output is None:
output = number(supply.get("LastPowerOutputWatts"))
return { return {
"id": text(supply.get("Id")), "id": text(supply.get("Id")) or text(supply.get("MemberId")),
"name": _name(supply, "Name"), "name": _name(supply, "Name"),
"present": present(supply), "present": present(supply),
"status": _ok(supply), "status": _ok(supply),
+4 -3
View File
@@ -99,6 +99,7 @@ def parse_ethernet_interface(iface: Mapping[str, Any]) -> dict[str, Any]:
"ipv6": _ipv6(iface.get("IPv6Addresses")), "ipv6": _ipv6(iface.get("IPv6Addresses")),
"hostname": text(iface.get("HostName")), "hostname": text(iface.get("HostName")),
"fqdn": text(iface.get("FQDN")), "fqdn": text(iface.get("FQDN")),
"link_status": link_status,
"vlan": vlan_id, "vlan": vlan_id,
"permanent_mac_address": _mac(iface.get("PermanentMACAddress")) or None, "permanent_mac_address": _mac(iface.get("PermanentMACAddress")) or None,
} }
@@ -107,13 +108,13 @@ def parse_ethernet_interface(iface: Mapping[str, Any]) -> dict[str, Any]:
def parse_network_protocol(proto: Mapping[str, Any]) -> dict[str, Any]: def parse_network_protocol(proto: Mapping[str, Any]) -> dict[str, Any]:
"""``Managers/{id}/NetworkProtocol``: which services the BMC runs, NTP and SNMP. """``Managers/{id}/NetworkProtocol``: which services the BMC runs, NTP and SNMP.
``protocols`` holds every property with a ``ProtocolEnabled`` flag, so OEM-free services ``protocols`` holds every property with a ``ProtocolEnabled`` flag or a ``Port``, so
the schema adds later show up without code changes. NTP servers and the SNMP details services the schema adds later show up without code changes. NTP servers and the SNMP details
exist only in newer schema versions; missing values are ``None``. exist only in newer schema versions; missing values are ``None``.
""" """
protocols = {} protocols = {}
for key, value in proto.items(): for key, value in proto.items():
if isinstance(value, Mapping) and "ProtocolEnabled" in value: if isinstance(value, Mapping) and ("ProtocolEnabled" in value or "Port" in value):
protocols[key] = { protocols[key] = {
"enabled": boolean(value.get("ProtocolEnabled")), "enabled": boolean(value.get("ProtocolEnabled")),
"port": integer(value.get("Port")), "port": integer(value.get("Port")),
+14
View File
@@ -281,3 +281,17 @@ def test_build_environment_from_new_resources(rackmount):
def test_build_environment_empty(): def test_build_environment_empty():
assert build_environment() == { assert build_environment() == {
"fans": {}, "temperature": {}, "power": {}, "voltages": {}, "consumed_watts": None} "fans": {}, "temperature": {}, "power": {}, "voltages": {}, "consumed_watts": None}
def test_power_supply_legacy_item(rackmount):
# Items of the legacy Power.PowerSupplies array parse the same way: MemberId as id,
# LastPowerOutputWatts as output.
(item,) = rackmount(CHASSIS + "/Power")["PowerSupplies"]
psu = parse_power_supply(item)
assert psu["id"] == "0"
assert psu["name"] == "Power Supply Bay"
assert psu["capacity"] == 800.0
assert psu["output"] == 325.0
assert psu["model"] == "499253-B21"
assert psu["serial_number"] == "1Z0000001"
assert psu["status"] is False
+7
View File
@@ -57,6 +57,7 @@ def test_ethernet_interface(rackmount):
assert eth["fqdn"] == "web483-bmc.dmtf.org" assert eth["fqdn"] == "web483-bmc.dmtf.org"
assert eth["vlan"] == 101 assert eth["vlan"] == 101
assert eth["permanent_mac_address"] == "23:11:8A:33:CF:EA" assert eth["permanent_mac_address"] == "23:11:8A:33:CF:EA"
assert eth["link_status"] == "LinkUp"
def test_ethernet_interface_without_link_status(rackmount): def test_ethernet_interface_without_link_status(rackmount):
@@ -64,6 +65,7 @@ def test_ethernet_interface_without_link_status(rackmount):
eth = parse_ethernet_interface(rackmount(MANAGER + "/EthernetInterfaces/ToHost")) eth = parse_ethernet_interface(rackmount(MANAGER + "/EthernetInterfaces/ToHost"))
assert eth["interface"]["is_up"] is True assert eth["interface"]["is_up"] is True
assert eth["ipv4"] == {"192.168.20.55": {"prefix_length": 24}} assert eth["ipv4"] == {"192.168.20.55": {"prefix_length": 24}}
assert eth["link_status"] is None
assert eth["vlan"] is None assert eth["vlan"] is None
@@ -151,6 +153,11 @@ def test_network_protocol_ntp(telemetry):
"communities": []} "communities": []}
def test_network_protocol_port_without_flag():
np = parse_network_protocol({"IPMI": {"Port": 623}, "Proxy": {"Enabled": True}})
assert np["protocols"] == {"IPMI": {"enabled": None, "port": 623}}
def test_network_protocol_ntp_blank_entries(): def test_network_protocol_ntp_blank_entries():
servers = ["", None, " pool.ntp.org ", "pool.ntp.org"] servers = ["", None, " pool.ntp.org ", "pool.ntp.org"]
np = parse_network_protocol({"NTP": {"ProtocolEnabled": True, "NTPServers": servers}}) np = parse_network_protocol({"NTP": {"ProtocolEnabled": True, "NTPServers": servers}})