Skip to content

Commit 0304c73

Browse files
committed
Report system firmware information in the inventory
Change-Id: I5b6ceb9cdcf4baa97a6f0482d1030d14f3f2ecff
1 parent 2ddb693 commit 0304c73

File tree

6 files changed

+90
-20
lines changed

6 files changed

+90
-20
lines changed

doc/source/admin/how_it_works.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,9 @@ fields:
198198

199199
``system_vendor``
200200
system vendor information from SMBIOS as reported by ``dmidecode``:
201-
``product_name``, ``serial_number`` and ``manufacturer``.
201+
``product_name``, ``serial_number`` and ``manufacturer``, as well as
202+
a ``firmware`` structure with fields ``vendor``, ``version`` and
203+
``build_date``.
202204

203205
``boot``
204206
boot information with fields: ``current_boot_mode`` (boot mode used for

ironic_python_agent/hardware.py

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -339,22 +339,16 @@ def get_component_devices(raid_device):
339339

340340
def _calc_memory(sys_dict):
341341
physical = 0
342-
for sys_child in sys_dict['children']:
343-
if sys_child['id'] != 'core':
344-
continue
345-
for core_child in sys_child['children']:
346-
if not _MEMORY_ID_RE.match(core_child['id']):
347-
continue
348-
if core_child.get('size'):
349-
value = ("%(size)s %(units)s" % core_child)
350-
physical += int(UNIT_CONVERTER(value).to
351-
('MB').magnitude)
352-
else:
353-
for bank in core_child.get('children', ()):
354-
if bank.get('size'):
355-
value = ("%(size)s %(units)s" % bank)
356-
physical += int(UNIT_CONVERTER(value).to
357-
('MB').magnitude)
342+
core_dict = next(utils.find_in_lshw(sys_dict, 'core'), {})
343+
for core_child in utils.find_in_lshw(core_dict, _MEMORY_ID_RE):
344+
if core_child.get('size'):
345+
value = ("%(size)s %(units)s" % core_child)
346+
physical += int(UNIT_CONVERTER(value).to('MB').magnitude)
347+
else:
348+
for bank in core_child.get('children', ()):
349+
if bank.get('size'):
350+
value = ("%(size)s %(units)s" % bank)
351+
physical += int(UNIT_CONVERTER(value).to('MB').magnitude)
358352
return physical
359353

360354

@@ -835,13 +829,24 @@ def __init__(self, total, physical_mb=None):
835829
self.physical_mb = physical_mb
836830

837831

832+
class SystemFirmware(encoding.SerializableComparable):
833+
serializable_fields = ('vendor', 'version', 'build_date')
834+
835+
def __init__(self, vendor, version, build_date):
836+
self.version = version
837+
self.build_date = build_date
838+
self.vendor = vendor
839+
840+
838841
class SystemVendorInfo(encoding.SerializableComparable):
839-
serializable_fields = ('product_name', 'serial_number', 'manufacturer')
842+
serializable_fields = ('product_name', 'serial_number', 'manufacturer',
843+
'firmware')
840844

841-
def __init__(self, product_name, serial_number, manufacturer):
845+
def __init__(self, product_name, serial_number, manufacturer, firmware):
842846
self.product_name = product_name
843847
self.serial_number = serial_number
844848
self.manufacturer = manufacturer
849+
self.firmware = firmware
845850

846851

847852
class BootInfo(encoding.SerializableComparable):
@@ -1512,9 +1517,17 @@ def get_system_vendor_info(self):
15121517
except (processutils.ProcessExecutionError, OSError, ValueError) as e:
15131518
LOG.warning('Could not retrieve vendor info from lshw: %s', e)
15141519
sys_dict = {}
1520+
1521+
core_dict = next(utils.find_in_lshw(sys_dict, 'core'), {})
1522+
fw_dict = next(utils.find_in_lshw(core_dict, 'firmware'), {})
1523+
1524+
firmware = SystemFirmware(vendor=fw_dict.get('vendor', ''),
1525+
version=fw_dict.get('version', ''),
1526+
build_date=fw_dict.get('date', ''))
15151527
return SystemVendorInfo(product_name=sys_dict.get('product', ''),
15161528
serial_number=sys_dict.get('serial', ''),
1517-
manufacturer=sys_dict.get('vendor', ''))
1529+
manufacturer=sys_dict.get('vendor', ''),
1530+
firmware=firmware)
15181531

