diff --git a/napalm_redfish/parsers/environment.py b/napalm_redfish/parsers/environment.py index e2f2f95..508c299 100644 --- a/napalm_redfish/parsers/environment.py +++ b/napalm_redfish/parsers/environment.py @@ -134,16 +134,12 @@ def parse_power(power: Mapping[str, Any]) -> dict[str, Any]: """``Chassis/{id}/Power``: ``power`` (supplies), ``voltages``, ``consumed_watts``.""" supplies: dict[str, Any] = {} for index, item in _members(power, "PowerSupplies"): - if not present(item): + psu = parse_power_supply(item) + if not psu["present"]: continue - name = unique_name(supplies, _name(item, "Name"), item.get("MemberId", index)) - capacity = number(item.get("PowerCapacityWatts")) - output = number(item.get("LastPowerOutputWatts")) - 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, - } + name = unique_name(supplies, psu["name"], item.get("MemberId", index)) + supplies[name] = {"status": psu["status"], "capacity": psu["capacity"], + "output": psu["output"]} voltages: dict[str, Any] = {} 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], 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) capacity = number(supply.get("PowerCapacityWatts")) output = _excerpt(metrics, "OutputPowerWatts") if metrics else None + if output is None: + output = number(supply.get("LastPowerOutputWatts")) return { - "id": text(supply.get("Id")), + "id": text(supply.get("Id")) or text(supply.get("MemberId")), "name": _name(supply, "Name"), "present": present(supply), "status": _ok(supply), diff --git a/napalm_redfish/parsers/manager.py b/napalm_redfish/parsers/manager.py index a9ac778..9cdbca6 100644 --- a/napalm_redfish/parsers/manager.py +++ b/napalm_redfish/parsers/manager.py @@ -99,6 +99,7 @@ def parse_ethernet_interface(iface: Mapping[str, Any]) -> dict[str, Any]: "ipv6": _ipv6(iface.get("IPv6Addresses")), "hostname": text(iface.get("HostName")), "fqdn": text(iface.get("FQDN")), + "link_status": link_status, "vlan": vlan_id, "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]: """``Managers/{id}/NetworkProtocol``: which services the BMC runs, NTP and SNMP. - ``protocols`` holds every property with a ``ProtocolEnabled`` flag, so OEM-free services - the schema adds later show up without code changes. NTP servers and the SNMP details + ``protocols`` holds every property with a ``ProtocolEnabled`` flag or a ``Port``, so + 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``. """ protocols = {} 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] = { "enabled": boolean(value.get("ProtocolEnabled")), "port": integer(value.get("Port")), diff --git a/tests/parsers/test_environment.py b/tests/parsers/test_environment.py index 86f3b53..7aecaa5 100644 --- a/tests/parsers/test_environment.py +++ b/tests/parsers/test_environment.py @@ -281,3 +281,17 @@ def test_build_environment_from_new_resources(rackmount): def test_build_environment_empty(): assert build_environment() == { "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 diff --git a/tests/parsers/test_manager.py b/tests/parsers/test_manager.py index 7052c26..d5e2e31 100644 --- a/tests/parsers/test_manager.py +++ b/tests/parsers/test_manager.py @@ -57,6 +57,7 @@ def test_ethernet_interface(rackmount): assert eth["fqdn"] == "web483-bmc.dmtf.org" assert eth["vlan"] == 101 assert eth["permanent_mac_address"] == "23:11:8A:33:CF:EA" + assert eth["link_status"] == "LinkUp" 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")) assert eth["interface"]["is_up"] is True assert eth["ipv4"] == {"192.168.20.55": {"prefix_length": 24}} + assert eth["link_status"] is None assert eth["vlan"] is None @@ -151,6 +153,11 @@ def test_network_protocol_ntp(telemetry): "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(): servers = ["", None, " pool.ntp.org ", "pool.ntp.org"] np = parse_network_protocol({"NTP": {"ProtocolEnabled": True, "NTPServers": servers}})