diff options
20 files changed, 592 insertions, 418 deletions
diff --git a/ironic_python_agent/efi_utils.py b/ironic_python_agent/efi_utils.py index 54d1e62f..c4e9dcc7 100644 --- a/ironic_python_agent/efi_utils.py +++ b/ironic_python_agent/efi_utils.py @@ -20,9 +20,9 @@ from oslo_concurrency import processutils from oslo_log import log from ironic_python_agent import errors -from ironic_python_agent.extensions import image from ironic_python_agent import hardware from ironic_python_agent import partition_utils +from ironic_python_agent import raid_utils from ironic_python_agent import utils @@ -112,7 +112,7 @@ def manage_uefi(device, efi_system_part_uuid=None): efi_mounted = False holders = hardware.get_holder_disks(device) - efi_md_device = image.prepare_boot_partitions_for_softraid( + efi_md_device = raid_utils.prepare_boot_partitions_for_softraid( device, holders, efi_device_part, target_boot_mode='uefi' ) efi_devices = hardware.get_component_devices(efi_md_device) @@ -297,8 +297,14 @@ def _run_efibootmgr(valid_efi_bootloaders, device, efi_partition, 'File: %s', v_bl) # These files are always UTF-16 encoded, sometimes have a header. # Positive bonus is python silently drops the FEFF header. - with open(mount_point + '/' + v_bl, 'r', encoding='utf-16') as csv: - contents = str(csv.read()) + try: + with open(mount_point + '/' + v_bl, 'r', + encoding='utf-16') as csv: + contents = str(csv.read()) + except UnicodeError: + with open(mount_point + '/' + v_bl, 'r', + encoding='utf-16-le') as csv: + contents = str(csv.read()) csv_contents = contents.split(',', maxsplit=3) csv_filename = v_bl.split('/')[-1] v_efi_bl_path = v_bl.replace(csv_filename, str(csv_contents[0])) diff --git a/ironic_python_agent/extensions/image.py b/ironic_python_agent/extensions/image.py index 6f20c8a8..de880f84 100644 --- a/ironic_python_agent/extensions/image.py +++ b/ironic_python_agent/extensions/image.py @@ -18,8 +18,6 @@ import re import shutil import tempfile -from ironic_lib import disk_utils -from ironic_lib import utils as ilib_utils from oslo_concurrency import processutils from oslo_config import cfg from oslo_log import log @@ -104,123 +102,6 @@ def _is_bootloader_loaded(dev): return _find_bootable_device(stdout, dev) -# TODO(rg): handle PreP boot parts relocation as well -def prepare_boot_partitions_for_softraid(device, holders, efi_part, - target_boot_mode): - """Prepare boot partitions when relevant. - - Create either a RAIDed EFI partition or bios boot partitions for software - RAID, according to both target boot mode and disk holders partition table - types. - - :param device: the softraid device path - :param holders: the softraid drive members - :param efi_part: when relevant the efi partition coming from the image - deployed on softraid device, can be/is often None - :param target_boot_mode: target boot mode can be bios/uefi/None - or anything else for unspecified - - :returns: the path to the ESP md device when target boot mode is uefi, - nothing otherwise. - """ - # Actually any fat partition could be a candidate. Let's assume the - # partition also has the esp flag - if target_boot_mode == 'uefi': - if not efi_part: - - LOG.debug("No explicit EFI partition provided. Scanning for any " - "EFI partition located on software RAID device %s to " - "be relocated", - device) - - # NOTE: for whole disk images, no efi part uuid will be provided. - # Let's try to scan for esp on the root softraid device. If not - # found, it's fine in most cases to just create an empty esp and - # let grub handle the magic. - efi_part = disk_utils.find_efi_partition(device) - if efi_part: - efi_part = '{}p{}'.format(device, efi_part['number']) - - LOG.info("Creating EFI partitions on software RAID holder disks") - # We know that we kept this space when configuring raid,see - # hardware.GenericHardwareManager.create_configuration. - # We could also directly get the EFI partition size. - partsize_mib = raid_utils.ESP_SIZE_MIB - partlabel_prefix = 'uefi-holder-' - efi_partitions = [] - for number, holder in enumerate(holders): - # NOTE: see utils.get_partition_table_type_from_specs - # for uefi we know that we have setup a gpt partition table, - # sgdisk can be used to edit table, more user friendly - # for alignment and relative offsets - partlabel = '{}{}'.format(partlabel_prefix, number) - out, _u = utils.execute('sgdisk', '-F', holder) - start_sector = '{}s'.format(out.splitlines()[-1].strip()) - out, _u = utils.execute( - 'sgdisk', '-n', '0:{}:+{}MiB'.format(start_sector, - partsize_mib), - '-t', '0:ef00', '-c', '0:{}'.format(partlabel), holder) - - # Refresh part table - utils.execute("partprobe") - utils.execute("blkid") - - target_part, _u = utils.execute( - "blkid", "-l", "-t", "PARTLABEL={}".format(partlabel), holder) - - target_part = target_part.splitlines()[-1].split(':', 1)[0] - efi_partitions.append(target_part) - - LOG.debug("EFI partition %s created on holder disk %s", - target_part, holder) - - # RAID the ESPs, metadata=1.0 is mandatory to be able to boot - md_device = raid_utils.get_next_free_raid_device() - LOG.debug("Creating md device %(md_device)s for the ESPs " - "on %(efi_partitions)s", - {'md_device': md_device, 'efi_partitions': efi_partitions}) - utils.execute('mdadm', '--create', md_device, '--force', - '--run', '--metadata=1.0', '--level', '1', - '--name', 'esp', '--raid-devices', len(efi_partitions), - *efi_partitions) - - disk_utils.trigger_device_rescan(md_device) - - if efi_part: - # Blockdev copy the source ESP and erase it - LOG.debug("Relocating EFI %s to %s", efi_part, md_device) - utils.execute('cp', efi_part, md_device) - LOG.debug("Erasing EFI partition %s", efi_part) - utils.execute('wipefs', '-a', efi_part) - else: - fslabel = 'efi-part' - ilib_utils.mkfs(fs='vfat', path=md_device, label=fslabel) - - return md_device - - elif target_boot_mode == 'bios': - partlabel_prefix = 'bios-boot-part-' - for number, holder in enumerate(holders): - label = disk_utils.get_partition_table_type(holder) - if label == 'gpt': - LOG.debug("Creating bios boot partition on disk holder %s", - holder) - out, _u = utils.execute('sgdisk', '-F', holder) - start_sector = '{}s'.format(out.splitlines()[-1].strip()) - partlabel = '{}{}'.format(partlabel_prefix, number) - out, _u = utils.execute( - 'sgdisk', '-n', '0:{}:+2MiB'.format(start_sector), - '-t', '0:ef02', '-c', '0:{}'.format(partlabel), holder) - - # Q: MBR case, could we dd the boot code from the softraid - # (446 first bytes) if we detect a bootloader with - # _is_bootloader_loaded? - # A: This won't work. Because it includes the address on the - # disk, as in virtual disk, where to load the data from. - # Since there is a structural difference, this means it will - # fail. - - def _umount_all_partitions(path, path_variable, umount_warn_msg): """Umount all partitions we may have mounted""" umount_binds_success = True @@ -313,7 +194,7 @@ def _install_grub2(device, root_uuid, efi_system_part_uuid=None, efi_partition = efi_part if hardware.is_md_device(device): holders = hardware.get_holder_disks(device) - efi_partition = prepare_boot_partitions_for_softraid( + efi_partition = raid_utils.prepare_boot_partitions_for_softraid( device, holders, efi_part, target_boot_mode ) @@ -604,17 +485,58 @@ def _try_preserve_efi_assets(device, path, def _append_uefi_to_fstab(fs_path, efi_system_part_uuid): """Append the efi partition id to the filesystem table. - :param fs_path: - :param efi_system_part_uuid: + :param fs_path: The path to the filesystem. + :param efi_system_part_uuid: uuid to use to try and find the + partition. Warning: this may be + a partition uuid or a actual uuid. """ fstab_file = os.path.join(fs_path, 'etc/fstab') if not os.path.exists(fstab_file): return try: - fstab_string = ("UUID=%s\t/boot/efi\tvfat\tumask=0077\t" - "0\t1\n") % efi_system_part_uuid + # Collect all of the block devices so we appropriately match UUID + # or PARTUUID into an fstab entry. + block_devs = hardware.list_all_block_devices(block_type='part') + + # Default to uuid, but if we find a partuuid instead, that is okay, + # we just need to know later on. + fstab_label = None + for bdev in block_devs: + # Check UUID first + if bdev.uuid and efi_system_part_uuid in bdev.uuid: + LOG.debug('Identified block device %(dev)s UUID %(uuid)s ' + 'for UEFI boot. Proceeding with fstab update using ' + 'a UUID.', + {'dev': bdev.name, + 'uuid': efi_system_part_uuid}) + # What we have works, and is correct, we can break the loop + fstab_label = 'UUID' + break + # Fallback to PARTUUID, since we don't know if the provided + # UUID matches a PARTUUID, or UUID field, and the fstab entry + # needs to match it. + if bdev.partuuid and efi_system_part_uuid in bdev.partuuid: + LOG.debug('Identified block device %(dev)s partition UUID ' + '%(uuid)s for UEFI boot. Proceeding with fstab ' + 'update using a PARTUUID.', + {'dev': bdev.name, + 'uuid': efi_system_part_uuid}) + fstab_label = 'PARTUUID' + break + + if not fstab_label: + # Fallback to prior behavior, which should generally be correct. + LOG.warning('Falling back to fstab entry addition label of UUID. ' + 'We could not identify which UUID or PARTUUID ' + 'identifier label should be used, thus UUID will be ' + 'used.') + fstab_label = 'UUID' + + fstab_string = ("%s=%s\t/boot/efi\tvfat\tumask=0077\t" + "0\t1\n") % (fstab_label, efi_system_part_uuid) with open(fstab_file, "r+") as fstab: - if efi_system_part_uuid not in fstab.read(): + already_present_string = fstab_label + '=' + efi_system_part_uuid + if already_present_string not in fstab.read(): fstab.writelines(fstab_string) except (OSError, EnvironmentError, IOError) as exc: LOG.debug('Failed to add entry to /etc/fstab. Error %s', exc) diff --git a/ironic_python_agent/hardware.py b/ironic_python_agent/hardware.py index ceafe31e..62e39f1d 100644 --- a/ironic_python_agent/hardware.py +++ b/ironic_python_agent/hardware.py @@ -545,6 +545,7 @@ def list_all_block_devices(block_type='disk', 'block', 'vendor'), by_path=by_path_name, uuid=device['UUID'], + partuuid=device['PARTUUID'], **extra)) return devices @@ -612,7 +613,7 @@ class BlockDevice(encoding.SerializableComparable): def __init__(self, name, model, size, rotational, wwn=None, serial=None, vendor=None, wwn_with_extension=None, wwn_vendor_extension=None, hctl=None, by_path=None, - uuid=None): + uuid=None, partuuid=None): self.name = name self.model = model self.size = size @@ -625,6 +626,7 @@ class BlockDevice(encoding.SerializableComparable): self.wwn_vendor_extension = wwn_vendor_extension self.hctl = hctl self.by_path = by_path + self.partuuid = partuuid class NetworkInterface(encoding.SerializableComparable): diff --git a/ironic_python_agent/partition_utils.py b/ironic_python_agent/partition_utils.py index 18b4cfdb..42b90445 100644 --- a/ironic_python_agent/partition_utils.py +++ b/ironic_python_agent/partition_utils.py @@ -316,6 +316,9 @@ def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format, "formatted for node %(node)s", {'ephemeral': ephemeral_part, 'node': node_uuid}) + # Rescan device to get current status (e.g. reflect modification of mkfs) + disk_utils.trigger_device_rescan(dev) + uuids_to_return = { 'root uuid': root_part, 'efi system partition uuid': part_dict.get('efi system partition'), diff --git a/ironic_python_agent/raid_utils.py b/ironic_python_agent/raid_utils.py index d4c338a8..0a53eec5 100644 --- a/ironic_python_agent/raid_utils.py +++ b/ironic_python_agent/raid_utils.py @@ -13,6 +13,7 @@ import copy import re +from ironic_lib import disk_utils from ironic_lib import utils as il_utils from oslo_concurrency import processutils from oslo_log import log as logging @@ -262,3 +263,120 @@ def get_next_free_raid_device(): return name raise errors.SoftwareRAIDError("No free md (RAID) devices are left") + + +# TODO(rg): handle PreP boot parts relocation as well +def prepare_boot_partitions_for_softraid(device, holders, efi_part, + target_boot_mode): + """Prepare boot partitions when relevant. + + Create either a RAIDed EFI partition or bios boot partitions for software + RAID, according to both target boot mode and disk holders partition table + types. + + :param device: the softraid device path + :param holders: the softraid drive members + :param efi_part: when relevant the efi partition coming from the image + deployed on softraid device, can be/is often None + :param target_boot_mode: target boot mode can be bios/uefi/None + or anything else for unspecified + + :returns: the path to the ESP md device when target boot mode is uefi, + nothing otherwise. + """ + # Actually any fat partition could be a candidate. Let's assume the + # partition also has the esp flag + if target_boot_mode == 'uefi': + if not efi_part: + + LOG.debug("No explicit EFI partition provided. Scanning for any " + "EFI partition located on software RAID device %s to " + "be relocated", + device) + + # NOTE: for whole disk images, no efi part uuid will be provided. + # Let's try to scan for esp on the root softraid device. If not + # found, it's fine in most cases to just create an empty esp and + # let grub handle the magic. + efi_part = disk_utils.find_efi_partition(device) + if efi_part: + efi_part = '{}p{}'.format(device, efi_part['number']) + + LOG.info("Creating EFI partitions on software RAID holder disks") + # We know that we kept this space when configuring raid,see + # hardware.GenericHardwareManager.create_configuration. + # We could also directly get the EFI partition size. + partsize_mib = ESP_SIZE_MIB + partlabel_prefix = 'uefi-holder-' + efi_partitions = [] + for number, holder in enumerate(holders): + # NOTE: see utils.get_partition_table_type_from_specs + # for uefi we know that we have setup a gpt partition table, + # sgdisk can be used to edit table, more user friendly + # for alignment and relative offsets + partlabel = '{}{}'.format(partlabel_prefix, number) + out, _u = utils.execute('sgdisk', '-F', holder) + start_sector = '{}s'.format(out.splitlines()[-1].strip()) + out, _u = utils.execute( + 'sgdisk', '-n', '0:{}:+{}MiB'.format(start_sector, + partsize_mib), + '-t', '0:ef00', '-c', '0:{}'.format(partlabel), holder) + + # Refresh part table + utils.execute("partprobe") + utils.execute("blkid") + + target_part, _u = utils.execute( + "blkid", "-l", "-t", "PARTLABEL={}".format(partlabel), holder) + + target_part = target_part.splitlines()[-1].split(':', 1)[0] + efi_partitions.append(target_part) + + LOG.debug("EFI partition %s created on holder disk %s", + target_part, holder) + + # RAID the ESPs, metadata=1.0 is mandatory to be able to boot + md_device = get_next_free_raid_device() + LOG.debug("Creating md device %(md_device)s for the ESPs " + "on %(efi_partitions)s", + {'md_device': md_device, 'efi_partitions': efi_partitions}) + utils.execute('mdadm', '--create', md_device, '--force', + '--run', '--metadata=1.0', '--level', '1', + '--name', 'esp', '--raid-devices', len(efi_partitions), + *efi_partitions) + + disk_utils.trigger_device_rescan(md_device) + + if efi_part: + # Blockdev copy the source ESP and erase it + LOG.debug("Relocating EFI %s to %s", efi_part, md_device) + utils.execute('cp', efi_part, md_device) + LOG.debug("Erasing EFI partition %s", efi_part) + utils.execute('wipefs', '-a', efi_part) + else: + fslabel = 'efi-part' + il_utils.mkfs(fs='vfat', path=md_device, label=fslabel) + + return md_device + + elif target_boot_mode == 'bios': + partlabel_prefix = 'bios-boot-part-' + for number, holder in enumerate(holders): + label = disk_utils.get_partition_table_type(holder) + if label == 'gpt': + LOG.debug("Creating bios boot partition on disk holder %s", + holder) + out, _u = utils.execute('sgdisk', '-F', holder) + start_sector = '{}s'.format(out.splitlines()[-1].strip()) + partlabel = '{}{}'.format(partlabel_prefix, number) + out, _u = utils.execute( + 'sgdisk', '-n', '0:{}:+2MiB'.format(start_sector), + '-t', '0:ef02', '-c', '0:{}'.format(partlabel), holder) + + # Q: MBR case, could we dd the boot code from the softraid + # (446 first bytes) if we detect a bootloader with + # _is_bootloader_loaded? + # A: This won't work. Because it includes the address on the + # disk, as in virtual disk, where to load the data from. + # Since there is a structural difference, this means it will + # fail. diff --git a/ironic_python_agent/tests/unit/extensions/test_image.py b/ironic_python_agent/tests/unit/extensions/test_image.py index b2506b9c..00aaf706 100644 --- a/ironic_python_agent/tests/unit/extensions/test_image.py +++ b/ironic_python_agent/tests/unit/extensions/test_image.py @@ -234,7 +234,7 @@ class TestImageExtension(base.IronicAgentTest): ('', ''), ('', '')]) expected = [mock.call('efibootmgr', '--version'), - mock.call('partx', '-a', '/dev/fake', attempts=3, + mock.call('partx', '-av', '/dev/fake', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('mount', self.fake_efi_system_part, @@ -281,7 +281,7 @@ class TestImageExtension(base.IronicAgentTest): ('', ''), ('', '')]) expected = [mock.call('efibootmgr', '--version'), - mock.call('partx', '-a', '/dev/fake', attempts=3, + mock.call('partx', '-av', '/dev/fake', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('mount', self.fake_efi_system_part, @@ -337,7 +337,7 @@ Boot0002 VENDMAGIC FvFile(9f3c6294-bf9b-4208-9808-be45dfc34b51) ('', ''), ('', '')]) expected = [mock.call('efibootmgr', '--version'), - mock.call('partx', '-a', '/dev/fake', attempts=3, + mock.call('partx', '-av', '/dev/fake', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('mount', self.fake_efi_system_part, @@ -398,7 +398,7 @@ Boot0004* ironic1 HD(1,GPT,55db8d03-c8f6-4a5b-9155-790dddc348fa,0x800,0x640 ('', ''), ('', ''), ('', ''), ('', '')]) expected = [mock.call('efibootmgr', '--version'), - mock.call('partx', '-a', '/dev/fake', attempts=3, + mock.call('partx', '-av', '/dev/fake', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('mount', self.fake_efi_system_part, @@ -450,7 +450,7 @@ Boot0004* ironic1 HD(1,GPT,55db8d03-c8f6-4a5b-9155-790dddc348fa,0x800,0x640 ('', '')]) expected = [mock.call('efibootmgr', '--version'), - mock.call('partx', '-a', '/dev/fake', attempts=3, + mock.call('partx', '-av', '/dev/fake', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('mount', self.fake_efi_system_part, @@ -751,6 +751,18 @@ Boot0004* ironic1 HD(1,GPT,55db8d03-c8f6-4a5b-9155-790dddc348fa,0x800,0x640 mock_is_md_device.return_value = False mock_md_get_raid_devices.return_value = {} mock_exists.side_effect = iter([False, True, False, True, True]) + partuuid_device = ('KNAME="sda" MODEL="DRIVE 0" SIZE="10240000" ' + 'ROTA="1" TYPE="disk" UUID="987654-3210" ' + 'PARTUUID=""\n' + 'KNAME="sda0" MODEL="DRIVE 0" SIZE="102400" ' + 'ROTA="1" TYPE="part" ' + 'UUID="' + self.fake_efi_system_part_uuid + '" ' + 'PARTUUID="1234-2918"\n') + exec_side_effect = [('', '')] * 16 + exec_side_effect.append((partuuid_device, '')) + exec_side_effect.extend([('', '')] * 8) + mock_execute.side_effect = exec_side_effect + with mock.patch('builtins.open', mock.mock_open()) as mock_open: image._install_grub2( self.fake_dev, root_uuid=self.fake_root_uuid, @@ -805,6 +817,9 @@ Boot0004* ironic1 HD(1,GPT,55db8d03-c8f6-4a5b-9155-790dddc348fa,0x800,0x640 'GRUB_DISABLE_OS_PROBER': 'true', 'GRUB_SAVEDEFAULT': 'true'}, use_standard_locale=True), + mock.call('udevadm', 'settle'), + mock.call('lsblk', '-Pbia', + '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID'), mock.call('umount', self.fake_dir + '/boot/efi', attempts=3, delay_on_retry=True), mock.call(('chroot %s /bin/sh -c "umount -a -t vfat"' % @@ -849,8 +864,21 @@ Boot0004* ironic1 HD(1,GPT,55db8d03-c8f6-4a5b-9155-790dddc348fa,0x800,0x640 environ_mock.get.return_value = '/sbin' mock_is_md_device.return_value = False mock_md_get_raid_devices.return_value = {} + partuuid_device = ('KNAME="sda" MODEL="DRIVE 0" SIZE="10240000" ' + 'ROTA="1" TYPE="disk" UUID="987654-3210" ' + 'PARTUUID=""\n' + 'KNAME="sda0" MODEL="DRIVE 0" SIZE="102400" ' + 'ROTA="1" TYPE="part" UUID="987654-3210" ' + 'PARTUUID="' + self.fake_efi_system_part_uuid + + '"\n') + exec_side_effect = [('', '')] * 16 + exec_side_effect.append((partuuid_device, '')) + exec_side_effect.extend([('', '')] * 8) + mock_execute.side_effect = exec_side_effect + # Validates the complete opposite path *and* no-write behavior + # occurs if the entry already exists. fstab_data = ( - 'UUID=%s\tpath vfat option' % self.fake_efi_system_part_uuid) + 'PARTUUID=%s\tpath vfat option' % self.fake_efi_system_part_uuid) mock_exists.side_effect = [True, False, True, True, True, False, True, True] with mock.patch('builtins.open', @@ -916,6 +944,9 @@ Boot0004* ironic1 HD(1,GPT,55db8d03-c8f6-4a5b-9155-790dddc348fa,0x800,0x640 'GRUB_DISABLE_OS_PROBER': 'true', 'GRUB_SAVEDEFAULT': 'true'}, use_standard_locale=True), + mock.call('udevadm', 'settle'), + mock.call('lsblk', '-Pbia', + '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID'), mock.call('umount', self.fake_dir + '/boot/efi', attempts=3, delay_on_retry=True), mock.call(('chroot %s /bin/sh -c "umount -a -t vfat"' % @@ -1026,6 +1057,7 @@ Boot0004* ironic1 HD(1,GPT,55db8d03-c8f6-4a5b-9155-790dddc348fa,0x800,0x640 @mock.patch.object(os, 'listdir', lambda *_: ['file1', 'file2']) @mock.patch.object(image, '_is_bootloader_loaded', lambda *_: False) + @mock.patch.object(image, '_append_uefi_to_fstab', autospec=True) @mock.patch.object(shutil, 'copy2', autospec=True) @mock.patch.object(os.path, 'isfile', autospec=True) @mock.patch.object(image, '_efi_boot_setup', autospec=True) @@ -1042,7 +1074,8 @@ Boot0004* ironic1 HD(1,GPT,55db8d03-c8f6-4a5b-9155-790dddc348fa,0x800,0x640 mock_is_md_device, mock_exists, mock_copytree, mock_efi_setup, mock_isfile, mock_copy2, - mock_execute, mock_dispatch): + mock_fstab_append, mock_execute, + mock_dispatch): mock_exists.return_value = True mock_efi_setup.return_value = True mock_get_part_uuid.side_effect = [self.fake_root_part, @@ -1106,6 +1139,9 @@ Boot0004* ironic1 HD(1,GPT,55db8d03-c8f6-4a5b-9155-790dddc348fa,0x800,0x640 uuid=self.fake_root_uuid) mock_get_part_uuid.assert_any_call(self.fake_dev, uuid=self.fake_efi_system_part_uuid) + mock_fstab_append.assert_called_once_with( + self.fake_dir, + self.fake_efi_system_part_uuid) self.assertFalse(mock_dispatch.called) @mock.patch.object(os.path, 'ismount', lambda *_: False) @@ -1344,6 +1380,7 @@ Boot0004* ironic1 HD(1,GPT,55db8d03-c8f6-4a5b-9155-790dddc348fa,0x800,0x640 self.fake_efi_system_part_uuid) @mock.patch.object(image, '_is_bootloader_loaded', lambda *_: False) + @mock.patch.object(image, '_append_uefi_to_fstab', autospec=True) @mock.patch.object(os, 'listdir', autospec=True) @mock.patch.object(shutil, 'copy2', autospec=True) @mock.patch.object(os.path, 'isfile', autospec=True) @@ -1361,8 +1398,8 @@ Boot0004* ironic1 HD(1,GPT,55db8d03-c8f6-4a5b-9155-790dddc348fa,0x800,0x640 mock_is_md_device, mock_exists, mock_copytree, mock_efi_setup, mock_isfile, mock_copy2, - mock_oslistdir, mock_execute, - mock_dispatch): + mock_oslistdir, mock_append_to_fstab, + mock_execute, mock_dispatch): mock_exists.return_value = True mock_efi_setup.return_value = True mock_get_part_uuid.side_effect = [self.fake_root_part, @@ -1431,6 +1468,8 @@ Boot0004* ironic1 HD(1,GPT,55db8d03-c8f6-4a5b-9155-790dddc348fa,0x800,0x640 uuid=self.fake_efi_system_part_uuid) self.assertFalse(mock_dispatch.called) self.assertEqual(2, mock_oslistdir.call_count) + mock_append_to_fstab.assert_called_with(self.fake_dir, + self.fake_efi_system_part_uuid) @mock.patch.object(os.path, 'ismount', lambda *_: False) @mock.patch.object(image, '_is_bootloader_loaded', lambda *_: False) @@ -1654,212 +1693,6 @@ Boot0004* ironic1 HD(1,GPT,55db8d03-c8f6-4a5b-9155-790dddc348fa,0x800,0x640 uuid=self.fake_root_uuid) self.assertFalse(mock_dispatch.called) - @mock.patch.object(disk_utils, 'trigger_device_rescan', autospec=True) - @mock.patch.object(raid_utils, 'get_next_free_raid_device', autospec=True, - return_value='/dev/md42') - @mock.patch.object(disk_utils, 'find_efi_partition', autospec=True) - def test_prepare_boot_partitions_for_softraid_uefi_gpt( - self, mock_efi_part, mock_free_raid_device, mock_rescan, - mock_execute, mock_dispatch): - mock_efi_part.return_value = {'number': '12'} - mock_execute.side_effect = [ - ('451', None), # sgdisk -F - (None, None), # sgdisk create part - (None, None), # partprobe - (None, None), # blkid - ('/dev/sda12: dsfkgsdjfg', None), # blkid - ('452', None), # sgdisk -F - (None, None), # sgdisk create part - (None, None), # partprobe - (None, None), # blkid - ('/dev/sdb14: whatever', None), # blkid - (None, None), # mdadm - (None, None), # cp - (None, None), # wipefs - ] - - efi_part = image.prepare_boot_partitions_for_softraid( - '/dev/md0', ['/dev/sda', '/dev/sdb'], None, - target_boot_mode='uefi') - - mock_efi_part.assert_called_once_with('/dev/md0') - expected = [ - mock.call('sgdisk', '-F', '/dev/sda'), - mock.call('sgdisk', '-n', '0:451s:+550MiB', '-t', '0:ef00', '-c', - '0:uefi-holder-0', '/dev/sda'), - mock.call('partprobe'), - mock.call('blkid'), - mock.call('blkid', '-l', '-t', 'PARTLABEL=uefi-holder-0', - '/dev/sda'), - mock.call('sgdisk', '-F', '/dev/sdb'), - mock.call('sgdisk', '-n', '0:452s:+550MiB', '-t', '0:ef00', '-c', - '0:uefi-holder-1', '/dev/sdb'), - mock.call('partprobe'), - mock.call('blkid'), - mock.call('blkid', '-l', '-t', 'PARTLABEL=uefi-holder-1', - '/dev/sdb'), - mock.call('mdadm', '--create', '/dev/md42', '--force', '--run', - '--metadata=1.0', '--level', '1', '--name', 'esp', - '--raid-devices', 2, '/dev/sda12', '/dev/sdb14'), - mock.call('cp', '/dev/md0p12', '/dev/md42'), - mock.call('wipefs', '-a', '/dev/md0p12') - ] - mock_execute.assert_has_calls(expected, any_order=False) - self.assertEqual(efi_part, '/dev/md42') - mock_rescan.assert_called_once_with('/dev/md42') - - @mock.patch.object(disk_utils, 'trigger_device_rescan', autospec=True) - @mock.patch.object(raid_utils, 'get_next_free_raid_device', autospec=True, - return_value='/dev/md42') - @mock.patch.object(disk_utils, 'find_efi_partition', autospec=True) - @mock.patch.object(ilib_utils, 'mkfs', autospec=True) - def test_prepare_boot_partitions_for_softraid_uefi_gpt_esp_not_found( - self, mock_mkfs, mock_efi_part, mock_free_raid_device, - mock_rescan, mock_execute, mock_dispatch): - mock_efi_part.return_value = None - mock_execute.side_effect = [ - ('451', None), # sgdisk -F - (None, None), # sgdisk create part - (None, None), # partprobe - (None, None), # blkid - ('/dev/sda12: dsfkgsdjfg', None), # blkid - ('452', None), # sgdisk -F - (None, None), # sgdisk create part - (None, None), # partprobe - (None, None), # blkid - ('/dev/sdb14: whatever', None), # blkid - (None, None), # mdadm - ] - - efi_part = image.prepare_boot_partitions_for_softraid( - '/dev/md0', ['/dev/sda', '/dev/sdb'], None, - target_boot_mode='uefi') - - mock_efi_part.assert_called_once_with('/dev/md0') - expected = [ - mock.call('sgdisk', '-F', '/dev/sda'), - mock.call('sgdisk', '-n', '0:451s:+550MiB', '-t', '0:ef00', '-c', - '0:uefi-holder-0', '/dev/sda'), - mock.call('partprobe'), - mock.call('blkid'), - mock.call('blkid', '-l', '-t', 'PARTLABEL=uefi-holder-0', - '/dev/sda'), - mock.call('sgdisk', '-F', '/dev/sdb'), - mock.call('sgdisk', '-n', '0:452s:+550MiB', '-t', '0:ef00', '-c', - '0:uefi-holder-1', '/dev/sdb'), - mock.call('partprobe'), - mock.call('blkid'), - mock.call('blkid', '-l', '-t', 'PARTLABEL=uefi-holder-1', - '/dev/sdb'), - ] - mock_execute.assert_has_calls(expected, any_order=False) - mock_mkfs.assert_has_calls([ - mock.call(path='/dev/md42', label='efi-part', fs='vfat'), - ], any_order=False) - self.assertEqual(efi_part, '/dev/md42') - mock_rescan.assert_called_once_with('/dev/md42') - - @mock.patch.object(disk_utils, 'trigger_device_rescan', autospec=True) - @mock.patch.object(raid_utils, 'get_next_free_raid_device', autospec=True, - return_value='/dev/md42') - def test_prepare_boot_partitions_for_softraid_uefi_gpt_efi_provided( - self, mock_free_raid_device, mock_rescan, - mock_execute, mock_dispatch): - mock_execute.side_effect = [ - ('451', None), # sgdisk -F - (None, None), # sgdisk create part - (None, None), # partprobe - (None, None), # blkid - ('/dev/sda12: dsfkgsdjfg', None), # blkid - ('452', None), # sgdisk -F - (None, None), # sgdisk create part - (None, None), # partprobe - (None, None), # blkid - ('/dev/sdb14: whatever', None), # blkid - (None, None), # mdadm create - (None, None), # cp - (None, None), # wipefs - ] - - efi_part = image.prepare_boot_partitions_for_softraid( - '/dev/md0', ['/dev/sda', '/dev/sdb'], '/dev/md0p15', - target_boot_mode='uefi') - - expected = [ - mock.call('sgdisk', '-F', '/dev/sda'), - mock.call('sgdisk', '-n', '0:451s:+550MiB', '-t', '0:ef00', '-c', - '0:uefi-holder-0', '/dev/sda'), - mock.call('partprobe'), - mock.call('blkid'), - mock.call('blkid', '-l', '-t', 'PARTLABEL=uefi-holder-0', - '/dev/sda'), - mock.call('sgdisk', '-F', '/dev/sdb'), - mock.call('sgdisk', '-n', '0:452s:+550MiB', '-t', '0:ef00', '-c', - '0:uefi-holder-1', '/dev/sdb'), - mock.call('partprobe'), - mock.call('blkid'), - mock.call('blkid', '-l', '-t', 'PARTLABEL=uefi-holder-1', - '/dev/sdb'), - mock.call('mdadm', '--create', '/dev/md42', '--force', '--run', - '--metadata=1.0', '--level', '1', '--name', 'esp', - '--raid-devices', 2, '/dev/sda12', '/dev/sdb14'), - mock.call('cp', '/dev/md0p15', '/dev/md42'), - mock.call('wipefs', '-a', '/dev/md0p15') - ] - mock_execute.assert_has_calls(expected, any_order=False) - self.assertEqual(efi_part, '/dev/md42') - - @mock.patch.object(disk_utils, 'get_partition_table_type', autospec=True, - return_value='msdos') - def test_prepare_boot_partitions_for_softraid_bios_msdos( - self, mock_label_scan, mock_execute, mock_dispatch): - - efi_part = image.prepare_boot_partitions_for_softraid( - '/dev/md0', ['/dev/sda', '/dev/sdb'], 'notusedanyway', - target_boot_mode='bios') - - expected = [ - mock.call('/dev/sda'), - mock.call('/dev/sdb'), - ] - mock_label_scan.assert_has_calls(expected, any_order=False) - self.assertIsNone(efi_part) - - @mock.patch.object(disk_utils, 'get_partition_table_type', autospec=True, - return_value='gpt') - def test_prepare_boot_partitions_for_softraid_bios_gpt( - self, mock_label_scan, mock_execute, mock_dispatch): - - mock_execute.side_effect = [ - ('whatever\n314', None), # sgdisk -F - (None, None), # bios boot grub - ('warning message\n914', None), # sgdisk -F - (None, None), # bios boot grub - ] - - efi_part = image.prepare_boot_partitions_for_softraid( - '/dev/md0', ['/dev/sda', '/dev/sdb'], 'notusedanyway', - target_boot_mode='bios') - - expected_scan = [ - mock.call('/dev/sda'), - mock.call('/dev/sdb'), - ] - - mock_label_scan.assert_has_calls(expected_scan, any_order=False) - - expected_exec = [ - mock.call('sgdisk', '-F', '/dev/sda'), - mock.call('sgdisk', '-n', '0:314s:+2MiB', '-t', '0:ef02', '-c', - '0:bios-boot-part-0', '/dev/sda'), - mock.call('sgdisk', '-F', '/dev/sdb'), - mock.call('sgdisk', '-n', '0:914s:+2MiB', '-t', '0:ef02', '-c', - '0:bios-boot-part-1', '/dev/sdb'), - ] - - mock_execute.assert_has_calls(expected_exec, any_order=False) - self.assertIsNone(efi_part) - @mock.patch.object(image, '_is_bootloader_loaded', lambda *_: True) @mock.patch.object(hardware, 'is_md_device', autospec=True) @mock.patch.object(hardware, 'md_restart', autospec=True) @@ -1869,7 +1702,7 @@ Boot0004* ironic1 HD(1,GPT,55db8d03-c8f6-4a5b-9155-790dddc348fa,0x800,0x640 @mock.patch.object(os, 'environ', autospec=True) @mock.patch.object(os, 'makedirs', autospec=True) @mock.patch.object(partition_utils, 'get_partition', autospec=True) - @mock.patch.object(image, 'prepare_boot_partitions_for_softraid', + @mock.patch.object(raid_utils, 'prepare_boot_partitions_for_softraid', autospec=True, return_value='/dev/md/esp') @mock.patch.object(image, '_has_dracut', @@ -1908,7 +1741,7 @@ Boot0004* ironic1 HD(1,GPT,55db8d03-c8f6-4a5b-9155-790dddc348fa,0x800,0x640 efi_system_part_uuid=self.fake_efi_system_part_uuid, target_boot_mode='uefi') - expected = [mock.call('partx', '-a', '/dev/fake', attempts=3, + expected = [mock.call('partx', '-av', '/dev/fake', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('mount', '/dev/fake2', self.fake_dir), @@ -1987,7 +1820,7 @@ Boot0004* ironic1 HD(1,GPT,55db8d03-c8f6-4a5b-9155-790dddc348fa,0x800,0x640 @mock.patch.object(os, 'environ', autospec=True) @mock.patch.object(os, 'makedirs', autospec=True) @mock.patch.object(partition_utils, 'get_partition', autospec=True) - @mock.patch.object(image, 'prepare_boot_partitions_for_softraid', + @mock.patch.object(raid_utils, 'prepare_boot_partitions_for_softraid', autospec=True, return_value=[]) @mock.patch.object(image, '_has_dracut', diff --git a/ironic_python_agent/tests/unit/samples/hardware_samples.py b/ironic_python_agent/tests/unit/samples/hardware_samples.py index 3ab54153..8cc9b1ea 100644 --- a/ironic_python_agent/tests/unit/samples/hardware_samples.py +++ b/ironic_python_agent/tests/unit/samples/hardware_samples.py @@ -160,6 +160,13 @@ RAID_BLK_DEVICE_TEMPLATE = ( 'PARTUUID=""' ) +PARTUUID_DEVICE_TEMPLATE = ( + 'KNAME="sda" MODEL="DRIVE 0" SIZE="1765517033472" ' + 'ROTA="1" TYPE="disk" UUID="" PARTUUID=""\n' + 'KNAME="sda1" MODEL="DRIVE 0" SIZE="107373133824" ' + 'ROTA="1" TYPE="part" UUID="987654-3210" PARTUUID="1234-5678"\n' +) + SHRED_OUTPUT_0_ITERATIONS_ZERO_FALSE = () SHRED_OUTPUT_1_ITERATION_ZERO_TRUE = ( diff --git a/ironic_python_agent/tests/unit/test_efi_utils.py b/ironic_python_agent/tests/unit/test_efi_utils.py index 775d0fce..137ec8d4 100644 --- a/ironic_python_agent/tests/unit/test_efi_utils.py +++ b/ironic_python_agent/tests/unit/test_efi_utils.py @@ -19,9 +19,9 @@ from ironic_lib import disk_utils from ironic_python_agent import efi_utils from ironic_python_agent import errors -from ironic_python_agent.extensions import image from ironic_python_agent import hardware from ironic_python_agent import partition_utils +from ironic_python_agent import raid_utils from ironic_python_agent.tests.unit import base from ironic_python_agent import utils @@ -321,7 +321,7 @@ Boot0002: VENDMAGIC FvFile(9f3c6294-bf9b-4208-9808-be45dfc34b51) @mock.patch.object(os.path, 'exists', lambda *_: False) @mock.patch.object(hardware, 'get_component_devices', autospec=True) - @mock.patch.object(image, + @mock.patch.object(raid_utils, 'prepare_boot_partitions_for_softraid', autospec=True) @mock.patch.object(hardware, 'get_holder_disks', autospec=True) diff --git a/ironic_python_agent/tests/unit/test_hardware.py b/ironic_python_agent/tests/unit/test_hardware.py index 7b19931b..60820727 100644 --- a/ironic_python_agent/tests/unit/test_hardware.py +++ b/ironic_python_agent/tests/unit/test_hardware.py @@ -66,6 +66,13 @@ RAID_BLK_DEVICE_TEMPLATE_DEVICES = [ vendor="FooTastic", uuid=""), ] +BLK_DEVICE_TEMPLATE_PARTUUID_DEVICE = [ + hardware.BlockDevice(name='/dev/sda1', model='DRIVE 0', + size=107373133824, rotational=True, + vendor="FooTastic", uuid="987654-3210", + partuuid="1234-5678"), +] + class FakeHardwareManager(hardware.GenericHardwareManager): def __init__(self, hardware_support): @@ -2563,22 +2570,22 @@ class TestGenericHardwareManager(base.IronicAgentTest): mock.call('sgdisk', '-F', '/dev/sdb'), mock.call('parted', '/dev/sda', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '42s', '10GiB'), - mock.call('partx', '-a', '/dev/sda', attempts=3, + mock.call('partx', '-av', '/dev/sda', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/sdb', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '42s', '10GiB'), - mock.call('partx', '-a', '/dev/sdb', attempts=3, + mock.call('partx', '-av', '/dev/sdb', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/sda', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '10GiB', '-1'), - mock.call('partx', '-a', '/dev/sda', attempts=3, + mock.call('partx', '-av', '/dev/sda', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/sdb', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '10GiB', '-1'), - mock.call('partx', '-a', '/dev/sdb', attempts=3, + mock.call('partx', '-av', '/dev/sdb', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('mdadm', '--create', '/dev/md0', '--force', '--run', @@ -2658,32 +2665,32 @@ class TestGenericHardwareManager(base.IronicAgentTest): mock.call('sgdisk', '-F', '/dev/sdc'), mock.call('parted', '/dev/sda', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '42s', '10GiB'), - mock.call('partx', '-a', '/dev/sda', attempts=3, + mock.call('partx', '-av', '/dev/sda', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/sdb', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '42s', '10GiB'), - mock.call('partx', '-a', '/dev/sdb', attempts=3, + mock.call('partx', '-av', '/dev/sdb', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/sdc', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '42s', '10GiB'), - mock.call('partx', '-a', '/dev/sdc', attempts=3, + mock.call('partx', '-av', '/dev/sdc', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/sda', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '10GiB', '-1'), - mock.call('partx', '-a', '/dev/sda', attempts=3, + mock.call('partx', '-av', '/dev/sda', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/sdb', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '10GiB', '-1'), - mock.call('partx', '-a', '/dev/sdb', attempts=3, + mock.call('partx', '-av', '/dev/sdb', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/sdc', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '10GiB', '-1'), - mock.call('partx', '-a', '/dev/sdc', attempts=3, + mock.call('partx', '-av', '/dev/sdc', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('mdadm', '--create', '/dev/md0', '--force', '--run', @@ -2764,42 +2771,42 @@ class TestGenericHardwareManager(base.IronicAgentTest): mock.call('sgdisk', '-F', '/dev/sdd'), mock.call('parted', '/dev/sda', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '42s', '10GiB'), - mock.call('partx', '-a', '/dev/sda', attempts=3, + mock.call('partx', '-av', '/dev/sda', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/sdb', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '42s', '10GiB'), - mock.call('partx', '-a', '/dev/sdb', attempts=3, + mock.call('partx', '-av', '/dev/sdb', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/sdc', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '42s', '10GiB'), - mock.call('partx', '-a', '/dev/sdc', attempts=3, + mock.call('partx', '-av', '/dev/sdc', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/sdd', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '42s', '10GiB'), - mock.call('partx', '-a', '/dev/sdd', attempts=3, + mock.call('partx', '-av', '/dev/sdd', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/sda', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '10GiB', '-1'), - mock.call('partx', '-a', '/dev/sda', attempts=3, + mock.call('partx', '-av', '/dev/sda', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/sdb', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '10GiB', '-1'), - mock.call('partx', '-a', '/dev/sdb', attempts=3, + mock.call('partx', '-av', '/dev/sdb', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/sdc', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '10GiB', '-1'), - mock.call('partx', '-a', '/dev/sdc', attempts=3, + mock.call('partx', '-av', '/dev/sdc', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/sdd', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '10GiB', '-1'), - mock.call('partx', '-a', '/dev/sdd', attempts=3, + mock.call('partx', '-av', '/dev/sdd', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('mdadm', '--create', '/dev/md0', '--force', '--run', @@ -2865,22 +2872,22 @@ class TestGenericHardwareManager(base.IronicAgentTest): mock.call('parted', '/dev/sdb', '-s', '--', 'mklabel', 'gpt'), mock.call('parted', '/dev/sda', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '551MiB', '10GiB'), - mock.call('partx', '-a', '/dev/sda', attempts=3, + mock.call('partx', '-av', '/dev/sda', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/sdb', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '551MiB', '10GiB'), - mock.call('partx', '-a', '/dev/sdb', attempts=3, + mock.call('partx', '-av', '/dev/sdb', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/sda', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '10GiB', '-1'), - mock.call('partx', '-a', '/dev/sda', attempts=3, + mock.call('partx', '-av', '/dev/sda', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/sdb', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '10GiB', '-1'), - mock.call('partx', '-a', '/dev/sdb', attempts=3, + mock.call('partx', '-av', '/dev/sdb', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('mdadm', '--create', '/dev/md0', '--force', '--run', @@ -2952,22 +2959,22 @@ class TestGenericHardwareManager(base.IronicAgentTest): mock.call('parted', '/dev/sdb', '-s', '--', 'mklabel', 'gpt'), mock.call('parted', '/dev/sda', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '8MiB', '10GiB'), - mock.call('partx', '-a', '/dev/sda', attempts=3, + mock.call('partx', '-av', '/dev/sda', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/sdb', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '8MiB', '10GiB'), - mock.call('partx', '-a', '/dev/sdb', attempts=3, + mock.call('partx', '-av', '/dev/sdb', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/sda', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '10GiB', '-1'), - mock.call('partx', '-a', '/dev/sda', attempts=3, + mock.call('partx', '-av', '/dev/sda', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/sdb', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '10GiB', '-1'), - mock.call('partx', '-a', '/dev/sdb', attempts=3, + mock.call('partx', '-av', '/dev/sdb', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('mdadm', '--create', '/dev/md0', '--force', '--run', @@ -3034,22 +3041,22 @@ class TestGenericHardwareManager(base.IronicAgentTest): mock.call('sgdisk', '-F', '/dev/sdb'), mock.call('parted', '/dev/sda', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '42s', '10GiB'), - mock.call('partx', '-a', '/dev/sda', attempts=3, + mock.call('partx', '-av', '/dev/sda', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/sdb', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '42s', '10GiB'), - mock.call('partx', '-a', '/dev/sdb', attempts=3, + mock.call('partx', '-av', '/dev/sdb', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/sda', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '10GiB', '30GiB'), - mock.call('partx', '-a', '/dev/sda', attempts=3, + mock.call('partx', '-av', '/dev/sda', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/sdb', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '10GiB', '30GiB'), - mock.call('partx', '-a', '/dev/sdb', attempts=3, + mock.call('partx', '-av', '/dev/sdb', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('mdadm', '--create', '/dev/md0', '--force', '--run', @@ -3118,22 +3125,22 @@ class TestGenericHardwareManager(base.IronicAgentTest): mock.call('sgdisk', '-F', '/dev/sdb'), mock.call('parted', '/dev/sda', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '42s', '20GiB'), - mock.call('partx', '-a', '/dev/sda', attempts=3, + mock.call('partx', '-av', '/dev/sda', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/sdb', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '42s', '20GiB'), - mock.call('partx', '-a', '/dev/sdb', attempts=3, + mock.call('partx', '-av', '/dev/sdb', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/sda', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '20GiB', '-1'), - mock.call('partx', '-a', '/dev/sda', attempts=3, + mock.call('partx', '-av', '/dev/sda', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/sdb', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '20GiB', '-1'), - mock.call('partx', '-a', '/dev/sdb', attempts=3, + mock.call('partx', '-av', '/dev/sdb', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('mdadm', '--create', '/dev/md0', '--force', '--run', @@ -3211,22 +3218,22 @@ class TestGenericHardwareManager(base.IronicAgentTest): mock.call('sgdisk', '-F', '/dev/sdb'), mock.call('parted', '/dev/sda', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '42s', '10GiB'), - mock.call('partx', '-a', '/dev/sda', attempts=3, + mock.call('partx', '-av', '/dev/sda', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/sdb', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '42s', '10GiB'), - mock.call('partx', '-a', '/dev/sdb', attempts=3, + mock.call('partx', '-av', '/dev/sdb', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/sda', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '10GiB', '-1'), - mock.call('partx', '-a', '/dev/sda', attempts=3, + mock.call('partx', '-av', '/dev/sda', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/sdb', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '10GiB', '-1'), - mock.call('partx', '-a', '/dev/sdb', attempts=3, + mock.call('partx', '-av', '/dev/sdb', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('mdadm', '--create', '/dev/md0', '--force', '--run', @@ -3558,22 +3565,22 @@ class TestGenericHardwareManager(base.IronicAgentTest): 'gpt'), mock.call('parted', '/dev/nvme0n1', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '551MiB', '10GiB'), - mock.call('partx', '-a', '/dev/nvme0n1', attempts=3, + mock.call('partx', '-av', '/dev/nvme0n1', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/nvme1n1', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '551MiB', '10GiB'), - mock.call('partx', '-a', '/dev/nvme1n1', attempts=3, + mock.call('partx', '-av', '/dev/nvme1n1', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/nvme0n1', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '10GiB', '-1'), - mock.call('partx', '-a', '/dev/nvme0n1', attempts=3, + mock.call('partx', '-av', '/dev/nvme0n1', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('parted', '/dev/nvme1n1', '-s', '-a', 'optimal', '--', 'mkpart', 'primary', '10GiB', '-1'), - mock.call('partx', '-a', '/dev/nvme1n1', attempts=3, + mock.call('partx', '-av', '/dev/nvme1n1', attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('mdadm', '--create', '/dev/md0', '--force', '--run', @@ -4291,6 +4298,25 @@ class TestModuleFunctions(base.IronicAgentTest): self.assertEqual(RAID_BLK_DEVICE_TEMPLATE_DEVICES, result) mocked_udev.assert_called_once_with() + @mock.patch.object(os, 'readlink', autospec=True) + @mock.patch.object(hardware, '_get_device_info', + lambda x, y, z: 'FooTastic') + @mock.patch.object(hardware, '_udev_settle', autospec=True) + @mock.patch.object(hardware.pyudev.Devices, "from_device_file", + autospec=False) + def test_list_all_block_devices_partuuid_success( + self, mocked_fromdevfile, + mocked_udev, mocked_readlink, + mocked_execute): + mocked_readlink.return_value = '../../sda' + mocked_fromdevfile.return_value = {} + mocked_execute.return_value = (hws.PARTUUID_DEVICE_TEMPLATE, '') + result = hardware.list_all_block_devices(block_type='part') + mocked_execute.assert_called_once_with( + 'lsblk', '-Pbia', '-oKNAME,MODEL,SIZE,ROTA,TYPE,UUID,PARTUUID') + self.assertEqual(BLK_DEVICE_TEMPLATE_PARTUUID_DEVICE, result) + mocked_udev.assert_called_once_with() + @mock.patch.object(hardware, '_get_device_info', lambda x, y: "FooTastic") @mock.patch.object(hardware, '_udev_settle', autospec=True) diff --git a/ironic_python_agent/tests/unit/test_partition_utils.py b/ironic_python_agent/tests/unit/test_partition_utils.py index f6f56695..b4017c2b 100644 --- a/ironic_python_agent/tests/unit/test_partition_utils.py +++ b/ironic_python_agent/tests/unit/test_partition_utils.py @@ -408,6 +408,8 @@ class WorkOnDiskTestCase(base.IronicAgentTest): cpu_arch="") mock_unlink.assert_called_once_with('fake-path') + @mock.patch.object(disk_utils, 'trigger_device_rescan', + lambda d: None) @mock.patch.object(utils, 'mkfs', lambda fs, path, label=None: None) @mock.patch.object(disk_utils, 'block_uuid', lambda p: 'uuid') @mock.patch.object(disk_utils, 'populate_image', autospec=True) @@ -442,6 +444,8 @@ class WorkOnDiskTestCase(base.IronicAgentTest): self.assertEqual('uuid', res['root uuid']) self.assertFalse(mock_populate.called) + @mock.patch.object(disk_utils, 'trigger_device_rescan', + lambda d: None) @mock.patch.object(utils, 'mkfs', lambda fs, path, label=None: None) @mock.patch.object(disk_utils, 'block_uuid', lambda p: 'uuid') @mock.patch.object(disk_utils, 'populate_image', lambda image_path, @@ -475,11 +479,12 @@ class WorkOnDiskTestCase(base.IronicAgentTest): disk_label='gpt', cpu_arch="") + @mock.patch.object(disk_utils, 'trigger_device_rescan', autospec=True) @mock.patch.object(disk_utils, 'block_uuid', autospec=True) @mock.patch.object(disk_utils, 'populate_image', autospec=True) @mock.patch.object(utils, 'mkfs', autospec=True) def test_uefi_localboot(self, mock_mkfs, mock_populate_image, - mock_block_uuid): + mock_block_uuid, mock_trigger_device_rescan): """Test that we create a fat filesystem with UEFI localboot.""" root_part = '/dev/fake-part1' efi_part = '/dev/fake-part2' @@ -510,12 +515,14 @@ class WorkOnDiskTestCase(base.IronicAgentTest): root_part, conv_flags=None) mock_block_uuid.assert_any_call(root_part) mock_block_uuid.assert_any_call(efi_part) + mock_trigger_device_rescan.assert_called_once_with(self.dev) + @mock.patch.object(disk_utils, 'trigger_device_rescan', autospec=True) @mock.patch.object(disk_utils, 'block_uuid', autospec=True) @mock.patch.object(disk_utils, 'populate_image', autospec=True) @mock.patch.object(utils, 'mkfs', autospec=True) def test_preserve_ephemeral(self, mock_mkfs, mock_populate_image, - mock_block_uuid): + mock_block_uuid, mock_trigger_device_rescan): """Test that ephemeral partition doesn't get overwritten.""" ephemeral_part = '/dev/fake-part1' root_part = '/dev/fake-part2' @@ -543,11 +550,12 @@ class WorkOnDiskTestCase(base.IronicAgentTest): cpu_arch="") self.assertFalse(mock_mkfs.called) + @mock.patch.object(disk_utils, 'trigger_device_rescan', autospec=True) @mock.patch.object(disk_utils, 'block_uuid', autospec=True) @mock.patch.object(disk_utils, 'populate_image', autospec=True) @mock.patch.object(utils, 'mkfs', autospec=True) def test_ppc64le_prep_part(self, mock_mkfs, mock_populate_image, - mock_block_uuid): + mock_block_uuid, mock_trigger_device_rescan): """Test that PReP partition uuid is returned.""" prep_part = '/dev/fake-part1' root_part = '/dev/fake-part2' @@ -573,11 +581,12 @@ class WorkOnDiskTestCase(base.IronicAgentTest): cpu_arch="ppc64le") self.assertFalse(mock_mkfs.called) + @mock.patch.object(disk_utils, 'trigger_device_rescan', autospec=True) @mock.patch.object(disk_utils, 'block_uuid', autospec=True) @mock.patch.object(disk_utils, 'populate_image', autospec=True) @mock.patch.object(utils, 'mkfs', autospec=True) def test_convert_to_sparse(self, mock_mkfs, mock_populate_image, - mock_block_uuid): + mock_block_uuid, mock_trigger_device_rescan): ephemeral_part = '/dev/fake-part1' swap_part = '/dev/fake-part2' root_part = '/dev/fake-part3' @@ -1199,7 +1208,7 @@ class TestGetPartition(base.IronicAgentTest): root_part = partition_utils.get_partition( self.fake_dev, self.fake_root_uuid) self.assertEqual('/dev/test2', root_part) - expected = [mock.call('partx', '-a', self.fake_dev, attempts=3, + expected = [mock.call('partx', '-av', self.fake_dev, attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('lsblk', '-PbioKNAME,UUID,PARTUUID,TYPE,LABEL', @@ -1219,7 +1228,7 @@ class TestGetPartition(base.IronicAgentTest): self.assertRaises(errors.DeviceNotFound, partition_utils.get_partition, self.fake_dev, self.fake_root_uuid) - expected = [mock.call('partx', '-a', self.fake_dev, attempts=3, + expected = [mock.call('partx', '-av', self.fake_dev, attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('lsblk', '-PbioKNAME,UUID,PARTUUID,TYPE,LABEL', @@ -1240,7 +1249,7 @@ class TestGetPartition(base.IronicAgentTest): result = partition_utils.get_partition( self.fake_dev, self.fake_root_uuid) self.assertEqual('/dev/loop0', result) - expected = [mock.call('partx', '-a', self.fake_dev, attempts=3, + expected = [mock.call('partx', '-av', self.fake_dev, attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('lsblk', '-PbioKNAME,UUID,PARTUUID,TYPE,LABEL', @@ -1257,7 +1266,7 @@ class TestGetPartition(base.IronicAgentTest): partition_utils.get_partition, self.fake_dev, self.fake_root_uuid) - expected = [mock.call('partx', '-a', self.fake_dev, attempts=3, + expected = [mock.call('partx', '-av', self.fake_dev, attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('lsblk', '-PbioKNAME,UUID,PARTUUID,TYPE,LABEL', @@ -1275,7 +1284,7 @@ class TestGetPartition(base.IronicAgentTest): root_part = partition_utils.get_partition( self.fake_dev, self.fake_root_uuid) self.assertEqual('/dev/test2', root_part) - expected = [mock.call('partx', '-a', self.fake_dev, attempts=3, + expected = [mock.call('partx', '-av', self.fake_dev, attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('lsblk', '-PbioKNAME,UUID,PARTUUID,TYPE,LABEL', @@ -1292,7 +1301,7 @@ class TestGetPartition(base.IronicAgentTest): root_part = partition_utils.get_partition( self.fake_dev, self.fake_root_uuid) self.assertEqual('/dev/test2', root_part) - expected = [mock.call('partx', '-a', self.fake_dev, attempts=3, + expected = [mock.call('partx', '-av', self.fake_dev, attempts=3, delay_on_retry=True), mock.call('udevadm', 'settle'), mock.call('lsblk', '-PbioKNAME,UUID,PARTUUID,TYPE,LABEL', diff --git a/ironic_python_agent/tests/unit/test_raid_utils.py b/ironic_python_agent/tests/unit/test_raid_utils.py index a20bd13e..5b8577e2 100644 --- a/ironic_python_agent/tests/unit/test_raid_utils.py +++ b/ironic_python_agent/tests/unit/test_raid_utils.py @@ -12,6 +12,8 @@ from unittest import mock +from ironic_lib import disk_utils +from ironic_lib import utils as ilib_utils from oslo_concurrency import processutils from ironic_python_agent import errors @@ -115,6 +117,222 @@ class TestRaidUtils(base.IronicAgentTest): raid_utils.create_raid_device, 0, logical_disk) + @mock.patch.object(disk_utils, 'trigger_device_rescan', autospec=True) + @mock.patch.object(raid_utils, 'get_next_free_raid_device', autospec=True, + return_value='/dev/md42') + @mock.patch.object(hardware, 'dispatch_to_managers', autospec=True) + @mock.patch.object(ilib_utils, 'execute', autospec=True) + @mock.patch.object(disk_utils, 'find_efi_partition', autospec=True) + def test_prepare_boot_partitions_for_softraid_uefi_gpt( + self, mock_efi_part, mock_execute, mock_dispatch, + mock_free_raid_device, mock_rescan): + mock_efi_part.return_value = {'number': '12'} + mock_execute.side_effect = [ + ('451', None), # sgdisk -F + (None, None), # sgdisk create part + (None, None), # partprobe + (None, None), # blkid + ('/dev/sda12: dsfkgsdjfg', None), # blkid + ('452', None), # sgdisk -F + (None, None), # sgdisk create part + (None, None), # partprobe + (None, None), # blkid + ('/dev/sdb14: whatever', None), # blkid + (None, None), # mdadm + (None, None), # cp + (None, None), # wipefs + ] + + efi_part = raid_utils.prepare_boot_partitions_for_softraid( + '/dev/md0', ['/dev/sda', '/dev/sdb'], None, + target_boot_mode='uefi') + + mock_efi_part.assert_called_once_with('/dev/md0') + expected = [ + mock.call('sgdisk', '-F', '/dev/sda'), + mock.call('sgdisk', '-n', '0:451s:+550MiB', '-t', '0:ef00', '-c', + '0:uefi-holder-0', '/dev/sda'), + mock.call('partprobe'), + mock.call('blkid'), + mock.call('blkid', '-l', '-t', 'PARTLABEL=uefi-holder-0', + '/dev/sda'), + mock.call('sgdisk', '-F', '/dev/sdb'), + mock.call('sgdisk', '-n', '0:452s:+550MiB', '-t', '0:ef00', '-c', + '0:uefi-holder-1', '/dev/sdb'), + mock.call('partprobe'), + mock.call('blkid'), + mock.call('blkid', '-l', '-t', 'PARTLABEL=uefi-holder-1', + '/dev/sdb'), + mock.call('mdadm', '--create', '/dev/md42', '--force', '--run', + '--metadata=1.0', '--level', '1', '--name', 'esp', + '--raid-devices', 2, '/dev/sda12', '/dev/sdb14'), + mock.call('cp', '/dev/md0p12', '/dev/md42'), + mock.call('wipefs', '-a', '/dev/md0p12') + ] + mock_execute.assert_has_calls(expected, any_order=False) + self.assertEqual(efi_part, '/dev/md42') + mock_rescan.assert_called_once_with('/dev/md42') + + @mock.patch.object(disk_utils, 'trigger_device_rescan', autospec=True) + @mock.patch.object(raid_utils, 'get_next_free_raid_device', autospec=True, + return_value='/dev/md42') + @mock.patch.object(hardware, 'dispatch_to_managers', autospec=True) + @mock.patch.object(ilib_utils, 'execute', autospec=True) + @mock.patch.object(disk_utils, 'find_efi_partition', autospec=True) + @mock.patch.object(ilib_utils, 'mkfs', autospec=True) + def test_prepare_boot_partitions_for_softraid_uefi_gpt_esp_not_found( + self, mock_mkfs, mock_efi_part, mock_execute, mock_dispatch, + mock_free_raid_device, mock_rescan): + mock_efi_part.return_value = None + mock_execute.side_effect = [ + ('451', None), # sgdisk -F + (None, None), # sgdisk create part + (None, None), # partprobe + (None, None), # blkid + ('/dev/sda12: dsfkgsdjfg', None), # blkid + ('452', None), # sgdisk -F + (None, None), # sgdisk create part + (None, None), # partprobe + (None, None), # blkid + ('/dev/sdb14: whatever', None), # blkid + (None, None), # mdadm + ] + + efi_part = raid_utils.prepare_boot_partitions_for_softraid( + '/dev/md0', ['/dev/sda', '/dev/sdb'], None, + target_boot_mode='uefi') + + mock_efi_part.assert_called_once_with('/dev/md0') + expected = [ + mock.call('sgdisk', '-F', '/dev/sda'), + mock.call('sgdisk', '-n', '0:451s:+550MiB', '-t', '0:ef00', '-c', + '0:uefi-holder-0', '/dev/sda'), + mock.call('partprobe'), + mock.call('blkid'), + mock.call('blkid', '-l', '-t', 'PARTLABEL=uefi-holder-0', + '/dev/sda'), + mock.call('sgdisk', '-F', '/dev/sdb'), + mock.call('sgdisk', '-n', '0:452s:+550MiB', '-t', '0:ef00', '-c', + '0:uefi-holder-1', '/dev/sdb'), + mock.call('partprobe'), + mock.call('blkid'), + mock.call('blkid', '-l', '-t', 'PARTLABEL=uefi-holder-1', + '/dev/sdb'), + ] + mock_execute.assert_has_calls(expected, any_order=False) + mock_mkfs.assert_has_calls([ + mock.call(path='/dev/md42', label='efi-part', fs='vfat'), + ], any_order=False) + self.assertEqual(efi_part, '/dev/md42') + mock_rescan.assert_called_once_with('/dev/md42') + + @mock.patch.object(disk_utils, 'trigger_device_rescan', autospec=True) + @mock.patch.object(raid_utils, 'get_next_free_raid_device', autospec=True, + return_value='/dev/md42') + @mock.patch.object(hardware, 'dispatch_to_managers', autospec=True) + @mock.patch.object(ilib_utils, 'execute', autospec=True) + def test_prepare_boot_partitions_for_softraid_uefi_gpt_efi_provided( + self, mock_execute, mock_dispatch, mock_free_raid_device, + mock_rescan): + mock_execute.side_effect = [ + ('451', None), # sgdisk -F + (None, None), # sgdisk create part + (None, None), # partprobe + (None, None), # blkid + ('/dev/sda12: dsfkgsdjfg', None), # blkid + ('452', None), # sgdisk -F + (None, None), # sgdisk create part + (None, None), # partprobe + (None, None), # blkid + ('/dev/sdb14: whatever', None), # blkid + (None, None), # mdadm create + (None, None), # cp + (None, None), # wipefs + ] + + efi_part = raid_utils.prepare_boot_partitions_for_softraid( + '/dev/md0', ['/dev/sda', '/dev/sdb'], '/dev/md0p15', + target_boot_mode='uefi') + + expected = [ + mock.call('sgdisk', '-F', '/dev/sda'), + mock.call('sgdisk', '-n', '0:451s:+550MiB', '-t', '0:ef00', '-c', + '0:uefi-holder-0', '/dev/sda'), + mock.call('partprobe'), + mock.call('blkid'), + mock.call('blkid', '-l', '-t', 'PARTLABEL=uefi-holder-0', + '/dev/sda'), + mock.call('sgdisk', '-F', '/dev/sdb'), + mock.call('sgdisk', '-n', '0:452s:+550MiB', '-t', '0:ef00', '-c', + '0:uefi-holder-1', '/dev/sdb'), + mock.call('partprobe'), + mock.call('blkid'), + mock.call('blkid', '-l', '-t', 'PARTLABEL=uefi-holder-1', + '/dev/sdb'), + mock.call('mdadm', '--create', '/dev/md42', '--force', '--run', + '--metadata=1.0', '--level', '1', '--name', 'esp', + '--raid-devices', 2, '/dev/sda12', '/dev/sdb14'), + mock.call('cp', '/dev/md0p15', '/dev/md42'), + mock.call('wipefs', '-a', '/dev/md0p15') + ] + mock_execute.assert_has_calls(expected, any_order=False) + self.assertEqual(efi_part, '/dev/md42') + + @mock.patch.object(hardware, 'dispatch_to_managers', autospec=True) + @mock.patch.object(ilib_utils, 'execute', autospec=True) + @mock.patch.object(disk_utils, 'get_partition_table_type', autospec=True, + return_value='msdos') + def test_prepare_boot_partitions_for_softraid_bios_msdos( + self, mock_label_scan, mock_execute, mock_dispatch): + + efi_part = raid_utils.prepare_boot_partitions_for_softraid( + '/dev/md0', ['/dev/sda', '/dev/sdb'], 'notusedanyway', + target_boot_mode='bios') + + expected = [ + mock.call('/dev/sda'), + mock.call('/dev/sdb'), + ] + mock_label_scan.assert_has_calls(expected, any_order=False) + self.assertIsNone(efi_part) + + @mock.patch.object(hardware, 'dispatch_to_managers', autospec=True) + @mock.patch.object(ilib_utils, 'execute', autospec=True) + @mock.patch.object(disk_utils, 'get_partition_table_type', autospec=True, + return_value='gpt') + def test_prepare_boot_partitions_for_softraid_bios_gpt( + self, mock_label_scan, mock_execute, mock_dispatch): + + mock_execute.side_effect = [ + ('whatever\n314', None), # sgdisk -F + (None, None), # bios boot grub + ('warning message\n914', None), # sgdisk -F + (None, None), # bios boot grub + ] + + efi_part = raid_utils.prepare_boot_partitions_for_softraid( + '/dev/md0', ['/dev/sda', '/dev/sdb'], 'notusedanyway', + target_boot_mode='bios') + + expected_scan = [ + mock.call('/dev/sda'), + mock.call('/dev/sdb'), + ] + + mock_label_scan.assert_has_calls(expected_scan, any_order=False) + + expected_exec = [ + mock.call('sgdisk', '-F', '/dev/sda'), + mock.call('sgdisk', '-n', '0:314s:+2MiB', '-t', '0:ef02', '-c', + '0:bios-boot-part-0', '/dev/sda'), + mock.call('sgdisk', '-F', '/dev/sdb'), + mock.call('sgdisk', '-n', '0:914s:+2MiB', '-t', '0:ef02', '-c', + '0:bios-boot-part-1', '/dev/sdb'), + ] + + mock_execute.assert_has_calls(expected_exec, any_order=False) + self.assertIsNone(efi_part) + @mock.patch.object(hardware, 'dispatch_to_managers', autospec=True) class TestGetNextFreeRaidDevice(base.IronicAgentTest): diff --git a/ironic_python_agent/tests/unit/test_utils.py b/ironic_python_agent/tests/unit/test_utils.py index 96d31688..1bb7c1fe 100644 --- a/ironic_python_agent/tests/unit/test_utils.py +++ b/ironic_python_agent/tests/unit/test_utils.py @@ -431,7 +431,8 @@ class TestUtils(ironic_agent_base.IronicAgentTest): file_list=[], io_dict={'journal': mock.ANY, 'ip_addr': mock.ANY, 'ps': mock.ANY, 'df': mock.ANY, 'iptables': mock.ANY, 'lshw': mock.ANY, - 'lsblk': mock.ANY, 'mdstat': mock.ANY}) + 'lsblk': mock.ANY, 'mdstat': mock.ANY, + 'mount': mock.ANY, 'parted': mock.ANY}) @mock.patch.object(utils, 'gzip_and_b64encode', autospec=True) @mock.patch.object(utils, 'is_journalctl_present', autospec=True) @@ -458,7 +459,8 @@ class TestUtils(ironic_agent_base.IronicAgentTest): file_list=[tmp.name], io_dict={'journal': mock.ANY, 'ip_addr': mock.ANY, 'ps': mock.ANY, 'df': mock.ANY, 'iptables': mock.ANY, 'lshw': mock.ANY, - 'lsblk': mock.ANY, 'mdstat': mock.ANY}) + 'lsblk': mock.ANY, 'mdstat': mock.ANY, + 'mount': mock.ANY, 'parted': mock.ANY}) @mock.patch.object(utils, 'gzip_and_b64encode', autospec=True) @mock.patch.object(utils, 'is_journalctl_present', autospec=True) @@ -480,7 +482,8 @@ class TestUtils(ironic_agent_base.IronicAgentTest): file_list=['/var/log'], io_dict={'iptables': mock.ANY, 'ip_addr': mock.ANY, 'ps': mock.ANY, 'dmesg': mock.ANY, 'df': mock.ANY, 'lshw': mock.ANY, - 'lsblk': mock.ANY, 'mdstat': mock.ANY}) + 'lsblk': mock.ANY, 'mdstat': mock.ANY, + 'mount': mock.ANY, 'parted': mock.ANY}) @mock.patch.object(utils, 'gzip_and_b64encode', autospec=True) @mock.patch.object(utils, 'is_journalctl_present', autospec=True) @@ -506,7 +509,8 @@ class TestUtils(ironic_agent_base.IronicAgentTest): file_list=['/var/log', tmp.name], io_dict={'iptables': mock.ANY, 'ip_addr': mock.ANY, 'ps': mock.ANY, 'dmesg': mock.ANY, 'df': mock.ANY, 'lshw': mock.ANY, - 'lsblk': mock.ANY, 'mdstat': mock.ANY}) + 'lsblk': mock.ANY, 'mdstat': mock.ANY, + 'mount': mock.ANY, 'parted': mock.ANY}) def test_get_ssl_client_options(self): # defaults diff --git a/ironic_python_agent/utils.py b/ironic_python_agent/utils.py index 71e29d16..ff0f8926 100644 --- a/ironic_python_agent/utils.py +++ b/ironic_python_agent/utils.py @@ -69,6 +69,8 @@ COLLECT_LOGS_COMMANDS = { 'lshw': ['lshw', '-quiet', '-json'], 'lsblk': ['lsblk', '--all', '-o%s' % ','.join(LSBLK_COLUMNS)], 'mdstat': ['cat', '/proc/mdstat'], + 'mount': ['mount'], + 'parted': ['parted', '-l'], } @@ -888,7 +890,7 @@ def rescan_device(device): kernel partition records. """ try: - execute('partx', '-a', device, attempts=3, delay_on_retry=True) + execute('partx', '-av', device, attempts=3, delay_on_retry=True) except processutils.ProcessExecutionError: LOG.warning("Couldn't re-read the partition table " "on device %s", device) diff --git a/releasenotes/notes/add_burnin_dynamic_network_pairing-33e398255050eb98.yaml b/releasenotes/notes/add_burnin_dynamic_network_pairing-33e398255050eb98.yaml index 1802f7ec..f4aaa203 100644 --- a/releasenotes/notes/add_burnin_dynamic_network_pairing-33e398255050eb98.yaml +++ b/releasenotes/notes/add_burnin_dynamic_network_pairing-33e398255050eb98.yaml @@ -3,15 +3,16 @@ features: - | For network burn-in, nodes can now be paired dynamically via a distributed coordination backend (as an alternative to a static - configuration). This allows burn-in to proceed on a 'first come - first served' basis with the nodes available, rather than a node + configuration). This allows burn-in to proceed on a "first come + first served" basis with the nodes available, rather than a node being blocked since the static partner is currently delayed. + In order to configure this dynamic pairing, the nodes will need - at least 'agent_burnin_fio_network_pairing_backend_url' in their - driver-info (the URL for the coordination backend). In order to + at least ``agent_burnin_fio_network_pairing_backend_url`` in their + ``driver_info`` (the URL for the coordination backend). In order to separate different hardware types, which may be using different networks and shall be burnt-in separately, the nodes can in - addition define 'agent_burnin_fio_network_pairing_group_name' to + addition define ``agent_burnin_fio_network_pairing_group_name`` to have pairing only happening between nodes in the same group. An - additional 'agent_burnin_fio_network_pairing_timeout' allows to + additional parameter ``agent_burnin_fio_network_pairing_timeout`` allows to limit the time given to the nodes to wait for a partner. diff --git a/releasenotes/notes/detect-endianness-f53a6c4571aba3fe.yaml b/releasenotes/notes/detect-endianness-f53a6c4571aba3fe.yaml new file mode 100644 index 00000000..82d90886 --- /dev/null +++ b/releasenotes/notes/detect-endianness-f53a6c4571aba3fe.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + In case the CSV file used for the bootloader hint does not have BOM + we fail reading its content as utf-16 codec is too generic. + Fail over to utf-16-le as Little Endian is mostly used. diff --git a/releasenotes/notes/handle-partuuid-for-fstab-e0aadea20a056982.yaml b/releasenotes/notes/handle-partuuid-for-fstab-e0aadea20a056982.yaml new file mode 100644 index 00000000..f9b6bbc5 --- /dev/null +++ b/releasenotes/notes/handle-partuuid-for-fstab-e0aadea20a056982.yaml @@ -0,0 +1,10 @@ +--- +fixes: + - | + Fixes handling of a Partition UUID being returned instead of a + Partition's UUID when the OS may not return the Partition's UUID in time. + These two fields are typically referred to as PARTUUID and UUID, + respectively. Often these sorts of issues arise under heavy IO load. + We now scan, and identify which "UUID" we identified, and update + a Linux fstab entry appropriately. For more information, please see + `story #2009881 <https://storyboard.openstack.org/#!/story/2009881>`_. diff --git a/releasenotes/notes/move_swraid_to_efibootmgr-d87c1bfde1661fb5.yaml b/releasenotes/notes/move_swraid_to_efibootmgr-d87c1bfde1661fb5.yaml index 3fa29284..39f202ff 100644 --- a/releasenotes/notes/move_swraid_to_efibootmgr-d87c1bfde1661fb5.yaml +++ b/releasenotes/notes/move_swraid_to_efibootmgr-d87c1bfde1661fb5.yaml @@ -1,7 +1,7 @@ --- fixes: - | - Use efibootmgr instead of grub2-install for software RAID. - This fixes an issue with images which include newer versions - of grub2-install as they refuse bootloader installations in - UEFI boot mode due to the lack of secure boot support. + Uses ``efibootmgr`` instead of ``grub2-install`` for software RAID. This + fixes an issue with images which include newer versions of + ``grub2-install``, such as CentOS Stream 8, as they refuse bootloader + installations in UEFI boot mode due to the lack of secure boot support. diff --git a/releasenotes/notes/rescan-device-after-mkfs-3f9d52a2e3b6fff3.yaml b/releasenotes/notes/rescan-device-after-mkfs-3f9d52a2e3b6fff3.yaml new file mode 100644 index 00000000..3a8d9e08 --- /dev/null +++ b/releasenotes/notes/rescan-device-after-mkfs-3f9d52a2e3b6fff3.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Adds device rescan operation after partitioning the root device to ensure + that updated UUIDs are reflected correctly diff --git a/zuul.d/ironic-python-agent-jobs.yaml b/zuul.d/ironic-python-agent-jobs.yaml index 34c4647a..4d3de185 100644 --- a/zuul.d/ironic-python-agent-jobs.yaml +++ b/zuul.d/ironic-python-agent-jobs.yaml @@ -38,6 +38,7 @@ s-object: True s-proxy: True devstack_localrc: + IRONIC_BOOT_MODE: bios IRONIC_DEFAULT_BOOT_OPTION: netboot IRONIC_DEFAULT_DEPLOY_INTERFACE: direct SWIFT_ENABLE_TEMPURLS: True @@ -131,7 +132,6 @@ description: Test ironic standalone with IPA from source vars: devstack_localrc: - IRONIC_DEFAULT_BOOT_OPTION: netboot IRONIC_BUILD_DEPLOY_RAMDISK: True # NOTE(dtantsur): the ansible deploy doesn't depend on IPA code, # excluding it from the enabled list to save gate time. diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml index fa87feaf..c7448f4a 100644 --- a/zuul.d/project.yaml +++ b/zuul.d/project.yaml @@ -22,6 +22,8 @@ voting: false - ironic-python-agent-check-image-dib-centos8: voting: false + - ironic-python-agent-check-image-dib-centos9: + voting: false # Non-voting jobs - ipa-tempest-ironic-inspector-src: voting: false |