diff --git a/python/ironic-understack/ironic_understack/inspect_hook_node_name_check.py b/python/ironic-understack/ironic_understack/inspect_hook_node_name_check.py index 0b8a349d3..0bcd218a0 100644 --- a/python/ironic-understack/ironic_understack/inspect_hook_node_name_check.py +++ b/python/ironic-understack/ironic_understack/inspect_hook_node_name_check.py @@ -19,7 +19,7 @@ def __call__(self, task, inventory, _plugin_data): node = task.node sys_data = inventory.get("system_vendor", {}) - serial_number = sys_data.get("sku", sys_data.get("serial_number")) + serial_number = sys_data.get("serial_number") if serial_number is None: raise exception.InvalidNodeInventory( node=node.uuid, reason="No serial number found in inventory data." diff --git a/python/ironic-understack/ironic_understack/tests/test_inspect_hook_node_name_check.py b/python/ironic-understack/ironic_understack/tests/test_inspect_hook_node_name_check.py new file mode 100644 index 000000000..11eb5af5e --- /dev/null +++ b/python/ironic-understack/ironic_understack/tests/test_inspect_hook_node_name_check.py @@ -0,0 +1,97 @@ +import pytest +from ironic.common import exception +from oslo_utils import uuidutils + +from ironic_understack.inspect_hook_node_name_check import InspectHookNodeNameCheck +from ironic_understack.inspect_hook_node_name_check import _manufacturer_slug + + +def _make_task(mocker, node_name): + node = mocker.Mock(id=1234, uuid=uuidutils.generate_uuid()) + node.name = node_name + return mocker.Mock(node=node) + + +class TestInspectHookNodeNameCheck: + def test_matching_name_passes(self, mocker): + task = _make_task(mocker, "Dell Inc._SN123") + inventory = { + "system_vendor": { + "serial_number": "SN123", + "manufacturer": "Dell Inc.", + } + } + + # Should not raise + InspectHookNodeNameCheck()(task, inventory, {}) + + def test_mismatched_name_raises(self, mocker): + task = _make_task(mocker, "Wrong-Name") + inventory = { + "system_vendor": { + "serial_number": "SN123", + "manufacturer": "Dell Inc.", + } + } + + with pytest.raises(RuntimeError, match="Hardware Identity Crisis"): + InspectHookNodeNameCheck()(task, inventory, {}) + + def test_missing_serial_number_raises(self, mocker): + task = _make_task(mocker, "Dell-SN123") + inventory = { + "system_vendor": { + "manufacturer": "Dell Inc.", + } + } + + with pytest.raises(exception.InvalidNodeInventory, match="No serial number"): + InspectHookNodeNameCheck()(task, inventory, {}) + + def test_missing_manufacturer_raises(self, mocker): + task = _make_task(mocker, "Dell-SN123") + inventory = { + "system_vendor": { + "serial_number": "SN123", + } + } + + with pytest.raises(exception.InvalidNodeInventory, match="No manufacturer"): + InspectHookNodeNameCheck()(task, inventory, {}) + + def test_sku_field_is_not_used_for_serial(self, mocker): + """serial_number comes only from serial_number, not sku.""" + task = _make_task(mocker, "Dell-SKU999") + inventory = { + "system_vendor": { + "sku": "SKU999", + "manufacturer": "Dell Inc.", + } + } + + with pytest.raises(exception.InvalidNodeInventory, match="No serial number"): + InspectHookNodeNameCheck()(task, inventory, {}) + + def test_empty_system_vendor(self, mocker): + task = _make_task(mocker, "Dell-SN123") + inventory = {"system_vendor": {}} + + with pytest.raises(exception.InvalidNodeInventory, match="No serial number"): + InspectHookNodeNameCheck()(task, inventory, {}) + + +class TestManufacturerSlug: + def test_dell(self): + assert _manufacturer_slug("Dell Inc.") == "Dell" + + def test_dell_uppercase(self): + assert _manufacturer_slug("DELL") == "Dell" + + def test_hp(self): + assert _manufacturer_slug("HPE") == "HP" + + def test_hp_lowercase(self): + assert _manufacturer_slug("hp") == "HP" + + def test_other_replaces_spaces(self): + assert _manufacturer_slug("Super Micro") == "Super_Micro" diff --git a/python/understack-workflows/tests/test_nautobot_device_sync.py b/python/understack-workflows/tests/test_nautobot_device_sync.py index 595ba86e6..976b7758f 100644 --- a/python/understack-workflows/tests/test_nautobot_device_sync.py +++ b/python/understack-workflows/tests/test_nautobot_device_sync.py @@ -138,7 +138,6 @@ def test_populate_from_inventory_full(self, device_info): assert device_info.manufacturer == "Dell" assert device_info.model == "PowerEdge R7615" - assert device_info.service_tag == "ABC1234" assert device_info.serial_number == "SN123456" def test_populate_from_inventory_agent_format(self, device_info): @@ -155,14 +154,12 @@ def test_populate_from_inventory_agent_format(self, device_info): _populate_from_inventory(device_info, inventory) - assert device_info.service_tag == "SERVICETAG123" - assert device_info.serial_number is None # Only set when sku exists + assert device_info.serial_number == "SERVICETAG123" def test_populate_from_inventory_empty(self, device_info): _populate_from_inventory(device_info, None) assert device_info.model is None - assert device_info.service_tag is None def test_populate_from_inventory_system_product_name(self, device_info): """Test that 'System' product name is ignored.""" @@ -202,7 +199,7 @@ def test_generate_name_with_both_fields(self): device_info = DeviceInfo( uuid="test-uuid", manufacturer="Dell", - service_tag="ABC1234", + serial_number="ABC1234", ) _generate_device_name(device_info) @@ -212,14 +209,14 @@ def test_generate_name_with_both_fields(self): def test_generate_name_missing_manufacturer(self): device_info = DeviceInfo( uuid="test-uuid", - service_tag="ABC1234", + serial_number="ABC1234", ) _generate_device_name(device_info) assert device_info.name is None - def test_generate_name_missing_service_tag(self): + def test_generate_name_missing_serial_number(self): device_info = DeviceInfo( uuid="test-uuid", manufacturer="Dell", diff --git a/python/understack-workflows/understack_workflows/oslo_event/nautobot_device_sync.py b/python/understack-workflows/understack_workflows/oslo_event/nautobot_device_sync.py index 18a1754cd..24f2f8590 100644 --- a/python/understack-workflows/understack_workflows/oslo_event/nautobot_device_sync.py +++ b/python/understack-workflows/understack_workflows/oslo_event/nautobot_device_sync.py @@ -68,7 +68,6 @@ class DeviceInfo: # Identity name: str | None = None serial_number: str | None = None - service_tag: str | None = None # Hardware manufacturer: str | None = None @@ -170,20 +169,14 @@ def _populate_from_inventory(device_info: DeviceInfo, inventory: dict | None) -> if product_name and product_name != "System": device_info.model = re.sub(r" \(.*\)", "", str(product_name)) - # Service tag: sku (REDFISH) or serial_number (AGENT) - service_tag = system_vendor.get("sku") or system_vendor.get("serial_number") - if service_tag: - device_info.service_tag = service_tag - - # Serial number: only if sku exists (REDFISH has both) - if system_vendor.get("sku"): + if system_vendor.get("serial_number"): device_info.serial_number = system_vendor.get("serial_number") def _generate_device_name(device_info: DeviceInfo) -> None: - """Generate device name from manufacturer and service tag.""" - if device_info.manufacturer and device_info.service_tag: - device_info.name = f"{device_info.manufacturer}-{device_info.service_tag}" + """Generate device name from manufacturer and serial number.""" + if device_info.manufacturer and device_info.serial_number: + device_info.name = f"{device_info.manufacturer}-{device_info.serial_number}" def _set_location_from_switches(