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
This commit is contained in:
Mahnoor Asghar 2023-05-03 12:08:47 -04:00
parent 7f281392c2
commit b3d7ba88d2
3 changed files with 275 additions and 23 deletions

View File

@ -41,6 +41,17 @@ if sushy:
sushy.PROCESSOR_ARCH_OEM: 'oem' 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 = { BOOT_MODE_MAP = {
sushy.BOOT_SOURCE_MODE_UEFI: boot_modes.UEFI, sushy.BOOT_SOURCE_MODE_UEFI: boot_modes.UEFI,
sushy.BOOT_SOURCE_MODE_BIOS: boot_modes.LEGACY_BIOS 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 # get the essential properties and update the node properties
# with it. # with it.
inspected_properties = task.node.properties inspected_properties = task.node.properties
inventory = {}
if system.memory_summary and system.memory_summary.size_gib: if system.memory_summary and system.memory_summary.size_gib:
inspected_properties['memory_mb'] = str( memory = system.memory_summary.size_gib * units.Ki
system.memory_summary.size_gib * units.Ki) inspected_properties['memory_mb'] = memory
inventory['memory'] = {'physical_mb': memory}
if system.processors and system.processors.summary: self._get_processor_info(task, system, inspected_properties, inventory)
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})
# TODO(etingof): should we respect root device hints here? # TODO(etingof): should we respect root device hints here?
local_gb = self._detect_local_gb(task, system) local_gb = self._detect_local_gb(task, system)
if local_gb: if local_gb:
inspected_properties['local_gb'] = str(local_gb) inspected_properties['local_gb'] = str(local_gb)
else: else:
LOG.warning("Could not provide a valid storage size configured " LOG.warning("Could not provide a valid storage size configured "
"for node %(node)s. Assuming this is a disk-less node", "for node %(node)s. Assuming this is a disk-less node",
{'node': task.node.uuid}) {'node': task.node.uuid})
inspected_properties['local_gb'] = '0' 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 system.boot.mode:
if not drivers_utils.get_node_capability(task.node, 'boot_mode'): if not drivers_utils.get_node_capability(task.node, 'boot_mode'):
capabilities = utils.get_updated_capabilities( capabilities = utils.get_updated_capabilities(
@ -137,6 +173,8 @@ class RedfishInspect(base.InspectInterface):
{'boot_mode': BOOT_MODE_MAP[system.boot.mode]}) {'boot_mode': BOOT_MODE_MAP[system.boot.mode]})
inspected_properties['capabilities'] = capabilities inspected_properties['capabilities'] = capabilities
inventory['boot'] = {'current_boot_mode':
BOOT_MODE_MAP[system.boot.mode]}
valid_keys = self.ESSENTIAL_PROPERTIES valid_keys = self.ESSENTIAL_PROPERTIES
missing_keys = valid_keys - set(inspected_properties) missing_keys = valid_keys - set(inspected_properties)
@ -182,6 +220,8 @@ class RedfishInspect(base.InspectInterface):
LOG.warning("No port information discovered " LOG.warning("No port information discovered "
"for node %(node)s", {'node': task.node.uuid}) "for node %(node)s", {'node': task.node.uuid})
inspect_utils.store_inspection_data(task.node,
inventory, None, task.context)
return states.MANAGEABLE return states.MANAGEABLE
def _create_ports(self, task, system): def _create_ports(self, task, system):
@ -269,3 +309,34 @@ class RedfishInspect(base.InspectInterface):
If cannot be determined, returns None. If cannot be determined, returns None.
""" """
return 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

View File

