From 4edccbec55e01310c756d4d4388a030d608e11d1 Mon Sep 17 00:00:00 2001 From: Steve Keay Date: Tue, 5 Nov 2024 20:25:48 +0000 Subject: [PATCH 1/6] Get Interface data from NICs that don't have the separate partition info We were assuming that every NIC with a partition like Slot.2-1-1 also had a redfish entry for the "base" interface like Slot.2-1, however this is not true on all devices. Some only have the partition-style names. Therefore we change the logic: instead of including only a fixed pattern of NIC names, we ignore any interface that looks like a partition of an existing base NIC. --- ...System.Embedded.1_EthernetInterfaces_.json | 6 +++ ...EthernetInterfaces_NIC.Embedded.1-1-1.json | 42 +++++++++++++++++++ ...EthernetInterfaces_NIC.Embedded.2-1-1.json | 42 +++++++++++++++++++ ...d.1_EthernetInterfaces_NIC.Slot.2-1-1.json | 42 +++++++++++++++++++ ...d.1_EthernetInterfaces_NIC.Slot.2-2-1.json | 42 +++++++++++++++++++ ...Ports_Oem_Dell_DellSwitchConnections_.json | 26 ++++++++++++ .../tests/test_bmc_chassis_info.py | 40 ++++++++++++++++++ .../understack_workflows/bmc_chassis_info.py | 39 +++++++++-------- 8 files changed, 259 insertions(+), 20 deletions(-) create mode 100644 python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Systems_System.Embedded.1_EthernetInterfaces_NIC.Embedded.1-1-1.json create mode 100644 python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Systems_System.Embedded.1_EthernetInterfaces_NIC.Embedded.2-1-1.json create mode 100644 python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Systems_System.Embedded.1_EthernetInterfaces_NIC.Slot.2-1-1.json create mode 100644 python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Systems_System.Embedded.1_EthernetInterfaces_NIC.Slot.2-2-1.json diff --git a/python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Systems_System.Embedded.1_EthernetInterfaces_.json b/python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Systems_System.Embedded.1_EthernetInterfaces_.json index 2cd739d02..fd8b295cd 100644 --- a/python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Systems_System.Embedded.1_EthernetInterfaces_.json +++ b/python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Systems_System.Embedded.1_EthernetInterfaces_.json @@ -33,6 +33,12 @@ }, { "@odata.id": "/redfish/v1/Systems/System.Embedded.1/EthernetInterfaces/NIC.Integrated.1-2-1" + }, + { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/EthernetInterfaces/NIC.Slot.2-1-1" + }, + { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/EthernetInterfaces/NIC.Slot.2-2-1" } ], "Members@odata.count": 10, diff --git a/python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Systems_System.Embedded.1_EthernetInterfaces_NIC.Embedded.1-1-1.json b/python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Systems_System.Embedded.1_EthernetInterfaces_NIC.Embedded.1-1-1.json new file mode 100644 index 000000000..96b4cd26e --- /dev/null +++ b/python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Systems_System.Embedded.1_EthernetInterfaces_NIC.Embedded.1-1-1.json @@ -0,0 +1,42 @@ +{ + "@odata.context": "/redfish/v1/$metadata#EthernetInterface.EthernetInterface", + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/EthernetInterfaces/NIC.Embedded.1-1-1", + "@odata.type": "#EthernetInterface.v1_10_0.EthernetInterface", + "AutoNeg": false, + "Description": "Embedded NIC 1 Port 1 Partition 1", + "EthernetInterfaceType": "Physical", + "FQDN": null, + "FullDuplex": false, + "HostName": null, + "IPv4Addresses": [], + "IPv4Addresses@odata.count": 0, + "IPv6AddressPolicyTable": [], + "IPv6AddressPolicyTable@odata.count": 0, + "IPv6Addresses": [], + "IPv6Addresses@odata.count": 0, + "IPv6DefaultGateway": null, + "IPv6StaticAddresses": [], + "IPv6StaticAddresses@odata.count": 0, + "Id": "NIC.Embedded.1-1-1", + "InterfaceEnabled": true, + "LinkStatus": "LinkDown", + "Links": { + "Chassis": { + "@odata.id": "/redfish/v1/Chassis/System.Embedded.1" + } + }, + "MACAddress": "C4:CB:E1:BF:8A:62", + "MTUSize": null, + "MaxIPv6StaticAddresses": null, + "Name": "System Ethernet Interface", + "NameServers": [], + "NameServers@odata.count": 0, + "PermanentMACAddress": "C4:CB:E1:BF:8A:62", + "SpeedMbps": 0, + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "UefiDevicePath": "PciRoot(0x3)/Pci(0x5,0x3)/Pci(0x0,0x0)", + "VLAN": {} +} diff --git a/python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Systems_System.Embedded.1_EthernetInterfaces_NIC.Embedded.2-1-1.json b/python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Systems_System.Embedded.1_EthernetInterfaces_NIC.Embedded.2-1-1.json new file mode 100644 index 000000000..b4bfcae55 --- /dev/null +++ b/python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Systems_System.Embedded.1_EthernetInterfaces_NIC.Embedded.2-1-1.json @@ -0,0 +1,42 @@ +{ + "@odata.context": "/redfish/v1/$metadata#EthernetInterface.EthernetInterface", + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/EthernetInterfaces/NIC.Embedded.2-1-1", + "@odata.type": "#EthernetInterface.v1_10_0.EthernetInterface", + "AutoNeg": false, + "Description": "Embedded NIC 1 Port 2 Partition 1", + "EthernetInterfaceType": "Physical", + "FQDN": null, + "FullDuplex": false, + "HostName": null, + "IPv4Addresses": [], + "IPv4Addresses@odata.count": 0, + "IPv6AddressPolicyTable": [], + "IPv6AddressPolicyTable@odata.count": 0, + "IPv6Addresses": [], + "IPv6Addresses@odata.count": 0, + "IPv6DefaultGateway": null, + "IPv6StaticAddresses": [], + "IPv6StaticAddresses@odata.count": 0, + "Id": "NIC.Embedded.2-1-1", + "InterfaceEnabled": true, + "LinkStatus": "LinkDown", + "Links": { + "Chassis": { + "@odata.id": "/redfish/v1/Chassis/System.Embedded.1" + } + }, + "MACAddress": "C4:CB:E1:BF:8A:63", + "MTUSize": null, + "MaxIPv6StaticAddresses": null, + "Name": "System Ethernet Interface", + "NameServers": [], + "NameServers@odata.count": 0, + "PermanentMACAddress": "C4:CB:E1:BF:8A:63", + "SpeedMbps": 0, + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "UefiDevicePath": "PciRoot(0x3)/Pci(0x5,0x3)/Pci(0x0,0x1)", + "VLAN": {} +} diff --git a/python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Systems_System.Embedded.1_EthernetInterfaces_NIC.Slot.2-1-1.json b/python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Systems_System.Embedded.1_EthernetInterfaces_NIC.Slot.2-1-1.json new file mode 100644 index 000000000..3a5b929a5 --- /dev/null +++ b/python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Systems_System.Embedded.1_EthernetInterfaces_NIC.Slot.2-1-1.json @@ -0,0 +1,42 @@ +{ + "@odata.context": "/redfish/v1/$metadata#EthernetInterface.EthernetInterface", + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/EthernetInterfaces/NIC.Slot.2-1-1", + "@odata.type": "#EthernetInterface.v1_10_0.EthernetInterface", + "AutoNeg": true, + "Description": "NIC in Slot 2 Port 1 Partition 1", + "EthernetInterfaceType": "Physical", + "FQDN": null, + "FullDuplex": true, + "HostName": null, + "IPv4Addresses": [], + "IPv4Addresses@odata.count": 0, + "IPv6AddressPolicyTable": [], + "IPv6AddressPolicyTable@odata.count": 0, + "IPv6Addresses": [], + "IPv6Addresses@odata.count": 0, + "IPv6DefaultGateway": null, + "IPv6StaticAddresses": [], + "IPv6StaticAddresses@odata.count": 0, + "Id": "NIC.Slot.2-1-1", + "InterfaceEnabled": true, + "LinkStatus": "LinkUp", + "Links": { + "Chassis": { + "@odata.id": "/redfish/v1/Chassis/System.Embedded.1" + } + }, + "MACAddress": "9C:63:C0:24:B0:C0", + "MTUSize": null, + "MaxIPv6StaticAddresses": null, + "Name": "System Ethernet Interface", + "NameServers": [], + "NameServers@odata.count": 0, + "PermanentMACAddress": "9C:63:C0:24:B0:C0", + "SpeedMbps": 100000, + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "UefiDevicePath": "PciRoot(0x3)/Pci(0x1,0x1)/Pci(0x0,0x0)/MAC(9C63C024B0C0,0x1)", + "VLAN": {} +} diff --git a/python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Systems_System.Embedded.1_EthernetInterfaces_NIC.Slot.2-2-1.json b/python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Systems_System.Embedded.1_EthernetInterfaces_NIC.Slot.2-2-1.json new file mode 100644 index 000000000..6efc8ae23 --- /dev/null +++ b/python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Systems_System.Embedded.1_EthernetInterfaces_NIC.Slot.2-2-1.json @@ -0,0 +1,42 @@ +{ + "@odata.context": "/redfish/v1/$metadata#EthernetInterface.EthernetInterface", + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/EthernetInterfaces/NIC.Slot.2-2-1", + "@odata.type": "#EthernetInterface.v1_10_0.EthernetInterface", + "AutoNeg": true, + "Description": "NIC in Slot 2 Port 2 Partition 1", + "EthernetInterfaceType": "Physical", + "FQDN": null, + "FullDuplex": true, + "HostName": null, + "IPv4Addresses": [], + "IPv4Addresses@odata.count": 0, + "IPv6AddressPolicyTable": [], + "IPv6AddressPolicyTable@odata.count": 0, + "IPv6Addresses": [], + "IPv6Addresses@odata.count": 0, + "IPv6DefaultGateway": null, + "IPv6StaticAddresses": [], + "IPv6StaticAddresses@odata.count": 0, + "Id": "NIC.Slot.2-2-1", + "InterfaceEnabled": true, + "LinkStatus": "LinkUp", + "Links": { + "Chassis": { + "@odata.id": "/redfish/v1/Chassis/System.Embedded.1" + } + }, + "MACAddress": "9C:63:C0:24:B0:C1", + "MTUSize": null, + "MaxIPv6StaticAddresses": null, + "Name": "System Ethernet Interface", + "NameServers": [], + "NameServers@odata.count": 0, + "PermanentMACAddress": "9C:63:C0:24:B0:C1", + "SpeedMbps": 100000, + "Status": { + "Health": "OK", + "State": "Enabled" + }, + "UefiDevicePath": "PciRoot(0x3)/Pci(0x1,0x1)/Pci(0x0,0x1)/MAC(9C63C024B0C1,0x1)", + "VLAN": {} +} diff --git a/python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Systems_System.Embedded.1_NetworkPorts_Oem_Dell_DellSwitchConnections_.json b/python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Systems_System.Embedded.1_NetworkPorts_Oem_Dell_DellSwitchConnections_.json index a0f01f0eb..bc45ccfaf 100644 --- a/python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Systems_System.Embedded.1_NetworkPorts_Oem_Dell_DellSwitchConnections_.json +++ b/python/understack-workflows/tests/json_samples/bmc_chassis_info/R7615/_redfish_v1_Systems_System.Embedded.1_NetworkPorts_Oem_Dell_DellSwitchConnections_.json @@ -94,6 +94,32 @@ "StaleData": "NotStale", "SwitchConnectionID": "c4:7e:e0:e4:10:7f", "SwitchPortConnectionID": "Ethernet1/6" + }, + { + "@odata.context": "/redfish/v1/$metadata#DellSwitchConnection.DellSwitchConnection", + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/NetworkPorts/Oem/Dell/DellSwitchConnections/NIC.Slot.2-1-1", + "@odata.type": "#DellSwitchConnection.v1_1_0.DellSwitchConnection", + "Description": "An instance of DellSwitchConnection will have the switch connection view information of all the ports.", + "FQDD": "NIC.Slot.2-1-1", + "Id": "NIC.Slot.2-1-1", + "InstanceID": "NIC.Slot.2-1-1", + "Name": "DellSwitchConnection", + "StaleData": "NotStale", + "SwitchConnectionID": "c4:7e:e0:e4:10:7f", + "SwitchPortConnectionID": "Ethernet1/8" + }, + { + "@odata.context": "/redfish/v1/$metadata#DellSwitchConnection.DellSwitchConnection", + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/NetworkPorts/Oem/Dell/DellSwitchConnections/NIC.Slot.2-2-1", + "@odata.type": "#DellSwitchConnection.v1_1_0.DellSwitchConnection", + "Description": "An instance of DellSwitchConnection will have the switch connection view information of all the ports.", + "FQDD": "NIC.Slot.2-2-1", + "Id": "NIC.Slot.2-2-1", + "InstanceID": "NIC.Slot.2-2-1", + "Name": "DellSwitchConnection", + "StaleData": "NotStale", + "SwitchConnectionID": "c4:7e:e0:e4:10:7f", + "SwitchPortConnectionID": "Ethernet1/7" } ], "Members@odata.count": 7, diff --git a/python/understack-workflows/tests/test_bmc_chassis_info.py b/python/understack-workflows/tests/test_bmc_chassis_info.py index 649031b47..62138d6d9 100644 --- a/python/understack-workflows/tests/test_bmc_chassis_info.py +++ b/python/understack-workflows/tests/test_bmc_chassis_info.py @@ -72,6 +72,16 @@ def test_chassis_info_R7615(): remote_switch_mac_address="C4:7E:E0:E4:32:DF", remote_switch_port_name="Ethernet1/5", ), + bmc_chassis_info.InterfaceInfo( + name="NIC.Embedded.1-1-1", + description="Embedded NIC 1 Port 1 Partition 1", + mac_address="C4:CB:E1:BF:8A:62", + ipv4_address=None, + ipv4_gateway=None, + dhcp=False, + remote_switch_mac_address=None, + remote_switch_port_name=None, + ), bmc_chassis_info.InterfaceInfo( description="NIC in Slot 1 Port 1", mac_address="14:23:F3:F5:25:F0", @@ -86,5 +96,35 @@ def test_chassis_info_R7615(): remote_switch_mac_address="C4:7E:E0:E4:10:7F", remote_switch_port_name="Ethernet1/6", ), + bmc_chassis_info.InterfaceInfo( + name="NIC.Embedded.2-1-1", + description="Embedded NIC 1 Port 2 Partition 1", + mac_address="C4:CB:E1:BF:8A:63", + ipv4_address=None, + ipv4_gateway=None, + dhcp=False, + remote_switch_mac_address=None, + remote_switch_port_name=None, + ), + bmc_chassis_info.InterfaceInfo( + name="NIC.Slot.2-1-1", + description="NIC in Slot 2 Port 1 Partition 1", + mac_address="9C:63:C0:24:B0:C0", + ipv4_address=None, + ipv4_gateway=None, + dhcp=False, + remote_switch_mac_address="C4:7E:E0:E4:10:7F", + remote_switch_port_name="Ethernet1/8", + ), + bmc_chassis_info.InterfaceInfo( + name="NIC.Slot.2-2-1", + description="NIC in Slot 2 Port 2 Partition 1", + mac_address="9C:63:C0:24:B0:C1", + ipv4_address=None, + ipv4_gateway=None, + dhcp=False, + remote_switch_mac_address="C4:7E:E0:E4:10:7F", + remote_switch_port_name="Ethernet1/7", + ), ], ) diff --git a/python/understack-workflows/understack_workflows/bmc_chassis_info.py b/python/understack-workflows/understack_workflows/bmc_chassis_info.py index 2b75f0572..410c6e8cd 100644 --- a/python/understack-workflows/understack_workflows/bmc_chassis_info.py +++ b/python/understack-workflows/understack_workflows/bmc_chassis_info.py @@ -74,11 +74,12 @@ def interface_data(bmc: Bmc) -> list[InterfaceInfo]: def combine_lldp(lldp, interface) -> InterfaceInfo: name = interface["name"] - lldp_entry = lldp.get(name, {}) + alternate_name = f"{name}-1" + lldp_entry = lldp.get(name, lldp.get(alternate_name)) if not lldp_entry: logger.info( - f"LLDP info from BMC is missing for {name}, we only " - f"have LLDP info for {list(lldp.keys())}" + f"LLDP info from BMC is missing for {name} or {alternate_name}, " + f"we only have LLDP info for {list(lldp.keys())}" ) return InterfaceInfo(**interface, **lldp_entry) @@ -128,11 +129,22 @@ def parse_ipv4( def in_band_interfaces(bmc: Bmc) -> list[dict]: - """A Collection of Ethernet Interfaces for this System.""" + """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 + 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. + """ index_data = bmc.redfish_request(REDFISH_ETHERNET_ENDPOINT) urls = [member["@odata.id"] for member in index_data["Members"]] - return [interface_detail(bmc, url) for url in urls if interface_is_relevant(url)] + return [ + interface_detail(bmc, url) + for url in urls + if re.sub(r"-\d$", "", url) not in urls + ] def interface_detail(bmc, path) -> dict: @@ -198,7 +210,7 @@ def parse_lldp_port(port_data: dict[str, str]) -> dict: mac = str(port_data["SwitchConnectionID"]).upper() port_name = normalize_interface_name(port_data["SwitchPortConnectionID"]) - if mac in ["NOT AVAILABLE", "NO LINK"]: + if mac in ["NOT AVAILABLE", "NO LINK", "NOT SUPPORTED"]: return { "remote_switch_mac_address": None, "remote_switch_port_name": None, @@ -210,18 +222,5 @@ def parse_lldp_port(port_data: dict[str, str]) -> dict: } -def interface_is_relevant(url: str) -> bool: - return bool(re.match(r".*(iDRAC.Embedded.*|NIC.(Integrated|Slot).\d-\d)$", url)) - - def server_interface_name(name: str) -> str: - if name.startswith("iDRAC.Embedded"): - return "iDRAC" - - # remove the "-1" partition number from dell NIC ports - slot_regexp = re.compile(r"(.*\.\d-\d)-\d") - match = slot_regexp.match(name) - if match: - return match.group(1) - - return name + return "iDRAC" if name.startswith("iDRAC.Embedded") else name From 7bcbf01cf1e7d728cc94db9653ef0848e06b37e6 Mon Sep 17 00:00:00 2001 From: Steve Keay Date: Thu, 7 Nov 2024 09:02:11 +0000 Subject: [PATCH 2/6] Clean up BMC sessions after verifying credentials These sessions age out on their own, but repeatedly calling this code can use up all the sessions on the device, which is annoying because it locks us out. --- .../tests/test_bmc_credentials.py | 7 +++-- .../understack_workflows/bmc_credentials.py | 27 +++++++++++++------ 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/python/understack-workflows/tests/test_bmc_credentials.py b/python/understack-workflows/tests/test_bmc_credentials.py index 1c4ee0e04..1298905a0 100644 --- a/python/understack-workflows/tests/test_bmc_credentials.py +++ b/python/understack-workflows/tests/test_bmc_credentials.py @@ -13,7 +13,7 @@ def mock_redfish(mocker): @pytest.fixture def mock_success_auth(mocker): mock = mocker.patch("understack_workflows.bmc_credentials._verify_auth") - mock.return_value = "tOkEn" + mock.return_value = "tOkEn", "/path/to/session/1234" return mock @@ -26,7 +26,10 @@ def mock_fail_auth(mocker): def test_set_bmc_password_noop(mock_success_auth, mock_redfish): set_bmc_password("1.2.3.4", "qwertyuiop") - assert not mock_redfish.called + assert mock_redfish.call_count == 1 + mock_redfish.assert_called_with( + "1.2.3.4", "/path/to/session/1234", "tOkEn", "DELETE" + ) def test_set_bmc_password_failed(mock_fail_auth, mock_redfish): diff --git a/python/understack-workflows/understack_workflows/bmc_credentials.py b/python/understack-workflows/understack_workflows/bmc_credentials.py index a5f0b9208..925f2d533 100644 --- a/python/understack-workflows/understack_workflows/bmc_credentials.py +++ b/python/understack-workflows/understack_workflows/bmc_credentials.py @@ -23,15 +23,17 @@ def set_bmc_password(ip_address, required_password, username="root"): Raises an Exception if the password is not confirmed working. """ - if _verify_auth(ip_address, username, required_password): + token, session = _verify_auth(ip_address, username, required_password) + if token: logger.info("Production BMC credentials are working on this BMC.") + close_session(ip_address, token, session) return logger.info( "Production BMC credentials don't work on this BMC. " "Trying factory default credentials." ) - token = _verify_auth(ip_address, username, FACTORY_PASSWORD) + token, session = _verify_auth(ip_address, username, FACTORY_PASSWORD) if not token: raise Exception( f"Unable to log in to BMC {ip_address} with any known password!" @@ -40,16 +42,21 @@ def set_bmc_password(ip_address, required_password, username="root"): logger.info("Changing BMC password to standard") _set_bmc_creds(ip_address, token, username, required_password) logger.info("BMC password has been set.") + close_session(ip_address, token, session) - if _verify_auth(ip_address, username, required_password): + token = _verify_auth(ip_address, username, required_password) + if token: logger.info("Production BMC credentials are working on this BMC.") + close_session(ip_address, token, session) -def _verify_auth(host: str, username: str = "root", password: str = "") -> str: +def _verify_auth( + host: str, username: str = "root", password: str = "" +) -> tuple[str | None, str | None]: """Test whether provided credentials work against a secured API endpoint. - Returns Session authentication token on success - Returns None on authorisation failure + Returns authentication token and session path on success + Returns None, None on authorisation failure Raises an Exception for other kinds of errors (e.g. timeout, etc) """ try: @@ -61,17 +68,21 @@ def _verify_auth(host: str, username: str = "root", password: str = "") -> str: json={"UserName": username, "Password": password}, ) if response.status_code == 401: - return None + return None, None if response.status_code >= 400: raise Exception( f"BMC {host} password login failed: " f" {response.status_code} {response.json()}" ) - return response.headers["X-Auth-Token"] + return response.headers["X-Auth-Token"], response.json()["@odata.id"] except Exception as e: raise e from None +def close_session(host, token, session): + _redfish_request(host, session, token, "DELETE") + + def _get_bmc_accounts(host: str, token: str, username: str) -> list[dict]: """A vendor agnostic approach to crawling the API for BMC accounts.""" try: From 4d48c4967ebb31d0216da78337866c2c38378abb Mon Sep 17 00:00:00 2001 From: Steve Keay Date: Thu, 7 Nov 2024 09:03:50 +0000 Subject: [PATCH 3/6] Add more informative error message when Nautobot cable can't be created --- .../understack_workflows/nautobot_device.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/python/understack-workflows/understack_workflows/nautobot_device.py b/python/understack-workflows/understack_workflows/nautobot_device.py index bbf9b886d..8cc53599f 100644 --- a/python/understack-workflows/understack_workflows/nautobot_device.py +++ b/python/understack-workflows/understack_workflows/nautobot_device.py @@ -378,7 +378,12 @@ def connect_interface_to_switch( cable = nautobot.dcim.cables.get(**identity) if cable is None: - cable = nautobot.dcim.cables.create(**identity, **attrs) + try: + cable = nautobot.dcim.cables.create(**identity, **attrs) + except pynautobot.core.query.RequestError as e: + raise Exception( + f"Failed to create nautobot cable {identity}: {e}" + ) from None logger.info(f"Created cable {cable.id} in Nautobot") else: logger.info(f"Cable {cable.id} already exists in Nautobot") From b3c532010ec72c8b2cc1806203e1bf0861e07e89 Mon Sep 17 00:00:00 2001 From: Steve Keay Date: Thu, 7 Nov 2024 09:34:48 +0000 Subject: [PATCH 4/6] Fix ironic provision state verification Fixes a copy-and-paste error in this code --- python/understack-workflows/understack_workflows/ironic_node.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python/understack-workflows/understack_workflows/ironic_node.py b/python/understack-workflows/understack_workflows/ironic_node.py index 85a1ef100..78e48d964 100644 --- a/python/understack-workflows/understack_workflows/ironic_node.py +++ b/python/understack-workflows/understack_workflows/ironic_node.py @@ -29,7 +29,6 @@ def create_or_update( ironic_node = create_ironic_node( client, node_uuid, device_hostname, driver, bmc ) - return ironic_node.provision_state if ironic_node.provision_state in STATES_ALLOWING_UPDATES: update_ironic_node(client, node_uuid, device_hostname, driver, bmc) From 26ca197dc9dca104e26d3b7d63e8bfa4d18ec2cf Mon Sep 17 00:00:00 2001 From: Steve Keay Date: Thu, 7 Nov 2024 15:16:53 +0000 Subject: [PATCH 5/6] Fix logging - use pformat and not pprint We don't want this polluting stdout, which is what pprint does. We wanted a string, which is what pformat does. In my ignorance I had used the wrong function. --- .../understack_workflows/main/undersync_device.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/understack-workflows/understack_workflows/main/undersync_device.py b/python/understack-workflows/understack_workflows/main/undersync_device.py index 7ffc4bc49..c56085cb0 100644 --- a/python/understack-workflows/understack_workflows/main/undersync_device.py +++ b/python/understack-workflows/understack_workflows/main/undersync_device.py @@ -1,7 +1,7 @@ import argparse import os import sys -from pprint import pprint +from pprint import pformat from uuid import UUID import requests @@ -159,7 +159,7 @@ def main(): vlan_group_id = update_nautobot(args) response = call_undersync(args, vlan_group_id) - logger.info(f"Undersync returned: {pprint(response.json())}") + logger.info(f"Undersync returned: {pformat(response.json())}") if __name__ == "__main__": From 5c8aeb58a292107b820ef4ac1ef7f2308277b860 Mon Sep 17 00:00:00 2001 From: Steve Keay Date: Thu, 7 Nov 2024 15:18:25 +0000 Subject: [PATCH 6/6] Pretty-format the discovered device info that is logged during Enrolment --- .../understack_workflows/main/enroll_server.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/understack-workflows/understack_workflows/main/enroll_server.py b/python/understack-workflows/understack_workflows/main/enroll_server.py index 034b5e340..0dcfbd9e3 100644 --- a/python/understack-workflows/understack_workflows/main/enroll_server.py +++ b/python/understack-workflows/understack_workflows/main/enroll_server.py @@ -1,5 +1,6 @@ import argparse import os +from pprint import pformat import pynautobot @@ -101,7 +102,7 @@ def main(): set_bmc_password(bmc.ip_address, bmc.password) device_info = chassis_info(bmc) - logger.info(f"Discovered {device_info}") + logger.info(f"Discovered {pformat(device_info)}") update_dell_drac_settings(bmc)