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
+212
-73
@@ -34,10 +34,22 @@ def ts(iso: str) -> float:
|
||||
@pytest.fixture
|
||||
def driver(server):
|
||||
# The mockup's system log advertises a nextLink page it does not contain.
|
||||
server.override("GET", SYSTEM_LOG + "?$skiptoken=2", 200, {
|
||||
"Members": [{"@odata.id": SYSTEM_LOG + "/3", "Id": "3", "Severity": "Warning",
|
||||
"Created": "2012-03-07T14:46:00Z", "Message": "Fan redundancy lost"}],
|
||||
})
|
||||
server.override(
|
||||
"GET",
|
||||
SYSTEM_LOG + "?$skiptoken=2",
|
||||
200,
|
||||
{
|
||||
"Members": [
|
||||
{
|
||||
"@odata.id": SYSTEM_LOG + "/3",
|
||||
"Id": "3",
|
||||
"Severity": "Warning",
|
||||
"Created": "2012-03-07T14:46:00Z",
|
||||
"Message": "Fan redundancy lost",
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
d = RedfishDriver("bmc.example.net", "netork", "s3cret", optional_args={"verify_ssl": False})
|
||||
d.open()
|
||||
yield d
|
||||
@@ -59,9 +71,15 @@ def test_role():
|
||||
assert primary_role_of(RedfishDriver) == "management_controller"
|
||||
assert RedfishDriver.USES_SSH is False
|
||||
assert RedfishDriver.DRIVER_NAME == "redfish"
|
||||
for method in ("get_power_state", "set_power_state", "get_hardware_inventory",
|
||||
"get_event_log", "get_firmware_inventory", "get_management_protocols",
|
||||
"reboot_host"):
|
||||
for method in (
|
||||
"get_power_state",
|
||||
"set_power_state",
|
||||
"get_hardware_inventory",
|
||||
"get_event_log",
|
||||
"get_firmware_inventory",
|
||||
"get_management_protocols",
|
||||
"reboot_host",
|
||||
):
|
||||
assert hasattr(RedfishDriver, method), method
|
||||
|
||||
|
||||
@@ -92,8 +110,13 @@ def test_open_failure_is_connection_exception(server):
|
||||
|
||||
|
||||
def test_optional_args(server):
|
||||
d = RedfishDriver("bmc.example.net", "u", "p", timeout=12,
|
||||
optional_args={"port": 8443, "verify_ssl": False, "auth": "basic"})
|
||||
d = RedfishDriver(
|
||||
"bmc.example.net",
|
||||
"u",
|
||||
"p",
|
||||
timeout=12,
|
||||
optional_args={"port": 8443, "verify_ssl": False, "auth": "basic"},
|
||||
)
|
||||
assert d.transport.base_url == "https://bmc.example.net:8443"
|
||||
assert d.transport.verify is False
|
||||
assert d.transport.auth == "basic"
|
||||
@@ -141,10 +164,14 @@ def test_get_interfaces(driver):
|
||||
|
||||
def test_get_interfaces_ip(driver):
|
||||
assert driver.get_interfaces_ip() == {
|
||||
"eth0": {"ipv4": {"192.168.0.10": {"prefix_length": 22}},
|
||||
"ipv6": {"fe80::1ec1:deff:fe6f:1e24": {"prefix_length": 64}}},
|
||||
"ToHost": {"ipv4": {"192.168.20.55": {"prefix_length": 24}},
|
||||
"ipv6": {"fe80::1ec1:deff:fe6f:1e24": {"prefix_length": 64}}},
|
||||
"eth0": {
|
||||
"ipv4": {"192.168.0.10": {"prefix_length": 22}},
|
||||
"ipv6": {"fe80::1ec1:deff:fe6f:1e24": {"prefix_length": 64}},
|
||||
},
|
||||
"ToHost": {
|
||||
"ipv4": {"192.168.20.55": {"prefix_length": 24}},
|
||||
"ipv6": {"fe80::1ec1:deff:fe6f:1e24": {"prefix_length": 64}},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -164,23 +191,33 @@ def test_get_environment_new_resources(driver, server):
|
||||
}
|
||||
assert len(env["temperature"]) == 8
|
||||
assert env["temperature"]["CPU #1 Temperature"] == {
|
||||
"temperature": 44.0, "is_alert": True, "is_critical": False}
|
||||
"temperature": 44.0,
|
||||
"is_alert": True,
|
||||
"is_critical": False,
|
||||
}
|
||||
# The legacy resources were not read.
|
||||
assert not server.calls("GET", CHASSIS + "/Thermal")
|
||||
assert not server.calls("GET", CHASSIS + "/Power")
|
||||
|
||||
|
||||
def test_get_environment_legacy(driver, server):
|
||||
server.override("GET", CHASSIS, 200,
|
||||
without(CHASSIS, "ThermalSubsystem", "PowerSubsystem", "Sensors"))
|
||||
server.override(
|
||||
"GET", CHASSIS, 200, without(CHASSIS, "ThermalSubsystem", "PowerSubsystem", "Sensors")
|
||||
)
|
||||
env = driver.get_environment()
|
||||
assert_shape(env, napalm_models.EnvironmentDict)
|
||||
assert env["fans"] == {"BaseBoard System Fan": {"status": True},
|
||||
"BaseBoard System Fan Backup": {"status": True}}
|
||||
assert env["fans"] == {
|
||||
"BaseBoard System Fan": {"status": True},
|
||||
"BaseBoard System Fan Backup": {"status": True},
|
||||
}
|
||||
assert env["temperature"]["CPU1 Temp"] == {
|
||||
"temperature": 41.0, "is_alert": False, "is_critical": False}
|
||||
assert env["power"] == {"Power Supply Bay": {"status": False, "capacity": 800.0,
|
||||
"output": 325.0}}
|
||||
"temperature": 41.0,
|
||||
"is_alert": False,
|
||||
"is_critical": False,
|
||||
}
|
||||
assert env["power"] == {
|
||||
"Power Supply Bay": {"status": False, "capacity": 800.0, "output": 325.0}
|
||||
}
|
||||
assert not server.calls("GET", CHASSIS + "/Sensors")
|
||||
|
||||
|
||||
@@ -202,10 +239,17 @@ def test_get_users(driver):
|
||||
|
||||
def test_get_ntp_servers(driver, server):
|
||||
assert driver.get_ntp_servers() == {}
|
||||
server.override("GET", MANAGER + "/NetworkProtocol", 200,
|
||||
load("dmtf-public-telemetry", "/redfish/v1/Managers/1/NetworkProtocol"))
|
||||
server.override(
|
||||
"GET",
|
||||
MANAGER + "/NetworkProtocol",
|
||||
200,
|
||||
load("dmtf-public-telemetry", "/redfish/v1/Managers/1/NetworkProtocol"),
|
||||
)
|
||||
assert driver.get_ntp_servers() == {
|
||||
"0.pool.ntp.org": {}, "1.pool.ntp.org": {}, "time-a.nist.gov": {}}
|
||||
"0.pool.ntp.org": {},
|
||||
"1.pool.ntp.org": {},
|
||||
"time-a.nist.gov": {},
|
||||
}
|
||||
|
||||
|
||||
def test_get_snmp_information(driver):
|
||||
@@ -234,9 +278,10 @@ def test_get_power_state(driver):
|
||||
state = driver.get_power_state()
|
||||
assert_shape(state, models.PowerStateDict)
|
||||
# On: "On" is not offered; Redfish-only values (Nmi, ForceOn, FullPowerCycle) are dropped.
|
||||
assert state == {"state": "On",
|
||||
"actions": ["GracefulShutdown", "ForceOff", "GracefulRestart",
|
||||
"ForceRestart"]}
|
||||
assert state == {
|
||||
"state": "On",
|
||||
"actions": ["GracefulShutdown", "ForceOff", "GracefulRestart", "ForceRestart"],
|
||||
}
|
||||
|
||||
|
||||
def test_get_power_state_off(driver, server):
|
||||
@@ -252,8 +297,16 @@ def test_get_power_state_from_action_info(driver, server):
|
||||
del reset["ResetType@Redfish.AllowableValues"]
|
||||
reset["@Redfish.ActionInfo"] = SYSTEM + "/ResetActionInfo"
|
||||
server.override("GET", SYSTEM, 200, system)
|
||||
server.override("GET", SYSTEM + "/ResetActionInfo", 200, {"Parameters": [
|
||||
{"Name": "ResetType", "AllowableValues": ["On", "ForceOff", "PowerCycle"]}]})
|
||||
server.override(
|
||||
"GET",
|
||||
SYSTEM + "/ResetActionInfo",
|
||||
200,
|
||||
{
|
||||
"Parameters": [
|
||||
{"Name": "ResetType", "AllowableValues": ["On", "ForceOff", "PowerCycle"]}
|
||||
]
|
||||
},
|
||||
)
|
||||
assert driver.get_power_state()["actions"] == ["ForceOff", "PowerCycle"]
|
||||
|
||||
|
||||
@@ -263,7 +316,12 @@ def test_get_power_state_unlisted_values(driver, server):
|
||||
del system["Actions"]["#ComputerSystem.Reset"]["ResetType@Redfish.AllowableValues"]
|
||||
server.override("GET", SYSTEM, 200, system)
|
||||
assert driver.get_power_state()["actions"] == [
|
||||
"GracefulShutdown", "ForceOff", "GracefulRestart", "ForceRestart", "PowerCycle"]
|
||||
"GracefulShutdown",
|
||||
"ForceOff",
|
||||
"GracefulRestart",
|
||||
"ForceRestart",
|
||||
"PowerCycle",
|
||||
]
|
||||
|
||||
|
||||
def test_get_power_state_without_reset_action(driver, server):
|
||||
@@ -351,42 +409,73 @@ def test_get_hardware_inventory(driver):
|
||||
assert_shape(cpu, models.BmcCpuDict)
|
||||
# The FPGA is a processor but not a CPU.
|
||||
assert inv["cpus"] == [
|
||||
{"socket": "CPU 1", "model": "Multi-Core Intel(R) Xeon(R) processor 7xxx Series",
|
||||
"cores": 8, "threads": 16, "status": "OK"},
|
||||
{
|
||||
"socket": "CPU 1",
|
||||
"model": "Multi-Core Intel(R) Xeon(R) processor 7xxx Series",
|
||||
"cores": 8,
|
||||
"threads": 16,
|
||||
"status": "OK",
|
||||
},
|
||||
{"socket": "CPU 2", "model": "", "cores": None, "threads": None, "status": "Absent"},
|
||||
]
|
||||
|
||||
for dimm in inv["memory"]:
|
||||
assert_shape(dimm, models.BmcDimmDict)
|
||||
assert [d["slot"] for d in inv["memory"]] == ["DIMM 1", "DIMM 2", "DIMM 3", "DIMM 4"]
|
||||
assert inv["memory"][0] == {"slot": "DIMM 1", "size_bytes": 32768 * 1024 * 1024,
|
||||
"type": "DDR4", "speed_mts": None, "part_number": "",
|
||||
"serial_number": "", "status": "OK"}
|
||||
assert inv["memory"][0] == {
|
||||
"slot": "DIMM 1",
|
||||
"size_bytes": 32768 * 1024 * 1024,
|
||||
"type": "DDR4",
|
||||
"speed_mts": None,
|
||||
"part_number": "",
|
||||
"serial_number": "",
|
||||
"status": "OK",
|
||||
}
|
||||
assert inv["memory"][3]["status"] == "Absent"
|
||||
|
||||
for drive in inv["drives"]:
|
||||
assert_shape(drive, models.BmcDriveDict)
|
||||
# This system has SimpleStorage only.
|
||||
assert inv["drives"][0] == {"slot": "SATA Bay 1", "model": "3000GT8", "serial_number": "",
|
||||
"size_bytes": 8000000000000, "media": "", "protocol": "",
|
||||
"status": "OK"}
|
||||
assert inv["drives"][0] == {
|
||||
"slot": "SATA Bay 1",
|
||||
"model": "3000GT8",
|
||||
"serial_number": "",
|
||||
"size_bytes": 8000000000000,
|
||||
"media": "",
|
||||
"protocol": "",
|
||||
"status": "OK",
|
||||
}
|
||||
assert [d["status"] for d in inv["drives"]] == ["OK", "Warning", "Absent", "Absent"]
|
||||
|
||||
for psu in inv["power_supplies"]:
|
||||
assert_shape(psu, models.BmcPsuDict)
|
||||
assert inv["power_supplies"] == [
|
||||
{"slot": "Power Supply Bay 1", "model": "RKS-440DC", "serial_number": "3488247",
|
||||
"capacity_watts": 400, "status": "Warning"},
|
||||
{"slot": "Power Supply Bay 2", "model": "", "serial_number": "",
|
||||
"capacity_watts": None, "status": "Absent"},
|
||||
{
|
||||
"slot": "Power Supply Bay 1",
|
||||
"model": "RKS-440DC",
|
||||
"serial_number": "3488247",
|
||||
"capacity_watts": 400,
|
||||
"status": "Warning",
|
||||
},
|
||||
{
|
||||
"slot": "Power Supply Bay 2",
|
||||
"model": "",
|
||||
"serial_number": "",
|
||||
"capacity_watts": None,
|
||||
"status": "Absent",
|
||||
},
|
||||
]
|
||||
|
||||
for nic in inv["nics"]:
|
||||
assert_shape(nic, models.BmcNicDict)
|
||||
nics = {n["name"]: n for n in inv["nics"]}
|
||||
assert set(nics) == {"12446A3B0411", "12446A3B8890", "ToManager", "VLAN1"}
|
||||
assert nics["12446A3B0411"] == {"name": "12446A3B0411", "mac_address": "12:44:6a:3b:04:11",
|
||||
"speed_mbps": 1000, "link_up": True}
|
||||
assert nics["12446A3B0411"] == {
|
||||
"name": "12446A3B0411",
|
||||
"mac_address": "12:44:6a:3b:04:11",
|
||||
"speed_mbps": 1000,
|
||||
"link_up": True,
|
||||
}
|
||||
assert nics["ToManager"]["link_up"] is None
|
||||
|
||||
|
||||
@@ -396,25 +485,38 @@ def test_get_hardware_inventory_storage_drives(driver, server):
|
||||
del system["SimpleStorage"]
|
||||
system["Storage"] = {"@odata.id": SYSTEM + "/Storage"}
|
||||
server.override("GET", SYSTEM, 200, system)
|
||||
server.override("GET", SYSTEM + "/Storage", 200,
|
||||
load("dmtf-public-localstorage", SYSTEM + "/Storage"))
|
||||
server.override("GET", SYSTEM + "/Storage/1", 200,
|
||||
load("dmtf-public-localstorage", SYSTEM + "/Storage/1"))
|
||||
server.override(
|
||||
"GET", SYSTEM + "/Storage", 200, load("dmtf-public-localstorage", SYSTEM + "/Storage")
|
||||
)
|
||||
server.override(
|
||||
"GET", SYSTEM + "/Storage/1", 200, load("dmtf-public-localstorage", SYSTEM + "/Storage/1")
|
||||
)
|
||||
for drive in ("35D38F11ACEF7BD3", "3F5A8C54207B7233", "32ADF365C6C1B7BD", "3D58ECBC375FD9F2"):
|
||||
path = f"{CHASSIS}/Drives/{drive}"
|
||||
server.override("GET", path, 200, load("dmtf-public-localstorage", path))
|
||||
drives = driver.get_hardware_inventory()["drives"]
|
||||
assert len(drives) == 4
|
||||
assert drives[0] == {"slot": "Drive Sample", "model": "C123", "serial_number": "1234567",
|
||||
"size_bytes": 899527000064, "media": "HDD", "protocol": "SAS",
|
||||
"status": "OK"}
|
||||
assert drives[0] == {
|
||||
"slot": "Drive Sample",
|
||||
"model": "C123",
|
||||
"serial_number": "1234567",
|
||||
"size_bytes": 899527000064,
|
||||
"media": "HDD",
|
||||
"protocol": "SAS",
|
||||
"status": "OK",
|
||||
}
|
||||
|
||||
|
||||
def test_get_hardware_inventory_legacy_power(driver, server):
|
||||
server.override("GET", CHASSIS, 200, without(CHASSIS, "PowerSubsystem"))
|
||||
assert driver.get_hardware_inventory()["power_supplies"] == [
|
||||
{"slot": "Power Supply Bay", "model": "499253-B21", "serial_number": "1Z0000001",
|
||||
"capacity_watts": 800, "status": "Warning"},
|
||||
{
|
||||
"slot": "Power Supply Bay",
|
||||
"model": "499253-B21",
|
||||
"serial_number": "1Z0000001",
|
||||
"capacity_watts": 800,
|
||||
"status": "Warning",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -423,14 +525,30 @@ def test_get_event_log(driver):
|
||||
for entry in log:
|
||||
assert_shape(entry, models.EventLogEntryDict)
|
||||
assert log == [
|
||||
{"id": SYSTEM_LOG + "/3", "timestamp": ts("2012-03-07T14:46:00Z"),
|
||||
"severity": "Warning", "message": "Fan redundancy lost"},
|
||||
{"id": SYSTEM_LOG + "/2", "timestamp": ts("2012-03-07T14:45:00Z"),
|
||||
"severity": "Critical", "message": "Temperature threshold exceeded"},
|
||||
{"id": SYSTEM_LOG + "/1", "timestamp": ts("2012-03-07T14:44:00Z"),
|
||||
"severity": "Critical", "message": "Temperature threshold exceeded"},
|
||||
{"id": MANAGER + "/LogServices/Log/Entries/1", "timestamp": ts("2012-03-07T14:44:00Z"),
|
||||
"severity": "Critical", "message": "System May be Melting"},
|
||||
{
|
||||
"id": SYSTEM_LOG + "/3",
|
||||
"timestamp": ts("2012-03-07T14:46:00Z"),
|
||||
"severity": "Warning",
|
||||
"message": "Fan redundancy lost",
|
||||
},
|
||||
{
|
||||
"id": SYSTEM_LOG + "/2",
|
||||
"timestamp": ts("2012-03-07T14:45:00Z"),
|
||||
"severity": "Critical",
|
||||
"message": "Temperature threshold exceeded",
|
||||
},
|
||||
{
|
||||
"id": SYSTEM_LOG + "/1",
|
||||
"timestamp": ts("2012-03-07T14:44:00Z"),
|
||||
"severity": "Critical",
|
||||
"message": "Temperature threshold exceeded",
|
||||
},
|
||||
{
|
||||
"id": MANAGER + "/LogServices/Log/Entries/1",
|
||||
"timestamp": ts("2012-03-07T14:44:00Z"),
|
||||
"severity": "Critical",
|
||||
"message": "System May be Melting",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -441,11 +559,18 @@ def test_get_event_log_limit(driver):
|
||||
|
||||
def test_get_event_log_timestamps(driver, server):
|
||||
entries = SYSTEM_LOG + "?$skiptoken=2"
|
||||
server.override("GET", entries, 200, {"Members": [
|
||||
{"Id": "3", "Created": "2026-10-13T08:00:00+02:00", "Message": "offset"},
|
||||
{"Id": "4", "Message": "no time", "Severity": "Informational"},
|
||||
{"Id": "5", "Created": "garbage", "Message": "bad time"},
|
||||
]})
|
||||
server.override(
|
||||
"GET",
|
||||
entries,
|
||||
200,
|
||||
{
|
||||
"Members": [
|
||||
{"Id": "3", "Created": "2026-10-13T08:00:00+02:00", "Message": "offset"},
|
||||
{"Id": "4", "Message": "no time", "Severity": "Informational"},
|
||||
{"Id": "5", "Created": "garbage", "Message": "bad time"},
|
||||
]
|
||||
},
|
||||
)
|
||||
log = {e["message"]: e for e in driver.get_event_log()}
|
||||
assert log["offset"]["timestamp"] == ts("2026-10-13T06:00:00Z")
|
||||
assert log["no time"]["timestamp"] is None
|
||||
@@ -482,8 +607,12 @@ def test_get_management_protocols(driver):
|
||||
|
||||
|
||||
def test_get_management_protocols_missing(driver, server):
|
||||
server.override("GET", MANAGER + "/NetworkProtocol", 200,
|
||||
{"HTTPS": {"ProtocolEnabled": True, "Port": 443}, "IPMI": {"Port": 623}})
|
||||
server.override(
|
||||
"GET",
|
||||
MANAGER + "/NetworkProtocol",
|
||||
200,
|
||||
{"HTTPS": {"ProtocolEnabled": True, "Port": 443}, "IPMI": {"Port": 623}},
|
||||
)
|
||||
protocols = driver.get_management_protocols()
|
||||
assert protocols["https"] == {"enabled": True, "port": 443}
|
||||
assert protocols["ipmi"] == {"enabled": False, "port": 623}
|
||||
@@ -493,8 +622,9 @@ def test_get_management_protocols_missing(driver, server):
|
||||
def _unlicensed(server):
|
||||
# A Supermicro X10 refuses Redfish entirely until a license is activated.
|
||||
x10 = Path(__file__).parent / "fixtures" / "supermicro-x10drl-i-3.91"
|
||||
server.override("GET", "/redfish/v1/", 403,
|
||||
json.loads((x10 / "service_root_403.json").read_text()))
|
||||
server.override(
|
||||
"GET", "/redfish/v1/", 403, json.loads((x10 / "service_root_403.json").read_text())
|
||||
)
|
||||
d = RedfishDriver("bmc.example.net", "netork", "s3cret", optional_args={"verify_ssl": False})
|
||||
d.open()
|
||||
return d
|
||||
@@ -506,15 +636,24 @@ def test_unlicensed_bmc_still_opens(server):
|
||||
d = _unlicensed(server)
|
||||
assert d.is_alive() == {"is_alive": True}
|
||||
assert d.get_facts() == {
|
||||
"uptime": -1.0, "vendor": "", "model": "", "hostname": "bmc.example.net",
|
||||
"fqdn": "bmc.example.net", "os_version": "", "serial_number": "", "interface_list": [],
|
||||
"uptime": -1.0,
|
||||
"vendor": "",
|
||||
"model": "",
|
||||
"hostname": "bmc.example.net",
|
||||
"fqdn": "bmc.example.net",
|
||||
"os_version": "",
|
||||
"serial_number": "",
|
||||
"interface_list": [],
|
||||
}
|
||||
|
||||
|
||||
def test_unlicensed_bmc_names_every_license_it_asks_for_as_missing(server):
|
||||
licenses = _unlicensed(server).get_licenses()
|
||||
assert [(lic["name"], lic["status"]) for lic in licenses] == [
|
||||
("SUM", "missing"), ("DCMS", "missing"), ("OOB", "missing")]
|
||||
("SUM", "missing"),
|
||||
("DCMS", "missing"),
|
||||
("OOB", "missing"),
|
||||
]
|
||||
# Refused altogether: nothing is withheld in part.
|
||||
assert all("withholds" not in lic and "Not licensed" in lic["detail"] for lic in licenses)
|
||||
|
||||
|
||||
Reference in new issue
Block a user