15191532
def get_boot_info(self):
15201533
boot_mode = 'uefi' if os.path.isdir('/sys/firmware/efi') else 'bios'

ironic_python_agent/tests/unit/samples/hardware_samples.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,34 @@
490490
"serial" : "1234",
491491
"slot" : "NULL",
492492
"children" : [
493+
{
494+
"id": "firmware",
495+
"class": "memory",
496+
"claimed": true,
497+
"description": "BIOS",
498+
"vendor": "BIOSVNDR",
499+
"physid": "0",
500+
"version": "1.2.3",
501+
"date": "03/30/2023",
502+
"units": "bytes",
503+
"size": 65536,
504+
"capacity": 16777216,
505+
"capabilities": {
506+
"isa": "ISA bus",
507+
"pci": "PCI bus",
508+
"pnp": "Plug-and-Play",
509+
"upgrade": "BIOS EEPROM can be upgraded",
510+
"shadowing": "BIOS shadowing",
511+
"cdboot": "Booting from CD-ROM/DVD",
512+
"bootselect": "Selectable boot path",
513+
"edd": "Enhanced Disk Drive extensions",
514+
"acpi": "ACPI",
515+
"usb": "USB legacy emulation",
516+
"biosbootspecification": "BIOS boot specification",
517+
"netboot": "Function-key initiated network service boot",
518+
"uefi": "UEFI specification is supported"
519+
}
520+
},
493521
{
494522
"id" : "memory:0",
495523
"class" : "memory",

ironic_python_agent/tests/unit/test_hardware.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5247,6 +5247,10 @@ def test_get_system_vendor_info(self, mocked_execute):
52475247
self.assertEqual('ABC123 (GENERIC_SERVER)', vendor_info.product_name)
52485248
self.assertEqual('1234567', vendor_info.serial_number)
52495249
self.assertEqual('GENERIC', vendor_info.manufacturer)
5250+
# This sample does not have firmware information
5251+
self.assertEqual('', vendor_info.firmware.vendor)
5252+
self.assertEqual('', vendor_info.firmware.build_date)
5253+
self.assertEqual('', vendor_info.firmware.version)
52505254

52515255
@mock.patch.object(il_utils, 'execute', autospec=True)
52525256
def test_get_system_vendor_info_lshw_list(self, mocked_execute):
@@ -5255,6 +5259,9 @@ def test_get_system_vendor_info_lshw_list(self, mocked_execute):
52555259
self.assertEqual('ABCD', vendor_info.product_name)
52565260
self.assertEqual('1234', vendor_info.serial_number)
52575261
self.assertEqual('ABCD', vendor_info.manufacturer)
5262+
self.assertEqual('BIOSVNDR', vendor_info.firmware.vendor)
5263+
self.assertEqual('03/30/2023', vendor_info.firmware.build_date)
5264+
self.assertEqual('1.2.3', vendor_info.firmware.version)
52585265

52595266
@mock.patch.object(il_utils, 'execute', autospec=True)
52605267
def test_get_system_vendor_info_failure(self, mocked_execute):
@@ -5263,6 +5270,9 @@ def test_get_system_vendor_info_failure(self, mocked_execute):
52635270
self.assertEqual('', vendor_info.product_name)
52645271
self.assertEqual('', vendor_info.serial_number)
52655272
self.assertEqual('', vendor_info.manufacturer)
5273+
self.assertEqual('', vendor_info.firmware.vendor)
5274+
self.assertEqual('', vendor_info.firmware.build_date)
5275+
self.assertEqual('', vendor_info.firmware.version)
52665276

52675277
@mock.patch.object(utils, 'get_agent_params',
52685278
lambda: {'BOOTIF': 'boot:if'})

ironic_python_agent/utils.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,3 +917,15 @@ def rescan_device(device):
917917
except processutils.ProcessExecutionError as e:
918918
LOG.warning('Something went wrong when waiting for udev '
919919
'to settle. Error: %s', e)
920+
921+
922+
def find_in_lshw(lshw, by_id):
923+
"""Yield all suitable records from lshw."""
924+
for child in lshw.get('children', ()):
925+
lshw_id = child.get('id', '')
926+
if isinstance(by_id, re.Pattern):
927+
if by_id.match(lshw_id) is not None:
928+
yield child
929+
else:
930+
if by_id == lshw_id:
931+
yield child
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
features:
3+
- |
4+
The hardware inventory now contains information about the system firmware:
5+
vendor, version and the build date.

0 commit comments

Comments
 (0)