diff options
-rw-r--r-- | doc/source/admin/how_it_works.rst | 18 | ||||
-rw-r--r-- | ironic_python_agent/config.py | 4 | ||||
-rw-r--r-- | ironic_python_agent/efi_utils.py | 15 | ||||
-rw-r--r-- | ironic_python_agent/hardware.py | 36 | ||||
-rw-r--r-- | ironic_python_agent/inspector.py | 9 | ||||
-rw-r--r-- | ironic_python_agent/netutils.py | 24 | ||||
-rw-r--r-- | ironic_python_agent/tests/unit/extensions/test_image.py | 45 | ||||
-rw-r--r-- | ironic_python_agent/tests/unit/test_efi_utils.py | 85 | ||||
-rw-r--r-- | releasenotes/notes/fixes-efibootmgr-character-encoding-19e531ba694824c1.yaml | 9 | ||||
-rw-r--r-- | releasenotes/notes/lldp-raw-a09174cb930bca97.yaml | 12 | ||||
-rw-r--r-- | setup.cfg | 1 | ||||
-rw-r--r-- | tox.ini | 2 |
12 files changed, 164 insertions, 96 deletions
diff --git a/doc/source/admin/how_it_works.rst b/doc/source/admin/how_it_works.rst index 973588b1..20a5f477 100644 --- a/doc/source/admin/how_it_works.rst +++ b/doc/source/admin/how_it_works.rst @@ -145,8 +145,16 @@ collectors are: * ``nics`` - list of objects with keys ``name`` (NIC name) and ``numa_node`` (node ID). +``lldp`` + Collects information about the network connectivity using LLDP_. Provides + one key: + + * ``lldp_raw`` - mapping of interface names to lists of raw + type-length-value (TLV) records. + .. _hardware: https://pypi.org/project/hardware/ .. _NUMA: https://en.wikipedia.org/wiki/Non-uniform_memory_access +.. _LLDP: https://en.wikipedia.org/wiki/Link_Layer_Discovery_Protocol .. _hardware-inventory: @@ -191,10 +199,12 @@ fields: ``interfaces`` list of network interfaces with fields: ``name``, ``mac_address``, ``ipv4_address``, ``lldp``, ``vendor``, ``product``, and optionally - ``biosdevname`` (BIOS given NIC name). If configuration option - ``collect_lldp`` is set to True the ``lldp`` field will be populated - by a list of type-length-value(TLV) fields retrieved using the - Link Layer Discovery Protocol (LLDP). + ``biosdevname`` (BIOS given NIC name). + + .. note:: + For backward compatibility, interfaces may contain ``lldp`` fields. + They are deprecated, consumers should rely on the ``lldp`` inspection + collector instead. ``system_vendor`` system vendor information from SMBIOS as reported by ``dmidecode``: diff --git a/ironic_python_agent/config.py b/ironic_python_agent/config.py index b1760179..5c5de305 100644 --- a/ironic_python_agent/config.py +++ b/ironic_python_agent/config.py @@ -149,7 +149,9 @@ cli_opts = [ help='Whether IPA should attempt to receive LLDP packets for ' 'each network interface it discovers in the inventory. ' 'Can be supplied as "ipa-collect-lldp" ' - 'kernel parameter.'), + 'kernel parameter.', + deprecated_for_removal=True, + deprecated_reason="Use the lldp collector instead"), cfg.StrOpt('inspection_callback_url', default=APARAMS.get('ipa-inspection-callback-url'), diff --git a/ironic_python_agent/efi_utils.py b/ironic_python_agent/efi_utils.py index da5e97b6..48708f22 100644 --- a/ironic_python_agent/efi_utils.py +++ b/ironic_python_agent/efi_utils.py @@ -275,8 +275,15 @@ def get_boot_records(): :return: an iterator yielding pairs (boot number, boot record). """ - efi_output = utils.execute('efibootmgr', '-v') - for line in efi_output[0].split('\n'): + # Invokes binary=True so we get a bytestream back. + efi_output = utils.execute('efibootmgr', '-v', binary=True) + # Bytes must be decoded before regex can be run and + # matching to work as intended. + # Also ignore errors on decoding, as we can basically get + # garbage out of the nvram record, this way we don't fail + # hard on unrelated records. + cmd_output = efi_output[0].decode('utf-16', errors='ignore') + for line in cmd_output.split('\n'): match = _ENTRY_LABEL.match(line) if match is not None: yield (match[1], match[2]) @@ -293,7 +300,7 @@ def add_boot_record(device, efi_partition, loader, label): # https://linux.die.net/man/8/efibootmgr utils.execute('efibootmgr', '-v', '-c', '-d', device, '-p', str(efi_partition), '-w', '-L', label, - '-l', loader) + '-l', loader, binary=True) def remove_boot_record(boot_num): @@ -301,7 +308,7 @@ def remove_boot_record(boot_num): :param boot_num: the number of the boot record """ - utils.execute('efibootmgr', '-b', boot_num, '-B') + utils.execute('efibootmgr', '-b', boot_num, '-B', binary=True) def _run_efibootmgr(valid_efi_bootloaders, device, efi_partition, diff --git a/ironic_python_agent/hardware.py b/ironic_python_agent/hardware.py index 8dd3a961..badb07db 100644 --- a/ironic_python_agent/hardware.py +++ b/ironic_python_agent/hardware.py @@ -865,6 +865,9 @@ class HardwareManager(object, metaclass=abc.ABCMeta): def list_network_interfaces(self): raise errors.IncompatibleHardwareMethodError + def collect_lldp_data(self, interface_names=None): + raise errors.IncompatibleHardwareMethodError + def get_cpus(self): raise errors.IncompatibleHardwareMethodError @@ -1198,7 +1201,6 @@ class GenericHardwareManager(HardwareManager): HARDWARE_MANAGER_VERSION = '1.1' def __init__(self): - self.sys_path = '/sys' self.lldp_data = {} def evaluate_hardware_support(self): @@ -1213,7 +1215,7 @@ class GenericHardwareManager(HardwareManager): self.wait_for_disks() return HardwareSupport.GENERIC - def collect_lldp_data(self, interface_names): + def collect_lldp_data(self, interface_names=None): """Collect and convert LLDP info from the node. In order to process the LLDP information later, the raw data needs to @@ -1222,7 +1224,8 @@ class GenericHardwareManager(HardwareManager): :param interface_names: list of names of node's interfaces. :return: a dict, containing the lldp data from every interface. """ - + if interface_names is None: + interface_names = netutils.list_interfaces() interface_names = [name for name in interface_names if name != 'lo'] lldp_data = {} try: @@ -1292,7 +1295,7 @@ class GenericHardwareManager(HardwareManager): """ global WARN_BIOSDEVNAME_NOT_FOUND - if self._is_vlan(interface_name): + if netutils.is_vlan(interface_name): LOG.debug('Interface %s is a VLAN, biosdevname not called', interface_name) return @@ -1313,35 +1316,14 @@ class GenericHardwareManager(HardwareManager): else: LOG.warning('Biosdevname returned exit code %s', e.exit_code) - def _is_device(self, interface_name): - device_path = '{}/class/net/{}/device'.format(self.sys_path, - interface_name) - return os.path.exists(device_path) - - def _is_vlan(self, interface_name): - # A VLAN interface does not have /device, check naming convention - # used when adding VLAN interface - - interface, sep, vlan = interface_name.partition('.') - - return vlan.isdigit() - - def _is_bond(self, interface_name): - device_path = '{}/class/net/{}/bonding'.format(self.sys_path, - interface_name) - return os.path.exists(device_path) - def list_network_interfaces(self): - network_interfaces_list = [] - iface_names = os.listdir('{}/class/net'.format(self.sys_path)) - iface_names = [name for name in iface_names - if self._is_vlan(name) or self._is_device(name) - or self._is_bond(name)] + iface_names = netutils.list_interfaces() if CONF.collect_lldp: self.lldp_data = dispatch_to_managers('collect_lldp_data', interface_names=iface_names) + network_interfaces_list = [] for iface_name in iface_names: try: result = dispatch_to_managers( diff --git a/ironic_python_agent/inspector.py b/ironic_python_agent/inspector.py index 8e570c3b..c2d98d5d 100644 --- a/ironic_python_agent/inspector.py +++ b/ironic_python_agent/inspector.py @@ -364,3 +364,12 @@ def collect_pci_devices_info(data, failures): 'revision': pci_revision, 'bus': subdir}) data['pci_devices'] = pci_devices_info + + +def collect_lldp(data, failures): + """Collect LLDP information for network interfaces. + + :param data: mutable data that we'll send to inspector + :param failures: AccumulatedFailures object + """ + data['lldp_raw'] = hardware.dispatch_to_managers('collect_lldp_data') diff --git a/ironic_python_agent/netutils.py b/ironic_python_agent/netutils.py index 0b595d96..2e289808 100644 --- a/ironic_python_agent/netutils.py +++ b/ironic_python_agent/netutils.py @@ -14,6 +14,7 @@ import ctypes import fcntl +import os import select import socket import struct @@ -247,6 +248,29 @@ def get_hostname(): return socket.gethostname() +def is_network_device(interface_name): + device_path = f'/sys/class/net/{interface_name}/device' + return os.path.exists(device_path) + + +def is_vlan(interface_name): + # A VLAN interface does not have /device, check naming convention + # used when adding VLAN interface + interface, sep, vlan = interface_name.partition('.') + return vlan.isdigit() + + +def is_bond(interface_name): + device_path = f'/sys/class/net/{interface_name}/bonding' + return os.path.exists(device_path) + + +def list_interfaces(): + iface_names = os.listdir('/sys/class/net') + return [name for name in iface_names + if is_vlan(name) or is_network_device(name) or is_bond(name)] + + def interface_has_carrier(interface_name): path = '/sys/class/net/{}/carrier'.format(interface_name) try: diff --git a/ironic_python_agent/tests/unit/extensions/test_image.py b/ironic_python_agent/tests/unit/extensions/test_image.py index 46afe0ef..2bfa782c 100644 --- a/ironic_python_agent/tests/unit/extensions/test_image.py +++ b/ironic_python_agent/tests/unit/extensions/test_image.py @@ -32,6 +32,9 @@ from ironic_python_agent.tests.unit import base from ironic_python_agent.tests.unit.samples import hardware_samples as hws +EFI_RESULT = ''.encode('utf-16') + + @mock.patch.object(hardware, 'dispatch_to_managers', autospec=True) @mock.patch.object(ilib_utils, 'execute', autospec=True) @mock.patch.object(tempfile, 'mkdtemp', lambda *_: '/tmp/fake-dir') @@ -231,7 +234,7 @@ class TestImageExtension(base.IronicAgentTest): mock_execute.side_effect = iter([('', ''), ('', ''), ('', ''), ('', ''), - ('', ''), ('', ''), + (EFI_RESULT, ''), (EFI_RESULT, ''), ('', ''), ('', '')]) expected = [mock.call('efibootmgr', '--version'), @@ -240,11 +243,11 @@ class TestImageExtension(base.IronicAgentTest): mock.call('udevadm', 'settle'), mock.call('mount', self.fake_efi_system_part, self.fake_dir + '/boot/efi'), - mock.call('efibootmgr', '-v'), + mock.call('efibootmgr', '-v', binary=True), mock.call('efibootmgr', '-v', '-c', '-d', self.fake_dev, '-p', '1', '-w', '-L', 'ironic1', '-l', - '\\EFI\\BOOT\\BOOTX64.EFI'), + '\\EFI\\BOOT\\BOOTX64.EFI', binary=True), mock.call('umount', self.fake_dir + '/boot/efi', attempts=3, delay_on_retry=True), mock.call('sync')] @@ -279,7 +282,7 @@ class TestImageExtension(base.IronicAgentTest): mock_efi_bl.return_value = ['EFI/BOOT/BOOTX64.EFI'] mock_execute.side_effect = iter([('', ''), ('', ''), ('', ''), ('', ''), - ('', ''), ('', ''), + (EFI_RESULT, ''), (EFI_RESULT, ''), ('', ''), ('', '')]) expected = [mock.call('efibootmgr', '--version'), @@ -288,11 +291,11 @@ class TestImageExtension(base.IronicAgentTest): mock.call('udevadm', 'settle'), mock.call('mount', self.fake_efi_system_part, self.fake_dir + '/boot/efi'), - mock.call('efibootmgr', '-v'), + mock.call('efibootmgr', '-v', binary=True), mock.call('efibootmgr', '-v', '-c', '-d', self.fake_dev, '-p', '1', '-w', '-L', 'ironic1', '-l', - '\\EFI\\BOOT\\BOOTX64.EFI'), + '\\EFI\\BOOT\\BOOTX64.EFI', binary=True), mock.call('umount', self.fake_dir + '/boot/efi', attempts=3, delay_on_retry=True), mock.call('sync')] @@ -333,10 +336,11 @@ Boot0000 ironic1 HD(1,GPT,4f3c6294-bf9b-4208-9808-be45dfc34b5c)File(\EFI\Boot\BO Boot0001 ironic2 HD(1,GPT,4f3c6294-bf9b-4208-9808-111111111112)File(\EFI\Boot\BOOTX64.EFI) Boot0002 VENDMAGIC FvFile(9f3c6294-bf9b-4208-9808-be45dfc34b51) """ # noqa This is a giant literal string for testing. + stdout_msg = stdout_msg.encode('utf-16') mock_execute.side_effect = iter([('', ''), ('', ''), ('', ''), ('', ''), - (stdout_msg, ''), ('', ''), - ('', ''), ('', ''), + (stdout_msg, ''), (EFI_RESULT, ''), + (EFI_RESULT, ''), (EFI_RESULT, ''), ('', ''), ('', '')]) expected = [mock.call('efibootmgr', '--version'), @@ -345,12 +349,12 @@ Boot0002 VENDMAGIC FvFile(9f3c6294-bf9b-4208-9808-be45dfc34b51) mock.call('udevadm', 'settle'), mock.call('mount', self.fake_efi_system_part, self.fake_dir + '/boot/efi'), - mock.call('efibootmgr', '-v'), - mock.call('efibootmgr', '-b', '0000', '-B'), + mock.call('efibootmgr', '-v', binary=True), + mock.call('efibootmgr', '-b', '0000', '-B', binary=True), mock.call('efibootmgr', '-v', '-c', '-d', self.fake_dev, '-p', '1', '-w', '-L', 'ironic1', '-l', - '\\EFI\\BOOT\\BOOTX64.EFI'), + '\\EFI\\BOOT\\BOOTX64.EFI', binary=True), mock.call('umount', self.fake_dir + '/boot/efi', attempts=3, delay_on_retry=True), mock.call('sync')] @@ -396,6 +400,7 @@ Boot0002* Hard Disk VenHw(1fad3248-0000-7950-2166-a1e506fdb83a,01000000)..GO Boot0003* Network VenHw(1fad3248-0000-7950-2166-a1e506fdb83a,05000000)..GO..NO............U.E.F.I.:. . . .S.L.O.T.2. .(.2.F./.0./.0.). .P.X.E. .I.P.4. . .Q.L.o.g.i.c. .Q.L.4.1.2.6.2. .P.C.I.e. .2.5.G.b. .2.-.P.o.r.t. .S.F.P.2.8. .E.t.h.e.r.n.e.t. .A.d.a.p.t.e.r. .-. .P.X.E........A....................%.4..Z...............................................................Gd-.;.A..MQ..L.P.X.E. .I.P.4. .Q.L.o.g.i.c. .Q.L.4.1.2.6.2. .P.C.I.e. .2.5.G.b. .2.-.P.o.r.t. .S.F.P.2.8. .E.t.h.e.r.n.e.t. .A.d.a.p.t.e.r. .-. .P.X.E.......BO..NO............U.E.F.I.:. . . .S.L.O.T.1. .(.3.0./.0./.0.). .P.X.E. .I.P.4. . .Q.L.o.g.i.c. .Q.L.4.1.2.6.2. .P.C.I.e. .2.5.G.b. .2.-.P.o.r.t. .S.F.P.2.8. .E.t.h.e.r.n.e.t. .A.d.a.p.t.e.r. .-. Boot0004* ironic1 HD(1,GPT,55db8d03-c8f6-4a5b-9155-790dddc348fa,0x800,0x64000)/File(\EFI\boot\shimx64.efi) """ # noqa This is a giant literal string for testing. + stdout_msg = stdout_msg.encode('utf-16') mock_execute.side_effect = iter([('', ''), ('', ''), ('', ''), ('', ''), (stdout_msg, ''), ('', ''), @@ -407,13 +412,13 @@ Boot0004* ironic1 HD(1,GPT,55db8d03-c8f6-4a5b-9155-790dddc348fa,0x800,0x640 mock.call('udevadm', 'settle'), mock.call('mount', self.fake_efi_system_part, self.fake_dir + '/boot/efi'), - mock.call('efibootmgr', '-v'), - mock.call('efibootmgr', '-b', '0000', '-B'), - mock.call('efibootmgr', '-b', '0004', '-B'), + mock.call('efibootmgr', '-v', binary=True), + mock.call('efibootmgr', '-b', '0000', '-B', binary=True), + mock.call('efibootmgr', '-b', '0004', '-B', binary=True), mock.call('efibootmgr', '-v', '-c', '-d', self.fake_dev, '-p', '1', '-w', '-L', 'ironic1', '-l', - '\\EFI\\BOOT\\BOOTX64.EFI'), + '\\EFI\\BOOT\\BOOTX64.EFI', binary=True), mock.call('umount', self.fake_dir + '/boot/efi', attempts=3, delay_on_retry=True), mock.call('sync')] @@ -450,8 +455,8 @@ Boot0004* ironic1 HD(1,GPT,55db8d03-c8f6-4a5b-9155-790dddc348fa,0x800,0x640 mock_execute.side_effect = iter([('', ''), ('', ''), ('', ''), ('', ''), - ('', ''), ('', ''), - ('', ''), ('', ''), + (EFI_RESULT, ''), (EFI_RESULT, ''), + (EFI_RESULT, ''), ('', ''), ('', '')]) expected = [mock.call('efibootmgr', '--version'), @@ -460,15 +465,15 @@ Boot0004* ironic1 HD(1,GPT,55db8d03-c8f6-4a5b-9155-790dddc348fa,0x800,0x640 mock.call('udevadm', 'settle'), mock.call('mount', self.fake_efi_system_part, self.fake_dir + '/boot/efi'), - mock.call('efibootmgr', '-v'), + mock.call('efibootmgr', '-v', binary=True), mock.call('efibootmgr', '-v', '-c', '-d', self.fake_dev, '-p', '1', '-w', '-L', 'ironic1', '-l', - '\\EFI\\BOOT\\BOOTX64.EFI'), + '\\EFI\\BOOT\\BOOTX64.EFI', binary=True), mock.call('efibootmgr', '-v', '-c', '-d', self.fake_dev, '-p', '1', '-w', '-L', 'ironic2', '-l', - '\\WINDOWS\\system32\\winload.efi'), + '\\WINDOWS\\system32\\winload.efi', binary=True), mock.call('umount', self.fake_dir + '/boot/efi', attempts=3, delay_on_retry=True), mock.call('sync')] diff --git a/ironic_python_agent/tests/unit/test_efi_utils.py b/ironic_python_agent/tests/unit/test_efi_utils.py index 64de61bc..09e89796 100644 --- a/ironic_python_agent/tests/unit/test_efi_utils.py +++ b/ironic_python_agent/tests/unit/test_efi_utils.py @@ -28,6 +28,9 @@ from ironic_python_agent.tests.unit.samples import hardware_samples from ironic_python_agent import utils +EFI_RESULT = ''.encode('utf-16') + + @mock.patch.object(os, 'walk', autospec=True) @mock.patch.object(os, 'access', autospec=False) class TestGetEfiBootloaders(base.IronicAgentTest): @@ -115,16 +118,16 @@ class TestRunEfiBootmgr(base.IronicAgentTest): mock_execute.assert_has_calls(expected) def test__run_efibootmgr(self, mock_execute): - mock_execute.return_value = ('', '') + mock_execute.return_value = (''.encode('utf-16'), '') result = efi_utils._run_efibootmgr(['EFI/BOOT/BOOTX64.EFI'], self.fake_dev, self.fake_efi_system_part, self.fake_dir) - expected = [mock.call('efibootmgr', '-v'), + expected = [mock.call('efibootmgr', '-v', binary=True), mock.call('efibootmgr', '-v', '-c', '-d', self.fake_dev, '-p', self.fake_efi_system_part, '-w', '-L', 'ironic1', '-l', - '\\EFI\\BOOT\\BOOTX64.EFI')] + '\\EFI\\BOOT\\BOOTX64.EFI', binary=True)] self.assertIsNone(result) mock_execute.assert_has_calls(expected) @@ -176,19 +179,18 @@ class TestManageUefi(base.IronicAgentTest): mock_is_md_device.return_value = False mock_efi_bl.return_value = ['EFI/BOOT/BOOTX64.EFI'] - - mock_execute.side_effect = iter([('', ''), ('', ''), - ('', ''), ('', ''), + mock_execute.side_effect = iter([('', ''), (EFI_RESULT, ''), + (EFI_RESULT, ''), ('', ''), ('', ''), ('', ''), ('', '')]) expected = [mock.call('mount', self.fake_efi_system_part, self.fake_dir + '/boot/efi'), - mock.call('efibootmgr', '-v'), + mock.call('efibootmgr', '-v', binary=True), mock.call('efibootmgr', '-v', '-c', '-d', self.fake_dev, '-p', '1', '-w', '-L', 'ironic1', '-l', - '\\EFI\\BOOT\\BOOTX64.EFI'), + '\\EFI\\BOOT\\BOOTX64.EFI', binary=True), mock.call('umount', self.fake_dir + '/boot/efi', attempts=3, delay_on_retry=True), mock.call('sync')] @@ -219,30 +221,34 @@ class TestManageUefi(base.IronicAgentTest): # at the start of the file, where as Red Hat *does* csv_file_data = u'shimx64.efi,Vendor String,,Grub2MadeUSDoThis\n' # This test also handles deleting a pre-existing matching vendor - # string in advance. + # string in advance. This string also includes a UTF16 character + # *on* purpose, to force proper decoding to be tested and garbage + # characters which can be found in OVMF test VM NVRAM records. dupe_entry = """ BootCurrent: 0001 Timeout: 0 seconds BootOrder: 0000,00001 -Boot0000* Vendor String HD(1,GPT,4f3c6294-bf9b-4208-9808-be45dfc34b5c)File(\EFI\Boot\BOOTX64.EFI) -Boot0001 Vendor String HD(2,GPT,4f3c6294-bf9b-4208-9808-be45dfc34b5c)File(\EFI\Boot\BOOTX64.EFI) -Boot0002: VENDMAGIC FvFile(9f3c6294-bf9b-4208-9808-be45dfc34b51) +Boot0000 UTF16ΓΏ HD(1,GPT,4f3c6294-bf9b-4208-9808-be45dfc34b5c)File(\EFI\Boot\BOOTX64.EFI) +Boot0001* Vendor String HD(1,GPT,4f3c6294-bf9b-4208-9808-be45dfc34b5c)File(\EFI\Boot\BOOTX64.EFI) +Boot0002 Vendor String HD(2,GPT,4f3c6294-bf9b-4208-9808-be45dfc34b5c)File(\EFI\Boot\BOOTX64.EFI) +Boot0003: VENDMAGIC FvFile(9f3c6294-bf9b-4208-9808-be45dfc34b51)N.....YM....R,Y. """ # noqa This is a giant literal string for testing. - - mock_execute.side_effect = iter([('', ''), (dupe_entry, ''), + dupe_entry = dupe_entry.encode('utf-16') + mock_execute.side_effect = iter([('', ''), + (dupe_entry, ''), ('', ''), ('', ''), ('', ''), ('', ''), ('', '')]) expected = [mock.call('mount', self.fake_efi_system_part, self.fake_dir + '/boot/efi'), - mock.call('efibootmgr', '-v'), - mock.call('efibootmgr', '-b', '0000', '-B'), - mock.call('efibootmgr', '-b', '0001', '-B'), + mock.call('efibootmgr', '-v', binary=True), + mock.call('efibootmgr', '-b', '0001', '-B', binary=True), + mock.call('efibootmgr', '-b', '0002', '-B', binary=True), mock.call('efibootmgr', '-v', '-c', '-d', self.fake_dev, '-p', '1', '-w', '-L', 'Vendor String', '-l', - '\\EFI\\vendor\\shimx64.efi'), + '\\EFI\\vendor\\shimx64.efi', binary=True), mock.call('umount', self.fake_dir + '/boot/efi', attempts=3, delay_on_retry=True), mock.call('sync')] @@ -266,19 +272,18 @@ Boot0002: VENDMAGIC FvFile(9f3c6294-bf9b-4208-9808-be45dfc34b51) mock_is_md_device.return_value = False mock_efi_bl.return_value = ['EFI/BOOT/BOOTX64.EFI'] - - mock_execute.side_effect = iter([('', ''), ('', ''), - ('', ''), ('', ''), + mock_execute.side_effect = iter([('', ''), (EFI_RESULT, ''), + (EFI_RESULT, ''), ('', ''), ('', ''), ('', ''), ('', '')]) expected = [mock.call('mount', '/dev/fakenvme0p1', self.fake_dir + '/boot/efi'), - mock.call('efibootmgr', '-v'), + mock.call('efibootmgr', '-v', binary=True), mock.call('efibootmgr', '-v', '-c', '-d', '/dev/fakenvme0', '-p', '1', '-w', '-L', 'ironic1', '-l', - '\\EFI\\BOOT\\BOOTX64.EFI'), + '\\EFI\\BOOT\\BOOTX64.EFI', binary=True), mock.call('umount', self.fake_dir + '/boot/efi', attempts=3, delay_on_retry=True), mock.call('sync')] @@ -301,19 +306,18 @@ Boot0002: VENDMAGIC FvFile(9f3c6294-bf9b-4208-9808-be45dfc34b51) mock_is_md_device.return_value = False mock_efi_bl.return_value = ['EFI/BOOT/BOOTX64.EFI'] - - mock_execute.side_effect = iter([('', ''), ('', ''), - ('', ''), ('', ''), + mock_execute.side_effect = iter([('', ''), (EFI_RESULT, ''), + (EFI_RESULT, ''), ('', ''), ('', ''), ('', ''), ('', '')]) expected = [mock.call('mount', self.fake_efi_system_part, self.fake_dir + '/boot/efi'), - mock.call('efibootmgr', '-v'), + mock.call('efibootmgr', '-v', binary=True), mock.call('efibootmgr', '-v', '-c', '-d', self.fake_dev, '-p', '1', '-w', '-L', 'ironic1', '-l', - '\\EFI\\BOOT\\BOOTX64.EFI'), + '\\EFI\\BOOT\\BOOTX64.EFI', binary=True), mock.call('umount', self.fake_dir + '/boot/efi', attempts=3, delay_on_retry=True), mock.call('sync')] @@ -347,11 +351,14 @@ Boot0002: VENDMAGIC FvFile(9f3c6294-bf9b-4208-9808-be45dfc34b51) mock_get_component_devices.return_value = ['/dev/sda3', '/dev/sdb3'] mock_efi_bl.return_value = ['EFI/BOOT/BOOTX64.EFI'] - - mock_execute.side_effect = iter([('', ''), ('', ''), - ('', ''), ('', ''), - ('', ''), ('', ''), - ('', ''), ('', ''), + mock_execute.side_effect = iter([('', ''), + ('', ''), + ('', ''), + (EFI_RESULT, ''), + (EFI_RESULT, ''), + (EFI_RESULT, ''), + (EFI_RESULT, ''), + ('', ''), ('', '')]) expected = [mock.call('mount', self.fake_efi_system_part, @@ -360,14 +367,14 @@ Boot0002: VENDMAGIC FvFile(9f3c6294-bf9b-4208-9808-be45dfc34b51) attempts=3, delay_on_retry=True), mock.call('mount', self.fake_efi_system_part, self.fake_dir + '/boot/efi'), - mock.call('efibootmgr', '-v'), + mock.call('efibootmgr', '-v', binary=True), mock.call('efibootmgr', '-v', '-c', '-d', '/dev/sda3', '-p', '3', '-w', '-L', 'ironic1 (RAID, part0)', - '-l', '\\EFI\\BOOT\\BOOTX64.EFI'), - mock.call('efibootmgr', '-v'), + '-l', '\\EFI\\BOOT\\BOOTX64.EFI', binary=True), + mock.call('efibootmgr', '-v', binary=True), mock.call('efibootmgr', '-v', '-c', '-d', '/dev/sdb3', '-p', '3', '-w', '-L', 'ironic1 (RAID, part1)', - '-l', '\\EFI\\BOOT\\BOOTX64.EFI'), + '-l', '\\EFI\\BOOT\\BOOTX64.EFI', binary=True), mock.call('umount', self.fake_dir + '/boot/efi', attempts=3, delay_on_retry=True), mock.call('sync')] @@ -425,7 +432,7 @@ Boot0002: VENDMAGIC FvFile(9f3c6294-bf9b-4208-9808-be45dfc34b51) expected = [mock.call('mount', self.fake_efi_system_part, self.fake_dir + '/boot/efi'), - mock.call('efibootmgr', '-v'), + mock.call('efibootmgr', '-v', binary=True), mock.call('umount', self.fake_dir + '/boot/efi', attempts=3, delay_on_retry=True), mock.call('sync')] @@ -462,7 +469,7 @@ Boot0002: VENDMAGIC FvFile(9f3c6294-bf9b-4208-9808-be45dfc34b51) expected = [mock.call('mount', self.fake_efi_system_part, self.fake_dir + '/boot/efi'), - mock.call('efibootmgr', '-v'), + mock.call('efibootmgr', '-v', binary=True), mock.call('umount', self.fake_dir + '/boot/efi', attempts=3, delay_on_retry=True)] diff --git a/releasenotes/notes/fixes-efibootmgr-character-encoding-19e531ba694824c1.yaml b/releasenotes/notes/fixes-efibootmgr-character-encoding-19e531ba694824c1.yaml new file mode 100644 index 00000000..b4c3270c --- /dev/null +++ b/releasenotes/notes/fixes-efibootmgr-character-encoding-19e531ba694824c1.yaml @@ -0,0 +1,9 @@ +--- +fixes: + - | + Fixes UEFI NVRAM record handling with efibootmgr so we can accept and + handle UTF-16 encoded data which is to be expected in UEFI NVRAM as + the records are UTF-16 encoded. + - | + Fixes handling of UEFI NVRAM records to allow for unexpected characters + in the response, so it is non-fatal to Ironic. diff --git a/releasenotes/notes/lldp-raw-a09174cb930bca97.yaml b/releasenotes/notes/lldp-raw-a09174cb930bca97.yaml new file mode 100644 index 00000000..19d668bc --- /dev/null +++ b/releasenotes/notes/lldp-raw-a09174cb930bca97.yaml @@ -0,0 +1,12 @@ +--- +features: + - | + Adds a new inspection collector ``lldp`` that collects LLDP information + into the ``lldp_raw`` field. +deprecations: + - | + The LLDP information as part of the general inventory is deprecated. + Use the new ``lldp`` inspection collector to retrieve it. + - | + The ``ipa-collect-lldp`` kernel parameter and the corresponding option are + now deprecated. @@ -55,6 +55,7 @@ ironic_python_agent.inspector.collectors = pci-devices = ironic_python_agent.inspector:collect_pci_devices_info numa-topology = ironic_python_agent.numa_inspector:collect_numa_topology_info dmi-decode = ironic_python_agent.dmi_inspector:collect_dmidecode_info + lldp = ironic_python_agent.inspector:collect_lldp [pbr] autodoc_index_modules = True @@ -39,7 +39,7 @@ commands = stestr run {posargs} [testenv:pep8] deps = - hacking>=4.1.0,<5.0.0 # Apache-2.0 + hacking~=6.0.0 # Apache-2.0 bashate>=0.5.1 # Apache-2.0 flake8-import-order>=0.17.1 # LGPLv3 pycodestyle>=2.0.0,<3.0.0 # MIT |