From 3114e5412988e6e61a3ad448829592cc85910879 Mon Sep 17 00:00:00 2001 From: Alan Bounds Date: Mon, 12 Jan 2026 08:22:09 -0600 Subject: [PATCH] PUC-1415: patch BMC disk functionality for HP smart controller. --- .../bmc_chassis_info/dl380_bmc_disk.json | 45 ++++++++++++++ .../{bmc_disk.json => r7615_bmc_disk.json} | 0 .../tests/test_bmc_disk.py | 59 +++++++++++++++---- .../understack_workflows/bmc.py | 4 ++ .../understack_workflows/bmc_disk.py | 54 +++++++++++++---- 5 files changed, 140 insertions(+), 22 deletions(-) create mode 100644 python/understack-workflows/tests/json_samples/bmc_chassis_info/dl380_bmc_disk.json rename python/understack-workflows/tests/json_samples/bmc_chassis_info/{bmc_disk.json => r7615_bmc_disk.json} (100%) diff --git a/python/understack-workflows/tests/json_samples/bmc_chassis_info/dl380_bmc_disk.json b/python/understack-workflows/tests/json_samples/bmc_chassis_info/dl380_bmc_disk.json new file mode 100644 index 000000000..8e02d4cf0 --- /dev/null +++ b/python/understack-workflows/tests/json_samples/bmc_chassis_info/dl380_bmc_disk.json @@ -0,0 +1,45 @@ +{ + "@odata.context": "/redfish/v1/$metadata#Systems/Members/1/SmartStorage/ArrayControllers/Members/3/DiskDrives/Members/$entity", + "@odata.id": "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/3/DiskDrives/4/", + "@odata.type": "#HpSmartStorageDiskDrive.1.3.0.HpSmartStorageDiskDrive", + "BlockSizeBytes": 512, + "CapacityGB": 600, + "CapacityLogicalBlocks": 1172123568, + "CapacityMiB": 572325, + "CarrierApplicationVersion": "11", + "CarrierAuthenticationStatus": "OK", + "CurrentTemperatureCelsius": 35, + "Description": "HP Smart Storage Disk Drive View", + "DiskDriveStatusReasons": [ + "None" + ], + "EncryptedDrive": false, + "FirmwareVersion": { + "Current": { + "VersionString": "HPD4" + } + }, + "Id": "4", + "InterfaceSpeedMbps": 12000, + "InterfaceType": "SAS", + "Location": "1I:1:1", + "LocationFormat": "ControllerPort:Box:Bay", + "MaximumTemperatureCelsius": 48, + "MediaType": "HDD", + "Model": "EH0600JEDHE", + "Name": "HpSmartStorageDiskDrive", + "PowerOnHours": null, + "RotationalSpeedRpm": 15000, + "SSDEnduranceUtilizationPercentage": null, + "SerialNumber": "S7M0EG2D0000K60456LU", + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "Type": "HpSmartStorageDiskDrive.1.3.0", + "links": { + "self": { + "href": "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/3/DiskDrives/4/" + } + } +} diff --git a/python/understack-workflows/tests/json_samples/bmc_chassis_info/bmc_disk.json b/python/understack-workflows/tests/json_samples/bmc_chassis_info/r7615_bmc_disk.json similarity index 100% rename from python/understack-workflows/tests/json_samples/bmc_chassis_info/bmc_disk.json rename to python/understack-workflows/tests/json_samples/bmc_chassis_info/r7615_bmc_disk.json diff --git a/python/understack-workflows/tests/test_bmc_disk.py b/python/understack-workflows/tests/test_bmc_disk.py index d66ec1bf2..b1c6e266f 100644 --- a/python/understack-workflows/tests/test_bmc_disk.py +++ b/python/understack-workflows/tests/test_bmc_disk.py @@ -5,34 +5,52 @@ from understack_workflows.bmc_disk import Disk -TESTED_DISK_PATH = "/redfish/v1/Systems/System.Embedded.1/Storage/RAID.SL.1-1/Drives/Disk.Bay.1:Enclosure.Internal.0-1:RAID.SL.1-1" # noqa: E501 +DELL_TEST_DISK_PATH = "/redfish/v1/Systems/System.Embedded.1/Storage/RAID.SL.1-1/Drives/Disk.Bay.1:Enclosure.Internal.0-1:RAID.SL.1-1" # noqa: E501 +HP_TEST_DISK_PATH = ( + "/redfish/v1/Systems/1/SmartStorage/ArrayControllers/3/DiskDrives/4/" +) @pytest.fixture -def mock_disk_data(): - with open("tests/json_samples/bmc_chassis_info/bmc_disk.json") as f: +def mock_dell_disk_data(): + with open("tests/json_samples/bmc_chassis_info/r7615_bmc_disk.json") as f: return json.load(f) @pytest.fixture -def mock_bmc(mock_disk_data, mocker: MockerFixture): +def mock_hp_disk_data(): + with open("tests/json_samples/bmc_chassis_info/dl380_bmc_disk.json") as f: + return json.load(f) + + +@pytest.fixture +def mock_dell_bmc(mock_dell_disk_data, mocker: MockerFixture): + mock_bmc = mocker.Mock() + mock_bmc.get_manufacturer.return_value = "dell inc." + mock_bmc.redfish_request.return_value = mock_dell_disk_data + return mock_bmc + + +@pytest.fixture +def mock_hp_bmc(mock_hp_disk_data, mocker: MockerFixture): mock_bmc = mocker.Mock() - mock_bmc.redfish_request.return_value = mock_disk_data + mock_bmc.get_manufacturer.return_value = "hpe" + mock_bmc.redfish_request.return_value = mock_hp_disk_data return mock_bmc -def test_disk_from_path(mock_bmc): - disk = Disk.from_path(mock_bmc, TESTED_DISK_PATH) +def test_dell_disk_from_path(mock_dell_bmc): + disk = Disk.from_path(mock_dell_bmc, DELL_TEST_DISK_PATH) assert isinstance(disk, Disk) -def test_disk_repr(mock_bmc): - disk = Disk.from_path(mock_bmc, TESTED_DISK_PATH) +def test_dell_disk_repr(mock_dell_bmc): + disk = Disk.from_path(mock_dell_bmc, DELL_TEST_DISK_PATH) assert repr(disk) == disk.name -def test_disk_attributes(mock_bmc): - disk = Disk.from_path(mock_bmc, TESTED_DISK_PATH) +def test_dell_disk_attributes(mock_dell_bmc): + disk = Disk.from_path(mock_dell_bmc, DELL_TEST_DISK_PATH) assert disk.media_type == "SSD" assert disk.model == "MTFDDAK480TDS" assert disk.name == "Solid State Disk 0:1:1" @@ -40,6 +58,25 @@ def test_disk_attributes(mock_bmc): assert disk.capacity_bytes == 479559942144 +def test_hp_disk_from_path(mock_hp_bmc): + disk = Disk.from_path(mock_hp_bmc, HP_TEST_DISK_PATH) + assert isinstance(disk, Disk) + + +def test_hp_disk_repr(mock_hp_bmc): + disk = Disk.from_path(mock_hp_bmc, HP_TEST_DISK_PATH) + assert repr(disk) == disk.name + + +def test_hp_disk_attributes(mock_hp_bmc): + disk = Disk.from_path(mock_hp_bmc, HP_TEST_DISK_PATH) + assert disk.media_type == "HDD" + assert disk.model == "EH0600JEDHE" + assert disk.name == "1I:1:1" + assert disk.health == "OK" + assert disk.capacity_bytes == 600000000000 + + def test_disk_gb_conversion(): disk = Disk( media_type="SSD", diff --git a/python/understack-workflows/understack_workflows/bmc.py b/python/understack-workflows/understack_workflows/bmc.py index e4ec1bf14..ca37e9131 100644 --- a/python/understack-workflows/understack_workflows/bmc.py +++ b/python/understack-workflows/understack_workflows/bmc.py @@ -88,6 +88,10 @@ def get_manager_path(self): _result = self.redfish_request("/redfish/v1/Managers/") return _result["Members"][0]["@odata.id"].rstrip("/") + def get_manufacturer(self) -> str: + """Read and return Manufacturer.""" + return self.redfish_request(self.system_path)["Manufacturer"].lower() + def get_user_accounts(self, token: str | None = None) -> list[dict]: """A vendor agnostic approach to crawling the API for BMC accounts.""" path = self.base_path diff --git a/python/understack-workflows/understack_workflows/bmc_disk.py b/python/understack-workflows/understack_workflows/bmc_disk.py index d5e004a2d..ccc76947e 100644 --- a/python/understack-workflows/understack_workflows/bmc_disk.py +++ b/python/understack-workflows/understack_workflows/bmc_disk.py @@ -27,31 +27,63 @@ def capacity_gb(self) -> int: """Capacity Math.""" return math.ceil(self.capacity_bytes / 10**9) + @staticmethod + def get_capacity_bytes(number) -> int: + """Calculate capacity_bytes.""" + return math.ceil(number * 10**9) + @staticmethod def from_path(bmc: Bmc, path: str): """Disk path request.""" disk_data = bmc.redfish_request(path) - + bmc_type = bmc.get_manufacturer() + if "dell" in bmc_type: + _bytes = disk_data["CapacityBytes"] + _name = disk_data["Name"] + else: + _bytes = Disk.get_capacity_bytes(disk_data["CapacityGB"]) + _name = disk_data["Location"] return Disk( media_type=disk_data["MediaType"], model=disk_data["Model"], - name=disk_data["Name"], + name=_name, health=disk_data.get("Status", {}).get("Health", "Unknown"), - capacity_bytes=disk_data["CapacityBytes"], + capacity_bytes=_bytes, ) def physical_disks(bmc: Bmc) -> list[Disk]: """Retrieve list of physical physical_disks.""" try: - storage_member_paths = [ - member["@odata.id"] - for member in bmc.redfish_request(bmc.system_path + "/Storage")["Members"] - ] - disks = [ - bmc.redfish_request(drive_path)["Drives"] - for drive_path in storage_member_paths - ] + bmc_type = bmc.get_manufacturer() + if "dell" in bmc_type: + path = "/Storage" + storage_member_paths = [ + member["@odata.id"] + for member in bmc.redfish_request(bmc.system_path + path)["Members"] + ] + disks = [ + bmc.redfish_request(drive_path)["Drives"] + for drive_path in storage_member_paths + ] + disk_paths = [disk for sublist in disks for disk in sublist] + disk_list = [ + Disk.from_path(bmc, path=disk["@odata.id"]) for disk in disk_paths + ] + else: + path = "/SmartStorage/ArrayControllers" + storage_controller_paths = [ + member["@odata.id"] + for member in bmc.redfish_request(bmc.system_path + path)["Members"] + ] + storage_member_paths = [ + bmc.redfish_request(drive_path)["links"]["PhysicalDrives"]["href"] + for drive_path in storage_controller_paths + ] + disks = [ + bmc.redfish_request(drive_path)["Members"] + for drive_path in storage_member_paths + ] disk_paths = [disk for sublist in disks for disk in sublist] disk_list = [Disk.from_path(bmc, path=disk["@odata.id"]) for disk in disk_paths] logger.debug("Retrieved %d disks.", len(disk_list))