From b3d7ba88d2f6cba917f72c10a11d8db19c32dfd6 Mon Sep 17 00:00:00 2001 From: Mahnoor Asghar Date: Wed, 3 May 2023 12:08:47 -0400 Subject: [PATCH] Add to Redfish hardware inventory collection Add to the information collected by Redfish hardware inspection from sushy, and store it in the documented hardware inventory format Change-Id: I651599b84e6b8901647960b719626489b000b65f --- ironic/drivers/modules/redfish/inspect.py | 97 +++++++-- .../drivers/modules/redfish/test_inspect.py | 193 +++++++++++++++++- ...h-hardware-inventory-830ebc0b2e0f50c2.yaml | 8 + 3 files changed, 275 insertions(+), 23 deletions(-) create mode 100644 releasenotes/notes/add-redfish-hardware-inventory-830ebc0b2e0f50c2.yaml diff --git a/ironic/drivers/modules/redfish/inspect.py b/ironic/drivers/modules/redfish/inspect.py index c514df4fe9..7ca9624af8 100644 --- a/ironic/drivers/modules/redfish/inspect.py +++ b/ironic/drivers/modules/redfish/inspect.py @@ -41,6 +41,17 @@ if sushy: sushy.PROCESSOR_ARCH_OEM: 'oem' } + PROCESSOR_INSTRUCTION_SET_MAP = { + sushy.InstructionSet.ARM_A32: 'arm', + sushy.InstructionSet.ARM_A64: 'aarch64', + sushy.InstructionSet.IA_64: 'ia64', + sushy.InstructionSet.MIPS32: 'mips', + sushy.InstructionSet.MIPS64: 'mips64', + sushy.InstructionSet.OEM: None, + sushy.InstructionSet.X86: 'i686', + sushy.InstructionSet.X86_64: 'x86_64' + } + BOOT_MODE_MAP = { sushy.BOOT_SOURCE_MODE_UEFI: boot_modes.UEFI, sushy.BOOT_SOURCE_MODE_BIOS: boot_modes.LEGACY_BIOS @@ -102,34 +113,59 @@ class RedfishInspect(base.InspectInterface): # get the essential properties and update the node properties # with it. inspected_properties = task.node.properties + inventory = {} if system.memory_summary and system.memory_summary.size_gib: - inspected_properties['memory_mb'] = str( - system.memory_summary.size_gib * units.Ki) + memory = system.memory_summary.size_gib * units.Ki + inspected_properties['memory_mb'] = memory + inventory['memory'] = {'physical_mb': memory} - if system.processors and system.processors.summary: - arch = system.processors.summary[1] - - if arch: - try: - inspected_properties['cpu_arch'] = CPU_ARCH_MAP[arch] - - except KeyError: - LOG.warning("Unknown CPU arch %(arch)s discovered " - "for node %(node)s", {'node': task.node.uuid, - 'arch': arch}) + self._get_processor_info(task, system, inspected_properties, inventory) # TODO(etingof): should we respect root device hints here? local_gb = self._detect_local_gb(task, system) if local_gb: inspected_properties['local_gb'] = str(local_gb) + else: LOG.warning("Could not provide a valid storage size configured " "for node %(node)s. Assuming this is a disk-less node", {'node': task.node.uuid}) inspected_properties['local_gb'] = '0' + if system.simple_storage: + simple_storage_list = system.simple_storage.get_members() + disks = list() + + for simple_storage in simple_storage_list: + for simple_storage_device in simple_storage.devices: + disk = {} + disk['name'] = simple_storage_device.name + disk['size'] = simple_storage_device.capacity_bytes + disks.append(disk) + + inventory['disks'] = disks + + if system.ethernet_interfaces and system.ethernet_interfaces.summary: + inventory['interfaces'] = [] + mac_addresses = list(system.ethernet_interfaces.summary.keys()) + for mac_address in mac_addresses: + inventory['interfaces'].append({'mac_address': mac_address}) + + system_vendor = {} + if system.name: + system_vendor['product_name'] = str(system.name) + + if system.serial_number: + system_vendor['serial_number'] = str(system.serial_number) + + if system.manufacturer: + system_vendor['manufacturer'] = str(system.manufacturer) + + if system_vendor: + inventory['system_vendor'] = system_vendor + if system.boot.mode: if not drivers_utils.get_node_capability(task.node, 'boot_mode'): capabilities = utils.get_updated_capabilities( @@ -137,6 +173,8 @@ class RedfishInspect(base.InspectInterface): {'boot_mode': BOOT_MODE_MAP[system.boot.mode]}) inspected_properties['capabilities'] = capabilities + inventory['boot'] = {'current_boot_mode': + BOOT_MODE_MAP[system.boot.mode]} valid_keys = self.ESSENTIAL_PROPERTIES missing_keys = valid_keys - set(inspected_properties) @@ -182,6 +220,8 @@ class RedfishInspect(base.InspectInterface): LOG.warning("No port information discovered " "for node %(node)s", {'node': task.node.uuid}) + inspect_utils.store_inspection_data(task.node, + inventory, None, task.context) return states.MANAGEABLE def _create_ports(self, task, system): @@ -269,3 +309,34 @@ class RedfishInspect(base.InspectInterface): If cannot be determined, returns None. """ return None + + def _get_processor_info(self, task, system, inspected_properties, + inventory): + if system.processors is None: + return + + cpu = {} + if system.processors.summary: + cpus, arch = system.processors.summary + if cpus: + inspected_properties['cpus'] = cpus + cpu['count'] = cpus + if arch: + try: + inspected_properties['cpu_arch'] = CPU_ARCH_MAP[arch] + except KeyError: + LOG.warning("Unknown CPU arch %(arch)s discovered " + "for node %(node)s", {'node': task.node.uuid, + 'arch': arch}) + + processor = system.processors.get_members()[0] + + if processor.model is not None: + cpu['model_name'] = str(processor.model) + if processor.max_speed_mhz is not None: + cpu['frequency'] = processor.max_speed_mhz + if processor.instruction_set is not None: + cpu['architecture'] = PROCESSOR_INSTRUCTION_SET_MAP[ + processor.instruction_set] + + inventory['cpu'] = cpu diff --git a/ironic/tests/unit/drivers/modules/redfish/test_inspect.py b/ironic/tests/unit/drivers/modules/redfish/test_inspect.py index 995dbb6d9e..fe7d9e8d71 100644 --- a/ironic/tests/unit/drivers/modules/redfish/test_inspect.py +++ b/ironic/tests/unit/drivers/modules/redfish/test_inspect.py @@ -58,7 +58,22 @@ class RedfishInspectTestCase(db_base.DbTestCase): system_mock.memory_summary.size_gib = 2 - system_mock.processors.summary = '8', sushy.PROCESSOR_ARCH_MIPS + mock_processor = mock.Mock() + mock_processor.model = 'test' + mock_processor.instruction_set = sushy.InstructionSet.X86 + mock_processor.max_speed_mhz = 1234 + system_mock.processors.get_members.return_value = [mock_processor] + + system_mock.processors.summary = '8', sushy.PROCESSOR_ARCH_x86 + + mock_simple_storage_device = mock.Mock() + mock_simple_storage_device.name = 'test-name' + mock_simple_storage_device.capacity_bytes = '123' + + mock_simple_storage = mock.Mock() + mock_simple_storage.devices = [mock_simple_storage_device] + system_mock.simple_storage.get_members.return_value = [ + mock_simple_storage] system_mock.simple_storage.disks_sizes_bytes = ( 1 * units.Gi, units.Gi * 3, units.Gi * 5) @@ -70,6 +85,12 @@ class RedfishInspectTestCase(db_base.DbTestCase): '66:77:88:99:AA:BB': sushy.STATE_DISABLED, } + system_mock.name = 'System1' + + system_mock.serial_number = '123456' + + system_mock.manufacturer = 'Sushy Emulator' + return system_mock def test_get_properties(self): @@ -93,9 +114,9 @@ class RedfishInspectTestCase(db_base.DbTestCase): mock_get_system): expected_properties = { 'capabilities': 'boot_mode:uefi', - 'cpu_arch': 'mips', 'local_gb': '3', 'memory_mb': '2048' + 'cpu_arch': 'x86_64', 'cpus': '8', + 'local_gb': '3', 'memory_mb': 2048, } - self.init_system_mock(mock_get_system.return_value) with task_manager.acquire(self.context, self.node.uuid, @@ -105,6 +126,34 @@ class RedfishInspectTestCase(db_base.DbTestCase): mock_get_system.assert_called_once_with(task.node) self.assertEqual(expected_properties, task.node.properties) + inventory = inspect_utils.get_inspection_data(task.node, + self.context) + + system_vendor = inventory['inventory']['system_vendor'] + expected_product_name = 'System1' + expected_serial_number = '123456' + expected_manufacturer = 'Sushy Emulator' + self.assertEqual(expected_product_name, + system_vendor['product_name']) + self.assertEqual(expected_serial_number, + system_vendor['serial_number']) + self.assertEqual(expected_manufacturer, + system_vendor['manufacturer']) + + expected_interfaces = [{'mac_address': '00:11:22:33:44:55'}, + {'mac_address': '66:77:88:99:AA:BB'}] + self.assertEqual(expected_interfaces, + inventory['inventory']['interfaces']) + + expected_cpu = {'count': '8', 'model_name': 'test', + 'frequency': 1234, 'architecture': 'i686'} + self.assertEqual(expected_cpu, + inventory['inventory']['cpu']) + + expected_disks = [{'name': 'test-name', 'size': '123'}] + self.assertEqual(expected_disks, + inventory["inventory"]['disks']) + @mock.patch.object(redfish_utils, 'get_system', autospec=True) @mock.patch.object(inspect_utils, 'create_ports_if_not_exist', autospec=True) @@ -120,7 +169,7 @@ class RedfishInspectTestCase(db_base.DbTestCase): task, result) @mock.patch.object(redfish_utils, 'get_system', autospec=True) - def test_inspect_hardware_fail_missing_cpu(self, mock_get_system): + def test_inspect_hardware_fail_missing_cpu_arch(self, mock_get_system): system_mock = self.init_system_mock(mock_get_system.return_value) system_mock.processors.summary = None, None @@ -131,7 +180,7 @@ class RedfishInspectTestCase(db_base.DbTestCase): task.driver.inspect.inspect_hardware, task) @mock.patch.object(redfish_utils, 'get_system', autospec=True) - def test_inspect_hardware_ignore_missing_cpu(self, mock_get_system): + def test_inspect_hardware_ignore_missing_cpu_count(self, mock_get_system): system_mock = self.init_system_mock(mock_get_system.return_value) system_mock.processors.summary = None, None @@ -139,11 +188,78 @@ class RedfishInspectTestCase(db_base.DbTestCase): shared=True) as task: expected_properties = { 'capabilities': 'boot_mode:uefi', - 'cpu_arch': 'x86_64', 'local_gb': '3', 'memory_mb': '2048' + 'cpu_arch': 'x86_64', 'local_gb': '3', 'memory_mb': 2048 } task.driver.inspect.inspect_hardware(task) self.assertEqual(expected_properties, task.node.properties) + inventory = inspect_utils.get_inspection_data(task.node, + self.context) + self.assertNotIn('count', inventory['inventory']['cpu']) + + @mock.patch.object(redfish_utils, 'get_system', autospec=True) + def test_inspect_hardware_ignore_missing_cpu_model(self, mock_get_system): + system_mock = self.init_system_mock(mock_get_system.return_value) + mock_processor = system_mock.processors.get_members.return_value[0] + mock_processor.model = None + + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + expected_properties = { + 'capabilities': 'boot_mode:uefi', + 'cpu_arch': 'x86_64', 'cpus': '8', + 'local_gb': '3', 'memory_mb': 2048 + } + task.driver.inspect.inspect_hardware(task) + self.assertEqual(expected_properties, task.node.properties) + + inventory = inspect_utils.get_inspection_data(task.node, + self.context) + self.assertNotIn('model', inventory['inventory']['cpu']) + + @mock.patch.object(redfish_utils, 'get_system', autospec=True) + def test_inspect_hardware_ignore_missing_cpu_frequency(self, + mock_get_system): + system_mock = self.init_system_mock(mock_get_system.return_value) + mock_processor = system_mock.processors.get_members.return_value[0] + mock_processor.max_speed_mhz = None + + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + expected_properties = { + 'capabilities': 'boot_mode:uefi', + 'cpu_arch': 'x86_64', 'cpus': '8', + 'local_gb': '3', 'memory_mb': 2048 + } + task.driver.inspect.inspect_hardware(task) + self.assertEqual(expected_properties, task.node.properties) + + inventory = inspect_utils.get_inspection_data(task.node, + self.context) + self.assertNotIn('frequency', inventory['inventory']['cpu']) + + @mock.patch.object(redfish_utils, 'get_system', autospec=True) + def test_inspect_hardware_ignore_missing_cpu_instruction_set( + self, + mock_get_system): + system_mock = self.init_system_mock(mock_get_system.return_value) + mock_processor = system_mock.processors.get_members.return_value[0] + mock_processor.instruction_set = None + + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + expected_properties = { + 'capabilities': 'boot_mode:uefi', + 'cpu_arch': 'x86_64', 'cpus': '8', + 'local_gb': '3', 'memory_mb': 2048 + } + task.driver.inspect.inspect_hardware(task) + self.assertEqual(expected_properties, task.node.properties) + + inventory = inspect_utils.get_inspection_data(task.node, + self.context) + self.assertNotIn('architecture', inventory['inventory']['cpu']) + @mock.patch.object(redfish_utils, 'get_system', autospec=True) def test_inspect_hardware_ignore_missing_local_gb(self, mock_get_system): system_mock = self.init_system_mock(mock_get_system.return_value) @@ -154,11 +270,32 @@ class RedfishInspectTestCase(db_base.DbTestCase): shared=True) as task: expected_properties = { 'capabilities': 'boot_mode:uefi', - 'cpu_arch': 'mips', 'local_gb': '0', 'memory_mb': '2048' + 'cpu_arch': 'x86_64', 'cpus': '8', + 'local_gb': '0', 'memory_mb': 2048 } task.driver.inspect.inspect_hardware(task) self.assertEqual(expected_properties, task.node.properties) + @mock.patch.object(redfish_utils, 'get_system', autospec=True) + def test_inspect_hardware_ignore_missing_simple_storage(self, + mock_get_system): + system_mock = self.init_system_mock(mock_get_system.return_value) + system_mock.simple_storage = None + + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + expected_properties = { + 'capabilities': 'boot_mode:uefi', + 'cpu_arch': 'x86_64', 'cpus': '8', + 'local_gb': '3', 'memory_mb': 2048 + } + task.driver.inspect.inspect_hardware(task) + self.assertEqual(expected_properties, task.node.properties) + + inventory = inspect_utils.get_inspection_data(task.node, + self.context) + self.assertNotIn('disks', inventory['inventory']) + @mock.patch.object(redfish_utils, 'get_system', autospec=True) def test_inspect_hardware_fail_missing_memory_mb(self, mock_get_system): system_mock = self.init_system_mock(mock_get_system.return_value) @@ -179,11 +316,16 @@ class RedfishInspectTestCase(db_base.DbTestCase): shared=True) as task: expected_properties = { 'capabilities': 'boot_mode:uefi', - 'cpu_arch': 'mips', 'local_gb': '3', 'memory_mb': '4096' + 'cpu_arch': 'x86_64', 'cpus': '8', + 'local_gb': '3', 'memory_mb': '4096' } task.driver.inspect.inspect_hardware(task) self.assertEqual(expected_properties, task.node.properties) + inventory = inspect_utils.get_inspection_data(task.node, + self.context) + self.assertNotIn('memory', inventory['inventory']) + @mock.patch.object(redfish_utils, 'get_system', autospec=True) @mock.patch.object(inspect_utils, 'create_ports_if_not_exist', autospec=True) @@ -197,6 +339,10 @@ class RedfishInspectTestCase(db_base.DbTestCase): task.driver.inspect.inspect_hardware(task) self.assertFalse(mock_create_ports_if_not_exist.called) + inventory = inspect_utils.get_inspection_data(task.node, + self.context) + self.assertNotIn('interfaces', inventory['inventory']) + @mock.patch.object(redfish_utils, 'get_system', autospec=True) def test_inspect_hardware_preserve_boot_mode(self, mock_get_system): self.init_system_mock(mock_get_system.return_value) @@ -208,11 +354,19 @@ class RedfishInspectTestCase(db_base.DbTestCase): } expected_properties = { 'capabilities': 'boot_mode:bios', - 'cpu_arch': 'mips', 'local_gb': '3', 'memory_mb': '2048' + 'cpu_arch': 'x86_64', 'cpus': '8', + 'local_gb': '3', 'memory_mb': 2048 } + task.driver.inspect.inspect_hardware(task) self.assertEqual(expected_properties, task.node.properties) + inventory = inspect_utils.get_inspection_data(task.node, + self.context) + expected_boot_mode = {'current_boot_mode': 'uefi'} + self.assertEqual(expected_boot_mode, + inventory['inventory']['boot']) + @mock.patch.object(redfish_utils, 'get_system', autospec=True) def test_inspect_hardware_ignore_missing_boot_mode(self, mock_get_system): system_mock = self.init_system_mock(mock_get_system.return_value) @@ -221,10 +375,14 @@ class RedfishInspectTestCase(db_base.DbTestCase): with task_manager.acquire(self.context, self.node.uuid, shared=True) as task: expected_properties = { - 'cpu_arch': 'mips', 'local_gb': '3', 'memory_mb': '2048' + 'cpu_arch': 'x86_64', 'cpus': '8', + 'local_gb': '3', 'memory_mb': 2048 } task.driver.inspect.inspect_hardware(task) self.assertEqual(expected_properties, task.node.properties) + inventory = inspect_utils.get_inspection_data(task.node, + self.context) + self.assertNotIn('boot', inventory['inventory']) @mock.patch.object(objects.Port, 'list_by_node_id') # noqa @mock.patch.object(redfish_utils, 'get_system', autospec=True) @@ -335,6 +493,21 @@ class RedfishInspectTestCase(db_base.DbTestCase): for port in ports: self.assertIn(port.address, expected_port_mac_list) + @mock.patch.object(redfish_utils, 'get_system', autospec=True) + def test_inspect_hardware_ignore_missing_system_vendor(self, + mock_get_system): + system_mock = self.init_system_mock(mock_get_system.return_value) + system_mock.name = None + system_mock.serial_number = None + system_mock.manufacturer = None + + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + task.driver.inspect.inspect_hardware(task) + inventory = inspect_utils.get_inspection_data(task.node, + self.context) + self.assertNotIn('system_vendor', inventory['inventory']) + def test_get_pxe_port_macs(self): expected_properties = None with task_manager.acquire(self.context, self.node.uuid, diff --git a/releasenotes/notes/add-redfish-hardware-inventory-830ebc0b2e0f50c2.yaml b/releasenotes/notes/add-redfish-hardware-inventory-830ebc0b2e0f50c2.yaml new file mode 100644 index 0000000000..81310e7851 --- /dev/null +++ b/releasenotes/notes/add-redfish-hardware-inventory-830ebc0b2e0f50c2.yaml @@ -0,0 +1,8 @@ +--- +features: + - Uses Redfish to collect the available hardware inventory information and + stores it in the right format. Information collected includes cpu + information including "count", "architecture", "model_name", and + "frequency", disk "size" (in bytes), interface "mac_address", + "system_vendor" information including "product_name", "serial_number" and + "manufacturer", and "current_boot_mode".