diff --git a/.gitignore b/.gitignore index f616ac9d9..66deb1b36 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ docs/workflows/ # devbox's envrc .envrc .direnv +.vscode # mkdocs site output /site/ diff --git a/python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Managers_.json b/python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Managers_.json new file mode 100644 index 000000000..0f0a4930a --- /dev/null +++ b/python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Managers_.json @@ -0,0 +1,13 @@ +{ + "@odata.context": "/redfish/v1/$metadata#ManagerCollection.ManagerCollection", + "@odata.id": "/redfish/v1/Managers", + "@odata.type": "#ManagerCollection.ManagerCollection", + "Description": "BMC", + "Members": [ + { + "@odata.id": "/redfish/v1/Managers/iDRAC.Embedded.1" + } + ], + "Members@odata.count": 1, + "Name": "Manager" +} diff --git a/python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Managers_iDRAC.Embedded.1_EthernetInterfaces_.json b/python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Managers_iDRAC.Embedded.1_EthernetInterfaces_.json new file mode 100644 index 000000000..337f353ee --- /dev/null +++ b/python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Managers_iDRAC.Embedded.1_EthernetInterfaces_.json @@ -0,0 +1,13 @@ +{ + "@odata.context": "/redfish/v1/$metadata#EthernetInterfaceCollection.EthernetInterfaceCollection", + "@odata.id": "/redfish/v1/Managers/iDRAC.Embedded.1/EthernetInterfaces", + "@odata.type": "#EthernetInterfaceCollection.EthernetInterfaceCollection", + "Description": "Collection of EthernetInterfaces for this Manager", + "Members": [ + { + "@odata.id": "/redfish/v1/Managers/iDRAC.Embedded.1/EthernetInterfaces/NIC.1" + } + ], + "Members@odata.count": 1, + "Name": "Ethernet Network Interface Collection" +} diff --git a/python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Systems_.json b/python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Systems_.json new file mode 100644 index 000000000..26df41be9 --- /dev/null +++ b/python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Systems_.json @@ -0,0 +1,13 @@ +{ + "@odata.context": "/redfish/v1/$metadata#ComputerSystemCollection.ComputerSystemCollection", + "@odata.id": "/redfish/v1/Systems", + "@odata.type": "#ComputerSystemCollection.ComputerSystemCollection", + "Description": "Collection of Computer Systems", + "Members": [ + { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1" + } + ], + "Members@odata.count": 1, + "Name": "Computer System Collection" +} diff --git a/python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Systems_System.Embedded.1_.json b/python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Systems_System.Embedded.1.json similarity index 100% rename from python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Systems_System.Embedded.1_.json rename to python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Systems_System.Embedded.1.json diff --git a/python/understack-workflows/tests/test_bmc_chassis_info.py b/python/understack-workflows/tests/test_bmc_chassis_info.py index 83d5d52a0..0efb6d61a 100644 --- a/python/understack-workflows/tests/test_bmc_chassis_info.py +++ b/python/understack-workflows/tests/test_bmc_chassis_info.py @@ -12,6 +12,7 @@ class FakeBmc(Bmc): def __init__(self, fixtures): self.fixtures = fixtures self.ip_address = "1.2.3.4" + super().__init__(ip_address=self.ip_address) def redfish_request(self, path: str, *_args, **_kw) -> dict: path = path.replace("/", "_") + ".json" diff --git a/python/understack-workflows/understack_workflows/bmc.py b/python/understack-workflows/understack_workflows/bmc.py index 9cf703d7c..f193a3cab 100644 --- a/python/understack-workflows/understack_workflows/bmc.py +++ b/python/understack-workflows/understack_workflows/bmc.py @@ -1,3 +1,5 @@ +# pylint: disable=E1131,C0103 + import logging import os from dataclasses import dataclass @@ -18,21 +20,54 @@ } +class RedfishRequestError(Exception): + """Handle Exceptions from Redfish handler.""" + + @dataclass class Bmc: """Represent DRAC/iLo and know how to perform low-level query on it.""" - ip_address: str - username: str - password: str + def __init__( + self, ip_address: str, password: str | None = None, username: str = "root" + ) -> None: + """Initialize BMC data class.""" + self.ip_address = ip_address + self.username = username + self.password = password if password else "" + self._system_path: str | None = None + self._manager_path: str | None = None + + @property + def system_path(self) -> str: + """Read System path from BMC.""" + self._system_path = self._system_path or self.get_system_path() + return self._system_path + + @property + def manager_path(self) -> str: + """Read Manager path from BMC.""" + self._manager_path = self._manager_path or self.get_manager_path() + return self._manager_path def __str__(self): """Stringify without password being printed.""" return f"BMC {self.url()}" def url(self): + """Return base redfish URL.""" return f"https://{self.ip_address}" + def get_system_path(self): + """Get System Path.""" + _result = self.redfish_request("/redfish/v1/Systems/") + return _result["Members"][0]["@odata.id"].rstrip("/") + + def get_manager_path(self): + """Get Manager Path.""" + _result = self.redfish_request("/redfish/v1/Managers/") + return _result["Members"][0]["@odata.id"].rstrip("/") + def redfish_request( self, path: str, @@ -41,6 +76,7 @@ def redfish_request( verify: bool = False, timeout: int = 30, ) -> dict: + """Request a path via Redfish against the Bmc.""" url = f"{self.url()}{path}" r = requests.request( method, @@ -52,9 +88,9 @@ def redfish_request( headers=HEADERS, ) if r.status_code >= 400: - raise RedfishError( + raise RedfishRequestError( f"BMC communications failure HTTP {r.status_code} " - f"{r.reason} from {url} - {r.text}" + + f"{r.reason} from {url} - {r.text}" ) if r.text: return r.json() @@ -62,6 +98,7 @@ def redfish_request( return {} def sushy(self): + """Return a Sushy interface to BMC.""" return Sushy( self.url(), username=self.username, password=self.password, verify=False ) @@ -72,8 +109,8 @@ def bmc_for_ip_address( ) -> Bmc: """Factory method to create a Bmc object with a standard password. - If no password is supplied then we use the conventional BMC standard - password which is derived from the IP address and the BMC_MASTER secret key. + If no password is supplied then we use a conventional BMC standard one + which is derived from the IP address and the BMC_MASTER secret key. If no username is supplied then the username "root" is used. """ @@ -86,7 +123,3 @@ def bmc_for_ip_address( password=password, username=username, ) - - -class RedfishError(Exception): - pass diff --git a/python/understack-workflows/understack_workflows/bmc_bios.py b/python/understack-workflows/understack_workflows/bmc_bios.py index c2081750f..30b37d22a 100644 --- a/python/understack-workflows/understack_workflows/bmc_bios.py +++ b/python/understack-workflows/understack_workflows/bmc_bios.py @@ -1,13 +1,12 @@ from understack_workflows.bmc import Bmc -from understack_workflows.bmc import RedfishError +from understack_workflows.bmc import RedfishRequestError from understack_workflows.helpers import setup_logger logger = setup_logger(__name__) -REDFISH_BIOS_PATH = "/redfish/v1/Systems/System.Embedded.1/Bios" - def required_bios_settings(pxe_interface: str) -> dict: + """Return adjusted Bios settings map for BMC.""" return { "PxeDev1EnDis": "Enabled", "PxeDev1Interface": pxe_interface, @@ -27,7 +26,7 @@ def update_dell_bios_settings(bmc: Bmc, pxe_interface="NIC.Integrated.1-1") -> d Returns the changes that were made """ - current_settings = bmc.redfish_request(REDFISH_BIOS_PATH)["Attributes"] + current_settings = bmc.redfish_request(bmc.system_path + "/Bios")["Attributes"] required_settings = required_bios_settings(pxe_interface) required_changes = { @@ -39,22 +38,23 @@ def update_dell_bios_settings(bmc: Bmc, pxe_interface="NIC.Integrated.1-1") -> d if required_changes: logger.info("%s Updating BIOS settings: %s", bmc, required_changes) patch_bios_settings(bmc, required_changes) - logger.info("%s BIOS settings will be updated on next server boot", bmc) + logger.info("%s BIOS settings will be updated on next server boot.", bmc) else: - logger.info("%s all required BIOS settings present and correct", bmc) + logger.info("%s all required BIOS settings present and correct.", bmc) return required_changes def patch_bios_settings(bmc: Bmc, new_settings: dict): - path = f"{REDFISH_BIOS_PATH}/Settings" + """Apply Bios settings to BMC.""" + settings_path = f"{bmc.system_path}/Bios/Settings" payload = { "@Redfish.SettingsApplyTime": {"ApplyTime": "OnReset"}, "Attributes": new_settings, } try: - bmc.redfish_request(path, payload=payload, method="PATCH") - except RedfishError as e: + bmc.redfish_request(settings_path, payload=payload, method="PATCH") + except RedfishRequestError as e: if "Pending configuration values" in repr(e): logger.info("%s BIOS settings job already queued, ignoring.", bmc) return diff --git a/python/understack-workflows/understack_workflows/bmc_chassis_info.py b/python/understack-workflows/understack_workflows/bmc_chassis_info.py index 001915071..8b03dec6b 100644 --- a/python/understack-workflows/understack_workflows/bmc_chassis_info.py +++ b/python/understack-workflows/understack_workflows/bmc_chassis_info.py @@ -1,3 +1,5 @@ +# pylint: disable=E1131,C0103 + import re from dataclasses import dataclass from ipaddress import IPv4Address @@ -12,6 +14,8 @@ @dataclass(frozen=True) class InterfaceInfo: + """Interface Information Data Class.""" + name: str description: str mac_address: str @@ -26,6 +30,8 @@ class InterfaceInfo: @dataclass(frozen=True) class ChassisInfo: + """Chassis Information Data Class.""" + manufacturer: str model_number: str serial_number: str @@ -38,10 +44,12 @@ class ChassisInfo: @property def bmc_interface(self) -> InterfaceInfo: + """BMC Interface response.""" return self.interfaces[0] @property def bmc_hostname(self) -> str: + """BMC Hostname response.""" return str(self.bmc_interface.hostname) @property @@ -54,16 +62,6 @@ def neighbors(self) -> set: } -REDFISH_SYSTEM_ENDPOINT = "/redfish/v1/Systems/System.Embedded.1/" -REDFISH_ETHERNET_ENDPOINT = f"{REDFISH_SYSTEM_ENDPOINT}EthernetInterfaces/" -REDFISH_CONNECTION_ENDPOINT = ( - f"{REDFISH_SYSTEM_ENDPOINT}NetworkPorts/Oem/Dell/DellSwitchConnections/" -) -REDFISH_DRAC_NIC_ENDPOINT = ( - "/redfish/v1/Managers/iDRAC.Embedded.1/EthernetInterfaces/NIC.1" -) - - def chassis_info(bmc: Bmc) -> ChassisInfo: """Query DRAC for basic system info via redfish. @@ -72,7 +70,7 @@ def chassis_info(bmc: Bmc) -> ChassisInfo: MemorySummary.TotalSystemMemoryGiB """ - chassis_data = bmc.redfish_request(REDFISH_SYSTEM_ENDPOINT) + chassis_data = bmc.redfish_request(bmc.system_path) interfaces = interface_data(bmc) return ChassisInfo( @@ -89,18 +87,25 @@ def chassis_info(bmc: Bmc) -> ChassisInfo: def interface_data(bmc: Bmc) -> list[InterfaceInfo]: - interfaces = [bmc_interface(bmc)] + in_band_interfaces(bmc) - lldp = lldp_data_by_name(bmc) - return [combine_lldp(lldp, interface) for interface in interfaces] + """Interface parsed from BMC outputs.""" + bmc_interface_info = bmc_interface(bmc) + interfaces = [bmc_interface_info] + in_band_interfaces(bmc) + if get_system_vendor(bmc) == "Dell": + lldp = lldp_data_by_name(bmc) + return [combine_lldp(lldp, interface) for interface in interfaces] + else: + return [combine_lldp({}, interface) for interface in interfaces] def combine_lldp(lldp, interface) -> InterfaceInfo: + """Combined response, LLDP and Interface data.""" name = interface["name"] alternate_name = f"{name}-1" lldp_entry = lldp.get(name, lldp.get(alternate_name, {})) if not lldp_entry: logger.info( - "LLDP info from BMC is missing for %s or %s, we only have LLDP info for %s", + "LLDP info from BMC is missing for %s or %s," + "we only have LLDP info for %s", name, alternate_name, list(lldp.keys()), @@ -110,13 +115,25 @@ def combine_lldp(lldp, interface) -> InterfaceInfo: def bmc_interface(bmc) -> dict: """Retrieve DRAC BMC interface info via redfish API.""" - data = bmc.redfish_request(REDFISH_DRAC_NIC_ENDPOINT) - ipv4_address, ipv4_gateway, dhcp = parse_ipv4(data["IPv4Addresses"]) + _interface = bmc.redfish_request(bmc.manager_path + "/EthernetInterfaces/")[ + "Members" + ][0]["@odata.id"] + _data = bmc.redfish_request(_interface) + ipv4_address, ipv4_gateway, dhcp = parse_ipv4(_data["IPv4Addresses"]) + data = {k.lower(): v for k, v in _data.items()} + host_name = data.get("hostname") + bmc_name = "iDRAC" if get_system_vendor(bmc) == "Dell" else "iLO" + bmc_description = ( + "Dedicated iDRAC interface" if (bmc_name == "iDRAC") else data.get("name") + ) + bmc_mac = data.get("macaddress") return { - "name": "iDRAC", - "description": "Dedicated iDRAC interface", - "mac_address": data["MACAddress"].upper(), - "hostname": data["HostName"], + "name": bmc_name, + "description": bmc_description, + "mac_address": normalise_mac(bmc_mac) + if (bmc_mac and bmc_mac != "") + else bmc_mac, + "hostname": host_name, "ipv4_address": ipv4_address, "ipv4_gateway": ipv4_gateway, "dhcp": dhcp, @@ -156,20 +173,21 @@ def parse_ipv4( def in_band_interfaces(bmc: Bmc) -> list[dict]: """A Collection of Ethernet Interfaces for this System. - If the redfish list of Ethernet Interfaces includes "foo" as well as "foo-1" - then we disregard the latter. The -1 suffix is used for "partitions" of a + If the redfish list of Ethernet Interfaces includes "foo" and also "foo-1" + then we disregard the latter. The -1 suffix is used for "partitions" of a physical interface. It seems to vary by device whether these are included in redfish output at all, and if they are, whether the mac address information is present in the base interface, the partition, or both. + Excludes removal of devices where no reference of -1 exists. """ - index_data = bmc.redfish_request(REDFISH_ETHERNET_ENDPOINT) + index_data = bmc.redfish_request(bmc.system_path + "/EthernetInterfaces/") urls = [member["@odata.id"] for member in index_data["Members"]] - - return [ + interface_results = [ interface_detail(bmc, url) for url in urls - if re.sub(r"-\d$", "", url) not in urls + if (not re.search(r"-\d$", url)) or (re.sub(r"-\d$", "", url) not in urls) ] + return [interface for interface in interface_results] def interface_detail(bmc, path) -> dict: @@ -184,11 +202,16 @@ def interface_detail(bmc, path) -> dict: InterfaceEnabled, LinkStatus, Status.Health, State.Enabled, SpeedMbps """ data = bmc.redfish_request(path) + _data = {k.lower(): v for k, v in data.items()} + name = _data.get("name") + hostname = _data.get("hostname", "") + description = _data.get("description", "") + mac_addr = _data.get("macaddress", "") return { "name": server_interface_name(data["Id"]), - "description": data["Description"], - "mac_address": data["MACAddress"].upper(), - "hostname": data["HostName"], + "description": description if description != "" else name, + "mac_address": normalise_mac(mac_addr) if mac_addr != "" else mac_addr, + "hostname": hostname, } @@ -213,7 +236,7 @@ def lldp_data_by_name(bmc) -> dict: The MAC address is from the remote switch - it matches the base MAC that is found in `show version` output on a 2960, on N9k it is one of two things: - 1) on a switch configured with `lldp chassis-id switch` this will be the the + 1) On a switch configured with `lldp chassis-id switch` this will be the mac you see in `show mac address-table static | in Lo0` or `sho vdc detail` commands. Note that this lldp configuration option is only available starting in Nexus version 10.2(3)F @@ -223,8 +246,10 @@ def lldp_data_by_name(bmc) -> dict: 11:11:11:11:11:00 then the LLDP mac address seen on port e1/2 would be 11:11:11:11:11:02 """ - ports = bmc.redfish_request(REDFISH_CONNECTION_ENDPOINT)["Members"] - + _data = bmc.redfish_request( + bmc.system_path + "/NetworkPorts/Oem/Dell/DellSwitchConnections/" + ) + ports = _data["Members"] return {server_interface_name(port["Id"]): parse_lldp_port(port) for port in ports} @@ -251,15 +276,27 @@ def parse_lldp_port(port_data: dict[str, str]) -> dict: } +def get_system_vendor(bmc: Bmc) -> str: + """Read Vendor name from Oem reference.""" + _data = bmc.redfish_request(bmc.system_path) + vendor = [key for key in _data["Oem"]][0] + return normalise_manufacturer(vendor) + + def normalise_mac(mac: str) -> str: + """Format mac address to match standard.""" return ":".join(f"{int(n, 16):02X}" for n in mac.split(":")) def server_interface_name(name: str) -> str: + """Return 'iDRAC' in place of embedded name.""" return "iDRAC" if name.startswith("iDRAC.Embedded") else name def normalise_manufacturer(name: str) -> str: + """Return a standard name for Manufacturer.""" if "DELL" in name.upper(): return "Dell" + elif "HP" in name.upper(): + return "HP" raise ValueError(f"Server manufacturer {name} not supported") diff --git a/python/understack-workflows/understack_workflows/bmc_disk.py b/python/understack-workflows/understack_workflows/bmc_disk.py index f65ad488b..d5e004a2d 100644 --- a/python/understack-workflows/understack_workflows/bmc_disk.py +++ b/python/understack-workflows/understack_workflows/bmc_disk.py @@ -2,17 +2,16 @@ from dataclasses import dataclass from understack_workflows.bmc import Bmc -from understack_workflows.bmc import RedfishError +from understack_workflows.bmc import RedfishRequestError from understack_workflows.helpers import setup_logger logger = setup_logger(__name__) -REDFISH_DISKS_PATH = "/redfish/v1/Systems/System.Embedded.1/Storage/RAID.SL.1-1" - - @dataclass(frozen=True) class Disk: + """Disk Data Class.""" + media_type: str model: str name: str @@ -25,10 +24,12 @@ def __repr__(self) -> str: @property def capacity_gb(self) -> int: + """Capacity Math.""" return math.ceil(self.capacity_bytes / 10**9) @staticmethod def from_path(bmc: Bmc, path: str): + """Disk path request.""" disk_data = bmc.redfish_request(path) return Disk( @@ -43,11 +44,19 @@ def from_path(bmc: Bmc, path: str): def physical_disks(bmc: Bmc) -> list[Disk]: """Retrieve list of physical physical_disks.""" try: - disks = bmc.redfish_request(REDFISH_DISKS_PATH)["Drives"] - disk_list = [Disk.from_path(bmc, path=disk["@odata.id"]) for disk in disks] + 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 + ] + 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)) return disk_list - except RedfishError as err: + except RedfishRequestError as err: logger.error("Failed retrieving disk info: %s", err) raise (err) from err diff --git a/python/understack-workflows/understack_workflows/bmc_hostname.py b/python/understack-workflows/understack_workflows/bmc_hostname.py index e1c5d3ec5..0c47e52aa 100644 --- a/python/understack-workflows/understack_workflows/bmc_hostname.py +++ b/python/understack-workflows/understack_workflows/bmc_hostname.py @@ -1,11 +1,16 @@ from understack_workflows.bmc import Bmc from understack_workflows.helpers import setup_logger -REDFISH_PATH = "/redfish/v1/Managers/iDRAC.Embedded.1/EthernetInterfaces/NIC.1" - logger = setup_logger(__name__) +def bmc_get_mgmt_interface(bmc: Bmc) -> str: + """Read BMC interface.""" + return bmc.redfish_request(bmc.manager_path + "/EthernetInterfaces")["Members"][0][ + "@odata.id" + ] + + def bmc_set_hostname(bmc: Bmc, current_name: str, new_name: str): """Set the hostname if required.""" if not bmc or not current_name or not new_name: @@ -17,4 +22,5 @@ def bmc_set_hostname(bmc: Bmc, current_name: str, new_name: str): logger.info("Changing BMC hostname from %s to %s", current_name, new_name) payload = {"HostName": new_name} - bmc.redfish_request(REDFISH_PATH, method="PATCH", payload=payload) + interface_path = bmc_get_mgmt_interface(bmc) + bmc.redfish_request(interface_path, method="PATCH", payload=payload) diff --git a/python/understack-workflows/understack_workflows/bmc_network_config.py b/python/understack-workflows/understack_workflows/bmc_network_config.py index 18cf69fd7..61c185e7f 100644 --- a/python/understack-workflows/understack_workflows/bmc_network_config.py +++ b/python/understack-workflows/understack_workflows/bmc_network_config.py @@ -2,8 +2,6 @@ from understack_workflows.bmc_chassis_info import InterfaceInfo from understack_workflows.helpers import setup_logger -REDFISH_PATH = "/redfish/v1/Managers/iDRAC.Embedded.1/Attributes" - logger = setup_logger(__name__) @@ -30,4 +28,5 @@ def bmc_set_permanent_ip_addr(bmc: Bmc, interface_info: InterfaceInfo): logger.info( "BMC was DHCP IP %s, making this permanent", interface_info.ipv4_address ) - bmc.redfish_request(REDFISH_PATH, method="PATCH", payload=payload) + attribute_path = bmc.manager_path + "/Attributes" + bmc.redfish_request(attribute_path, method="PATCH", payload=payload) diff --git a/python/understack-workflows/understack_workflows/bmc_power.py b/python/understack-workflows/understack_workflows/bmc_power.py index b9ef38a95..d71d144ae 100644 --- a/python/understack-workflows/understack_workflows/bmc_power.py +++ b/python/understack-workflows/understack_workflows/bmc_power.py @@ -7,7 +7,7 @@ def bmc_power_on(bmc: Bmc): """Make a redfish call to switch on the power to the system.""" bmc.redfish_request( - "/redfish/v1/Systems/System.Embedded.1/Actions/ComputerSystem.Reset", + bmc.system_path + "/Actions/ComputerSystem.Reset", payload={"ResetType": "On"}, method="POST", ) diff --git a/python/understack-workflows/understack_workflows/bmc_settings.py b/python/understack-workflows/understack_workflows/bmc_settings.py index 7c267ec6f..d0836fe8c 100644 --- a/python/understack-workflows/understack_workflows/bmc_settings.py +++ b/python/understack-workflows/understack_workflows/bmc_settings.py @@ -14,7 +14,9 @@ "SwitchConnectionView.1.Enable": {"expect": "Enabled", "new_value": "Enabled"}, } -REDFISH_PATH = "/redfish/v1/Managers/iDRAC.Embedded.1/Attributes" + +class BiosSettingException(Exception): + """Exception when a required key are not present.""" def update_dell_drac_settings(bmc: Bmc) -> dict: @@ -22,11 +24,12 @@ def update_dell_drac_settings(bmc: Bmc) -> dict: Returns the changes that were made """ - current_values = bmc.redfish_request(REDFISH_PATH)["Attributes"] + attribute_path = bmc.manager_path + "/Attributes" + current_values = bmc.redfish_request(attribute_path)["Attributes"] - for key in STANDARD.keys(): + for key, _value in STANDARD.items(): if key not in current_values: - raise Exception(f"{bmc} has no BMC attribute {key}") + raise BiosSettingException(f"{bmc} has no BMC attribute {key}") required_changes = { k: x["new_value"] @@ -40,7 +43,7 @@ def update_dell_drac_settings(bmc: Bmc) -> dict: logger.info(" %s: %s->%s", k, current_values[k], STANDARD[k]["expect"]) payload = {"Attributes": required_changes} - bmc.redfish_request(REDFISH_PATH, payload=payload, method="PATCH") + bmc.redfish_request(attribute_path, payload=payload, method="PATCH") logger.info("%s DRAC settings have been updated", bmc) else: diff --git a/python/understack-workflows/understack_workflows/main/enroll_server.py b/python/understack-workflows/understack_workflows/main/enroll_server.py index 0cfb4524c..5db3c12b1 100644 --- a/python/understack-workflows/understack_workflows/main/enroll_server.py +++ b/python/understack-workflows/understack_workflows/main/enroll_server.py @@ -1,3 +1,5 @@ +# pylint: disable=E1131,C0103 + import argparse import logging import os @@ -69,6 +71,7 @@ def main(): def enroll_server(bmc: Bmc, old_password: str | None) -> str: + """Enroll BMC to Undercloud Ironic.""" set_bmc_password( ip_address=bmc.ip_address, new_password=bmc.password, @@ -86,7 +89,7 @@ def enroll_server(bmc: Bmc, old_password: str | None) -> str: pxe_interface = guess_pxe_interface(device_info) logger.info("Selected %s as PXE interface", pxe_interface) - # Note the above may require a restart of the DRAC, which in turn may delete + # Note the above may require a restart of the DRAC, which also may delete # any pending BIOS jobs, so do BIOS settings after the DRAC settings. update_dell_bios_settings(bmc, pxe_interface=pxe_interface) @@ -101,13 +104,15 @@ def enroll_server(bmc: Bmc, old_password: str | None) -> str: def guess_pxe_interface(device_info: ChassisInfo) -> str: + """Determine most probable PXE interface for BMC.""" interface = max(device_info.interfaces, key=_pxe_preference) return interface.name def _pxe_preference(interface: InterfaceInfo) -> int: - name = interface.name.upper() - if "DRAC" in name or "ILO" in name or "NIC.EMBEDDED" in name: + """Restrict BMC interfaces from PXE selection.""" + _name = interface.name.upper() + if "DRAC" in _name or "ILO" in _name or "NIC.EMBEDDED" in _name: return 0 NIC_PREFERENCE = { @@ -124,6 +129,7 @@ def _pxe_preference(interface: InterfaceInfo) -> int: def argument_parser(): + """Parse runtime arguments.""" parser = argparse.ArgumentParser( prog=os.path.basename(__file__), description="Ingest Baremetal Node" )