@ -58,7 +58,22 @@ class RedfishInspectTestCase(db_base.DbTestCase):
system_mock.memory_summary.size_gib = 2 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 = ( system_mock.simple_storage.disks_sizes_bytes = (
1 * units.Gi, units.Gi * 3, units.Gi * 5) 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, '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 return system_mock
def test_get_properties(self): def test_get_properties(self):
@ -93,9 +114,9 @@ class RedfishInspectTestCase(db_base.DbTestCase):
mock_get_system): mock_get_system):
expected_properties = { expected_properties = {
'capabilities': 'boot_mode:uefi', '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) self.init_system_mock(mock_get_system.return_value)
with task_manager.acquire(self.context, self.node.uuid, 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) mock_get_system.assert_called_once_with(task.node)
self.assertEqual(expected_properties, task.node.properties) 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(redfish_utils, 'get_system', autospec=True)
@mock.patch.object(inspect_utils, 'create_ports_if_not_exist', @mock.patch.object(inspect_utils, 'create_ports_if_not_exist',
autospec=True) autospec=True)
@ -120,7 +169,7 @@ class RedfishInspectTestCase(db_base.DbTestCase):
task, result) task, result)
@mock.patch.object(redfish_utils, 'get_system', autospec=True) @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 = self.init_system_mock(mock_get_system.return_value)
system_mock.processors.summary = None, None system_mock.processors.summary = None, None
@ -131,7 +180,7 @@ class RedfishInspectTestCase(db_base.DbTestCase):
task.driver.inspect.inspect_hardware, task) task.driver.inspect.inspect_hardware, task)
@mock.patch.object(redfish_utils, 'get_system', autospec=True) @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 = self.init_system_mock(mock_get_system.return_value)
system_mock.processors.summary = None, None system_mock.processors.summary = None, None
@ -139,11 +188,78 @@ class RedfishInspectTestCase(db_base.DbTestCase):
shared=True) as task: shared=True) as task:
expected_properties = { expected_properties = {
'capabilities': 'boot_mode:uefi', '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) task.driver.inspect.inspect_hardware(task)
self.assertEqual(expected_properties, task.node.properties) 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) @mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test_inspect_hardware_ignore_missing_local_gb(self, mock_get_system): def test_inspect_hardware_ignore_missing_local_gb(self, mock_get_system):
system_mock = self.init_system_mock(mock_get_system.return_value) system_mock = self.init_system_mock(mock_get_system.return_value)
@ -154,11 +270,32 @@ class RedfishInspectTestCase(db_base.DbTestCase):
shared=True) as task: shared=True) as task:
expected_properties = { expected_properties = {
'capabilities': 'boot_mode:uefi', '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) task.driver.inspect.inspect_hardware(task)
self.assertEqual(expected_properties, task.node.properties) 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) @mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test_inspect_hardware_fail_missing_memory_mb(self, mock_get_system): def test_inspect_hardware_fail_missing_memory_mb(self, mock_get_system):
system_mock = self.init_system_mock(mock_get_system.return_value) system_mock = self.init_system_mock(mock_get_system.return_value)
@ -179,11 +316,16 @@ class RedfishInspectTestCase(db_base.DbTestCase):
shared=True) as task: shared=True) as task:
expected_properties = { expected_properties = {
'capabilities': 'boot_mode:uefi', '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) task.driver.inspect.inspect_hardware(task)
self.assertEqual(expected_properties, task.node.properties) 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(redfish_utils, 'get_system', autospec=True)
@mock.patch.object(inspect_utils, 'create_ports_if_not_exist', @mock.patch.object(inspect_utils, 'create_ports_if_not_exist',
autospec=True) autospec=True)
@ -197,6 +339,10 @@ class RedfishInspectTestCase(db_base.DbTestCase):
task.driver.inspect.inspect_hardware(task) task.driver.inspect.inspect_hardware(task)
self.assertFalse(mock_create_ports_if_not_exist.called) 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) @mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test_inspect_hardware_preserve_boot_mode(self, mock_get_system): def test_inspect_hardware_preserve_boot_mode(self, mock_get_system):
self.init_system_mock(mock_get_system.return_value) self.init_system_mock(mock_get_system.return_value)
@ -208,11 +354,19 @@ class RedfishInspectTestCase(db_base.DbTestCase):
} }
expected_properties = { expected_properties = {
'capabilities': 'boot_mode:bios', '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) task.driver.inspect.inspect_hardware(task)
self.assertEqual(expected_properties, task.node.properties) 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) @mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test_inspect_hardware_ignore_missing_boot_mode(self, mock_get_system): def test_inspect_hardware_ignore_missing_boot_mode(self, mock_get_system):
system_mock = self.init_system_mock(mock_get_system.return_value) 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, with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task: shared=True) as task:
expected_properties = { 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) task.driver.inspect.inspect_hardware(task)
self.assertEqual(expected_properties, task.node.properties) 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(objects.Port, 'list_by_node_id') # noqa
@mock.patch.object(redfish_utils, 'get_system', autospec=True) @mock.patch.object(redfish_utils, 'get_system', autospec=True)
@ -335,6 +493,21 @@ class RedfishInspectTestCase(db_base.DbTestCase):
for port in ports: for port in ports:
self.assertIn(port.address, expected_port_mac_list) 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): def test_get_pxe_port_macs(self):
expected_properties = None expected_properties = None
with task_manager.acquire(self.context, self.node.uuid, with task_manager.acquire(self.context, self.node.uuid,

View File

@ -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".