diff --git a/ironic_python_agent/efi_utils.py b/ironic_python_agent/efi_utils.py index 54d1e62fe..ea7a94742 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) diff --git a/ironic_python_agent/extensions/image.py b/ironic_python_agent/extensions/image.py index 6f20c8a86..892aa2886 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 ) diff --git a/ironic_python_agent/raid_utils.py b/ironic_python_agent/raid_utils.py index d4c338a8f..0a53eec5e 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 b2506b9c1..6c45c4559 100644 --- a/ironic_python_agent/tests/unit/extensions/test_image.py +++ b/ironic_python_agent/tests/unit/extensions/test_image.py @@ -1654,212 +1654,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 +1663,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', @@ -1987,7 +1781,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/test_efi_utils.py b/ironic_python_agent/tests/unit/test_efi_utils.py index 775d0fce3..137ec8d4b 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_raid_utils.py b/ironic_python_agent/tests/unit/test_raid_utils.py index a20bd13ec..5b8577e27 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):