diff --git a/ironic_python_agent/extensions/image.py b/ironic_python_agent/extensions/image.py index e0dfd20a8..80c76f26c 100644 --- a/ironic_python_agent/extensions/image.py +++ b/ironic_python_agent/extensions/image.py @@ -68,8 +68,8 @@ def _get_partition(device, uuid): # UUID and use the "normal" discovery instead? if hardware.is_md_device(device): md_partition = device + 'p1' - if (not os.path.exists(md_partition) or - not stat.S_ISBLK(os.stat(md_partition).st_mode)): + if (not os.path.exists(md_partition) + or not stat.S_ISBLK(os.stat(md_partition).st_mode)): error_msg = ("Could not find partition %(part)s on md " "device %(dev)s" % {'part': md_partition, 'dev': device}) @@ -330,6 +330,7 @@ def _manage_uefi(device, efi_system_part_uuid=None): LOG.error(error_msg) raise errors.CommandExecutionError(error_msg) finally: + LOG.debug('Executing _manage_uefi clean-up.') umount_warn_msg = "Unable to umount %(local_path)s. Error: %(error)s" try: @@ -382,9 +383,11 @@ def _install_grub2(device, root_uuid, efi_system_part_uuid=None, """Install GRUB2 bootloader on a given device.""" LOG.debug("Installing GRUB2 bootloader on device %s", device) - efi_partition = None + efi_partitions = None efi_partition_mount_point = None efi_mounted = False + efi_preserved = False + path_variable = _get_path_variable() # NOTE(TheJulia): Seems we need to get this before ever possibly # restart the device in the case of multi-device RAID as pyudev @@ -408,18 +411,14 @@ def _install_grub2(device, root_uuid, efi_system_part_uuid=None, "as it is already marked bootable.", device) return try: - # Add /bin to PATH variable as grub requires it to find efibootmgr - # when running in uefi boot mode. - # Add /usr/sbin to PATH variable to ensure it is there as we do - # not use full path to grub binary anymore. - path_variable = os.environ.get('PATH', '') - path_variable = '%s:/bin:/usr/sbin:/sbin' % path_variable - # Mount the partition and binds path = tempfile.mkdtemp() if efi_system_part_uuid: - efi_partition = _get_partition(device, uuid=efi_system_part_uuid) + efi_part = _get_partition(device, uuid=efi_system_part_uuid) + efi_partitions = [efi_part] + + if efi_partitions: efi_partition_mount_point = os.path.join(path, "boot/efi") # For power we want to install grub directly onto the PreP partition @@ -434,16 +433,33 @@ def _install_grub2(device, root_uuid, efi_system_part_uuid=None, disks = [device] utils.execute('mount', root_partition, path) - for fs in BIND_MOUNTS: - utils.execute('mount', '-o', 'bind', fs, path + fs) - utils.execute('mount', '-t', 'sysfs', 'none', path + '/sys') + _mount_for_chroot(path) - if efi_partition: - if not os.path.exists(efi_partition_mount_point): - os.makedirs(efi_partition_mount_point) - utils.execute('mount', efi_partition, efi_partition_mount_point) - efi_mounted = True + # UEFI asset management for RAID is handled elsewhere + if not hardware.is_md_device(device) and efi_partition_mount_point: + # NOTE(TheJulia): It may make sense to retool all efi + # asset preservation logic at some point since the paths + # can be a little different, but largely this is JUST for + # partition images as there _should not_ be a mount + # point if we have no efi partitions at all. + efi_preserved = _try_preserve_efi_assets( + device, path, efi_system_part_uuid, + efi_partitions, efi_partition_mount_point) + if efi_preserved: + # Success preserving efi assets + return + else: + # Failure, either via exception or not found + # which in this case the partition needs to be + # remounted. + LOG.debug('No EFI assets were preserved for setup or the ' + 'ramdisk was unable to complete the setup. ' + 'falling back to bootloader installation from' + 'deployed image.') + if not os.path.ismount(root_partition): + LOG.debug('Re-mounting the root partition.') + utils.execute('mount', root_partition, path) binary_name = "grub" if os.path.exists(os.path.join(path, 'usr/sbin/grub2-install')): @@ -457,55 +473,81 @@ def _install_grub2(device, root_uuid, efi_system_part_uuid=None, {'path': path}, shell=True, env_variables={'PATH': path_variable}) - # Install grub. Normally, grub goes to one disk only. In case of - # md devices, grub goes to all underlying holder (RAID-1) disks. - LOG.info("GRUB2 will be installed on disks %s", disks) - for grub_disk in disks: - LOG.debug("Installing GRUB2 on disk %s", grub_disk) - utils.execute('chroot %(path)s /bin/sh -c ' - '"%(bin)s-install %(dev)s"' % - {'path': path, 'bin': binary_name, - 'dev': grub_disk}, - shell=True, env_variables={'PATH': path_variable}) - LOG.debug("GRUB2 successfully installed on device %s", grub_disk) + if efi_partitions: + if not os.path.exists(efi_partition_mount_point): + os.makedirs(efi_partition_mount_point) + LOG.warning("GRUB2 will be installed for UEFI on efi partitions " + "%s using the install command which does not place " + "Secure Boot signed binaries.", efi_partitions) + for efi_partition in efi_partitions: + utils.execute( + 'mount', efi_partition, efi_partition_mount_point) + efi_mounted = True + # FIXME(rg): does not work in cross boot mode case (target + # boot mode differs from ramdisk one) + # Probe for the correct target (depends on the arch, example + # --target=x86_64-efi) + utils.execute('chroot %(path)s /bin/sh -c ' + '"%(bin)s-install"' % + {'path': path, 'bin': binary_name}, + shell=True, + env_variables={ + 'PATH': path_variable + }) + # Also run grub-install with --removable, this installs grub to + # the EFI fallback path. Useful if the NVRAM wasn't written + # correctly, was reset or if testing with virt as libvirt + # resets the NVRAM on instance start. + # This operation is essentially a copy operation. Use of the + # --removable flag, per the grub-install source code changes + # the default file to be copied, destination file name, and + # prevents NVRAM from being updated. + # We only run grub2_install for uefi if we can't verify the + # uefi bits + utils.execute('chroot %(path)s /bin/sh -c ' + '"%(bin)s-install --removable"' % + {'path': path, 'bin': binary_name}, + shell=True, + env_variables={ + 'PATH': path_variable + }) + utils.execute('umount', efi_partition_mount_point, attempts=3, + delay_on_retry=True) + efi_mounted = False + # NOTE: probably never needed for grub-mkconfig, does not hurt in + # case of doubt, cleaned in the finally clause anyway + utils.execute('mount', efi_partitions[0], + efi_partition_mount_point) + efi_mounted = True + else: + # FIXME(rg): does not work if ramdisk boot mode is not the same + # as the target (--target=i386-pc, arch dependent). + # See previous FIXME - # Also run grub-install with --removable, this installs grub to the - # EFI fallback path. Useful if the NVRAM wasn't written correctly, - # was reset or if testing with virt as libvirt resets the NVRAM - # on instance start. - # This operation is essentially a copy operation. Use of the - # --removable flag, per the grub-install source code changes - # the default file to be copied, destination file name, and - # prevents NVRAM from being updated. - # We only run grub2_install for uefi if we can't verify the uefi bits - if efi_partition: - utils.execute('chroot %(path)s /bin/sh -c ' - '"%(bin)s-install %(dev)s --removable"' % - {'path': path, 'bin': binary_name, 'dev': device}, - shell=True, env_variables={'PATH': path_variable}) + # Install grub. Normally, grub goes to one disk only. In case of + # md devices, grub goes to all underlying holder (RAID-1) disks. + LOG.info("GRUB2 will be installed on disks %s", disks) + for grub_disk in disks: + LOG.debug("Installing GRUB2 on disk %s", grub_disk) + utils.execute( + 'chroot %(path)s /bin/sh -c "%(bin)s-install %(dev)s"' % + { + 'path': path, + 'bin': binary_name, + 'dev': grub_disk + }, + shell=True, + env_variables={ + 'PATH': path_variable + } + ) + LOG.debug("GRUB2 successfully installed on device %s", + grub_disk) - # If the image has dracut installed, set the rd.md.uuid kernel - # parameter for discovered md devices. - if hardware.is_md_device(device) and _has_dracut(path): - rd_md_uuids = ["rd.md.uuid=%s" % x['UUID'] - for x in hardware.md_get_raid_devices().values()] - - LOG.debug("Setting rd.md.uuid kernel parameters: %s", rd_md_uuids) - with open('%s/etc/default/grub' % path, 'r') as g: - contents = g.read() - with open('%s/etc/default/grub' % path, 'w') as g: - g.write( - re.sub(r'GRUB_CMDLINE_LINUX="(.*)"', - r'GRUB_CMDLINE_LINUX="\1 %s"' - % " ".join(rd_md_uuids), - contents)) - - # Generate the grub configuration file - utils.execute('chroot %(path)s /bin/sh -c ' - '"%(bin)s-mkconfig -o ' - '/boot/%(bin)s/grub.cfg"' % - {'path': path, 'bin': binary_name}, shell=True, - env_variables={'PATH': path_variable}) + # NOTE(TheJulia): Setup grub configuration again since IF we reach + # this point, then we've manually installed grub which is not the + # recommended path. + _configure_grub(device, path) LOG.info("GRUB2 successfully installed on %s", device) @@ -516,6 +558,7 @@ def _install_grub2(device, root_uuid, efi_system_part_uuid=None, raise errors.CommandExecutionError(error_msg) finally: + LOG.debug('Executing _install_grub2 clean-up.') # Umount binds and partition umount_warn_msg = "Unable to umount %(path)s. Error: %(error)s" @@ -532,7 +575,9 @@ def _install_grub2(device, root_uuid, efi_system_part_uuid=None, raise errors.CommandExecutionError(error_msg) # If umounting the binds succeed then we can try to delete it - if _umount_all_partitions(path, path_variable, umount_warn_msg): + if _umount_all_partitions(path, + path_variable, + umount_warn_msg): try: utils.execute('umount', path, attempts=3, delay_on_retry=True) except processutils.ProcessExecutionError as e: @@ -543,6 +588,242 @@ def _install_grub2(device, root_uuid, efi_system_part_uuid=None, shutil.rmtree(path) +def _get_path_variable(): + # Add /bin to PATH variable as grub requires it to find efibootmgr + # when running in uefi boot mode. + # Add /usr/sbin to PATH variable to ensure it is there as we do + # not use full path to grub binary anymore. + path_variable = os.environ.get('PATH', '') + return '%s:/bin:/usr/sbin:/sbin' % path_variable + + +def _configure_grub(device, path): + """Make consolidated grub configuration as it is device aware. + + :param device: The device for the filesystem. + :param path: The path in which the filesystem is mounted. + """ + LOG.debug('Attempting to generate grub Configuration') + path_variable = _get_path_variable() + binary_name = "grub" + if os.path.exists(os.path.join(path, 'usr/sbin/grub2-install')): + binary_name = "grub2" + # If the image has dracut installed, set the rd.md.uuid kernel + # parameter for discovered md devices. + if hardware.is_md_device(device) and _has_dracut(path): + rd_md_uuids = ["rd.md.uuid=%s" % x['UUID'] + for x in hardware.md_get_raid_devices().values()] + LOG.debug("Setting rd.md.uuid kernel parameters: %s", rd_md_uuids) + with open('%s/etc/default/grub' % path, 'r') as g: + contents = g.read() + with open('%s/etc/default/grub' % path, 'w') as g: + g.write( + re.sub(r'GRUB_CMDLINE_LINUX="(.*)"', + r'GRUB_CMDLINE_LINUX="\1 %s"' + % " ".join(rd_md_uuids), + contents)) + + utils.execute('chroot %(path)s /bin/sh -c ' + '"%(bin)s-mkconfig -o ' + '/boot/%(bin)s/grub.cfg"' % + {'path': path, 'bin': binary_name}, shell=True, + env_variables={'PATH': path_variable, + 'GRUB_DISABLE_OS_PROBER': 'true', + 'GRUB_SAVEDEFAULT': 'true'}, + use_standard_locale=True) + LOG.debug('Completed basic grub configuration.') + + +def _mount_for_chroot(path): + """Mount items for grub-mkconfig to succeed.""" + LOG.debug('Mounting Linux standard partitions for bootloader ' + 'configuration generation') + for fs in BIND_MOUNTS: + utils.execute('mount', '-o', 'bind', fs, path + fs) + utils.execute('mount', '-t', 'sysfs', 'none', path + '/sys') + + +def _try_preserve_efi_assets(device, path, + efi_system_part_uuid, + efi_partitions, + efi_partition_mount_point): + """Attempt to preserve UEFI boot assets. + + :param device: The device upon which wich to try to preserve + assets. + :param path: The path in which the filesystem is already mounted + which we should examine to preserve assets from. + :param efi_system_part_uuid: The partition ID representing the + created EFI system partition. + :param efi_partitions: The list of partitions upon wich to + write the preserved assets to. + :param efi_partition_mount_point: The folder at which to mount + the assets for the process of + preservation. + + :returns: True if assets have been preserved, otherwise False. + None is the result of this method if a failure has + occured. + """ + efi_assets_folder = efi_partition_mount_point + '/EFI' + if os.path.exists(efi_assets_folder): + # We appear to have EFI Assets, that need to be preserved + # and as such if we succeed preserving them, we will be returned + # True from _preserve_efi_assets to correspond with success or + # failure in this action. + # NOTE(TheJulia): Still makes sense to invoke grub-install as + # fragmentation of grub has occured. + if (os.path.exists(os.path.join(path, 'usr/sbin/grub2-install')) + or os.path.exists(os.path.join(path, 'usr/sbin/grub-install'))): + _configure_grub(device, path) + # But first, if we have grub, we should try to build a grub config! + LOG.debug('EFI asset folder detected, attempting to preserve assets.') + if _preserve_efi_assets(path, efi_assets_folder, + efi_partitions, + efi_partition_mount_point): + try: + # Since we have preserved the assets, we should be able + # to call the _efi_boot_setup method to scan the device + # and add loader entries + efi_preserved = _efi_boot_setup(device, efi_system_part_uuid) + # Executed before the return so we don't return and then begin + # execution. + return efi_preserved + except Exception as e: + # Remount the partition and proceed as we were. + LOG.debug('Exception encountered while attempting to ' + 'setup the EFI loader from a root ' + 'filesystem. Error: %s', e) + + +def _efi_boot_setup(device, efi_system_part_uuid=None, target_boot_mode=None): + """Identify and setup an EFI bootloader from supplied partition/disk. + + :param device: The device upon which to attempt the EFI bootloader setup. + :param efi_system_part_uuid: The partition UUID to utilize in searching + for an EFI bootloader. + :param target_boot_mode: The requested boot mode target for the + machine. This is optional and is mainly used + for the purposes of identifying a mismatch and + reporting a warning accordingly. + :returns: True if we succeeded in setting up an EFI bootloader in the + EFI nvram table. + False if we were unable to set the machine to EFI boot, + due to inability to locate assets required OR the efibootmgr + tool not being present. + None is returned if the node is NOT in UEFI boot mode or + the system is deploying upon a software RAID device. + """ + boot = hardware.dispatch_to_managers('get_boot_info') + # Explicitly only run if a target_boot_mode is set which prevents + # callers following-up from re-logging the same message + if target_boot_mode and boot.current_boot_mode != target_boot_mode: + LOG.warning('Boot mode mismatch: target boot mode is %(target)s, ' + 'current boot mode is %(current)s. Installing boot ' + 'loader may fail or work incorrectly.', + {'target': target_boot_mode, + 'current': boot.current_boot_mode}) + + # FIXME(arne_wiebalck): make software RAID work with efibootmgr + if (boot.current_boot_mode == 'uefi' + and not hardware.is_md_device(device)): + try: + utils.execute('efibootmgr', '--version') + except FileNotFoundError: + LOG.warning("efibootmgr is not available in the ramdisk") + else: + if _manage_uefi(device, + efi_system_part_uuid=efi_system_part_uuid): + return True + return False + + +def _preserve_efi_assets(path, efi_assets_folder, efi_partitions, + efi_partition_mount_point): + """Preserve the EFI assets in a partition image. + + :param path: The path used for the mounted image filesystem. + :param efi_assets_folder: The folder where we can find the + UEFI assets required for booting. + :param efi_partitions: The list of partitions upon which to + write the perserved assets to. + :param efi_partition_mount_point: The folder at which to mount + the assets for the process of + preservation. + :returns: True if EFI assets were able to be located and preserved + to their appropriate locations based upon the supplied + efi_partitions list. + False if any error is encountered in this process. + """ + try: + save_efi = os.path.join(tempfile.mkdtemp(), 'efi_loader') + LOG.debug('Copying EFI assets to %s.', save_efi) + shutil.copytree(efi_assets_folder, save_efi) + + # Identify grub2 config file for EFI booting as grub may require it + # in the folder. + + destlist = os.listdir(efi_assets_folder) + grub2_file = os.path.join(path, 'boot/grub2/grub.cfg') + if os.path.isfile(grub2_file): + LOG.debug('Local Grub2 configuration detected.') + # A grub2 config seems to be present, we should preserve it! + for dest in destlist: + grub_dest = os.path.join(save_efi, dest, 'grub.cfg') + if not os.path.isfile(grub_dest): + LOG.debug('A grub.cfg file was not found in %s. %s' + 'will be copied to that location.', + grub_dest, grub2_file) + try: + shutil.copy2(grub2_file, grub_dest) + except (IOError, OSError, shutil.SameFileError) as e: + LOG.warning('Failed to copy grub.cfg file for ' + 'EFI boot operation. Error %s', e) + grub2_env_file = os.path.join(path, 'boot/grub2/grubenv') + # NOTE(TheJulia): By saving the default, this file should be created. + # this appears to what diskimage-builder does. + # if the file is just a file, then we'll need to copy it. If it is + # anything else like a link, we're good. This behaivor is inconsistent + # depending on packager install scripts for grub. + if os.path.isfile(grub2_env_file): + LOG.debug('Detected grub environment file %s, will attempt ' + 'to copy this file to align with apparent bootloaders', + grub2_env_file) + for dest in destlist: + grub2env_dest = os.path.join(save_efi, dest, 'grubenv') + if not os.path.isfile(grub2env_dest): + LOG.debug('A grubenv file was not found. Copying ' + 'to %s along with the grub.cfg file as ' + 'grub generally expects it is present.', + grub2env_dest) + try: + shutil.copy2(grub2_env_file, grub2env_dest) + except (IOError, OSError, shutil.SameFileError) as e: + LOG.warning('Failed to copy grubenv file. ' + 'Error: %s', e) + # Loop through partitions because software RAID. + for efi_part in efi_partitions: + utils.execute('mount', '-t', 'vfat', efi_part, + efi_partition_mount_point) + shutil.copytree(save_efi, efi_assets_folder) + LOG.debug('Files preserved to %(disk)s for %(part)s. ' + 'Files: %(filelist)s From: %(from)s', + {'disk': efi_part, + 'part': efi_partition_mount_point, + 'filelist': os.listdir(efi_assets_folder), + 'from': save_efi}) + utils.execute('umount', efi_partition_mount_point) + return True + except Exception as e: + LOG.debug('Failed to preserve EFI assets. Error %s', e) + try: + utils.execute('umount', efi_partition_mount_point) + except Exception as e: + LOG.debug('Exception encountered while attempting unmount ' + 'the EFI partition mount point. Error: %s', e) + return False + + class ImageExtension(base.BaseAgentExtension): @base.sync_command('install_bootloader') @@ -573,31 +854,13 @@ class ImageExtension(base.BaseAgentExtension): else: ignore_failure = ignore_bootloader_failure - boot = hardware.dispatch_to_managers('get_boot_info') - if boot.current_boot_mode == 'uefi': - has_efibootmgr = True - # NOTE(iurygregory): adaptation for py27 since we don't have - # FileNotFoundError defined. - try: - FileNotFoundError - except NameError: - FileNotFoundError = OSError - try: - utils.execute('efibootmgr', '--version') - except FileNotFoundError: - LOG.warning("efibootmgr is not available in the ramdisk") - has_efibootmgr = False - - if has_efibootmgr: - try: - if _manage_uefi( - device, - efi_system_part_uuid=efi_system_part_uuid): - return - except Exception as e: - LOG.error('Error setting up bootloader. Error %s', e) - if not ignore_failure: - raise + try: + if _efi_boot_setup(device, efi_system_part_uuid): + return + except Exception as e: + LOG.error('Error setting up bootloader. Error %s', e) + if not ignore_failure: + raise # In case we can't use efibootmgr for uefi we will continue using grub2 LOG.debug('Using grub2-install to set up boot files') diff --git a/ironic_python_agent/tests/unit/extensions/test_image.py b/ironic_python_agent/tests/unit/extensions/test_image.py index 780859006..d47ebeb7a 100644 --- a/ironic_python_agent/tests/unit/extensions/test_image.py +++ b/ironic_python_agent/tests/unit/extensions/test_image.py @@ -204,6 +204,7 @@ class TestImageExtension(base.IronicAgentTest): ) mock_iscsi_clean.assert_called_once_with(self.fake_dev) + @mock.patch.object(hardware, 'is_md_device', lambda *_: False) @mock.patch.object(os.path, 'exists', lambda *_: False) @mock.patch.object(iscsi, 'clean_up', autospec=True) @mock.patch.object(image, '_get_efi_bootloaders', autospec=True) @@ -252,6 +253,7 @@ class TestImageExtension(base.IronicAgentTest): mock_utils_efi_part.assert_called_once_with(self.fake_dev) self.assertEqual(8, mock_execute.call_count) + @mock.patch.object(hardware, 'is_md_device', lambda *_: False) @mock.patch.object(os.path, 'exists', lambda *_: False) @mock.patch.object(iscsi, 'clean_up', autospec=True) @mock.patch.object(image, '_get_efi_bootloaders', autospec=True) @@ -299,6 +301,7 @@ class TestImageExtension(base.IronicAgentTest): mock_utils_efi_part.assert_called_once_with(self.fake_dev) self.assertEqual(8, mock_execute.call_count) + @mock.patch.object(hardware, 'is_md_device', lambda *_: False) @mock.patch.object(os.path, 'exists', lambda *_: False) @mock.patch.object(iscsi, 'clean_up', autospec=True) @mock.patch.object(image, '_get_efi_bootloaders', autospec=True) @@ -353,6 +356,7 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n mock_utils_efi_part.assert_called_once_with(self.fake_dev) self.assertEqual(10, mock_execute.call_count) + @mock.patch.object(hardware, 'is_md_device', lambda *_: False) @mock.patch.object(os.path, 'exists', lambda *_: False) @mock.patch.object(iscsi, 'clean_up', autospec=True) @mock.patch.object(image, '_get_efi_bootloaders', autospec=True) @@ -428,6 +432,7 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n prep_boot_part_uuid=self.fake_prep_boot_part_uuid) mock_iscsi_clean.assert_called_once_with(self.fake_dev) + @mock.patch.object(hardware, 'is_md_device', lambda *_: False) @mock.patch.object(os.path, 'exists', lambda *_: False) @mock.patch.object(iscsi, 'clean_up', autospec=True) def test_install_bootloader_failure(self, mock_iscsi_clean, mock_execute, @@ -486,8 +491,11 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n '"grub-mkconfig -o ' '/boot/grub/grub.cfg"' % self.fake_dir), shell=True, - env_variables={'PATH': - '/sbin:/bin:/usr/sbin:/sbin'}), + env_variables={ + 'PATH': '/sbin:/bin:/usr/sbin:/sbin', + 'GRUB_DISABLE_OS_PROBER': 'true', + 'GRUB_SAVEDEFAULT': 'true'}, + use_standard_locale=True), mock.call(('chroot %s /bin/sh -c "umount -a -t vfat"' % (self.fake_dir)), shell=True, env_variables={ @@ -546,8 +554,11 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n '"grub-mkconfig -o ' '/boot/grub/grub.cfg"' % self.fake_dir), shell=True, - env_variables={'PATH': - '/sbin:/bin:/usr/sbin:/sbin'}), + env_variables={ + 'PATH': '/sbin:/bin:/usr/sbin:/sbin', + 'GRUB_DISABLE_OS_PROBER': 'true', + 'GRUB_SAVEDEFAULT': 'true'}, + use_standard_locale=True), mock.call(('chroot %s /bin/sh -c "umount -a -t vfat"' % (self.fake_dir)), shell=True, env_variables={ @@ -569,6 +580,7 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n uuid=self.fake_prep_boot_part_uuid) self.assertFalse(mock_dispatch.called) + @mock.patch.object(os.path, 'ismount', lambda *_: True) @mock.patch.object(os.path, 'exists', lambda *_: False) @mock.patch.object(image, '_is_bootloader_loaded', lambda *_: True) @mock.patch.object(hardware, 'is_md_device', autospec=True) @@ -599,28 +611,34 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n self.fake_dir + '/run'), mock.call('mount', '-t', 'sysfs', 'none', self.fake_dir + '/sys'), - mock.call('mount', self.fake_efi_system_part, - self.fake_dir + '/boot/efi'), mock.call(('chroot %s /bin/sh -c "mount -a -t vfat"' % (self.fake_dir)), shell=True, env_variables={ 'PATH': '/sbin:/bin:/usr/sbin:/sbin'}), + mock.call('mount', self.fake_efi_system_part, + self.fake_dir + '/boot/efi'), mock.call(('chroot %s /bin/sh -c ' - '"grub-install %s"' % - (self.fake_dir, self.fake_dev)), shell=True, + '"grub-install"' % self.fake_dir), shell=True, env_variables={ 'PATH': '/sbin:/bin:/usr/sbin:/sbin'}), mock.call(('chroot %s /bin/sh -c ' - '"grub-install %s --removable"' % - (self.fake_dir, self.fake_dev)), shell=True, + '"grub-install --removable"' % self.fake_dir), + shell=True, env_variables={ 'PATH': '/sbin:/bin:/usr/sbin:/sbin'}), + mock.call('umount', self.fake_dir + '/boot/efi', + attempts=3, delay_on_retry=True), + mock.call('mount', self.fake_efi_system_part, + self.fake_dir + '/boot/efi'), mock.call(('chroot %s /bin/sh -c ' '"grub-mkconfig -o ' '/boot/grub/grub.cfg"' % self.fake_dir), shell=True, env_variables={ - 'PATH': '/sbin:/bin:/usr/sbin:/sbin'}), + 'PATH': '/sbin:/bin:/usr/sbin:/sbin', + 'GRUB_DISABLE_OS_PROBER': 'true', + 'GRUB_SAVEDEFAULT': 'true'}, + use_standard_locale=True), 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"' % @@ -645,6 +663,463 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n uuid=self.fake_efi_system_part_uuid) self.assertFalse(mock_dispatch.called) + @mock.patch.object(os.path, 'ismount', lambda *_: False) + @mock.patch.object(os, 'listdir', lambda *_: ['file1', 'file2']) + @mock.patch.object(image, '_is_bootloader_loaded', lambda *_: False) + @mock.patch.object(image, '_efi_boot_setup', autospec=True) + @mock.patch.object(shutil, 'copytree', autospec=True) + @mock.patch.object(os.path, 'exists', autospec=True) + @mock.patch.object(hardware, 'is_md_device', autospec=True) + @mock.patch.object(hardware, 'md_get_raid_devices', autospec=True) + @mock.patch.object(os, 'environ', autospec=True) + @mock.patch.object(os, 'makedirs', autospec=True) + @mock.patch.object(image, '_get_partition', autospec=True) + def test__install_grub2_uefi_partition_image_with_loader( + self, mock_get_part_uuid, mkdir_mock, + environ_mock, mock_md_get_raid_devices, + mock_is_md_device, mock_exists, + mock_copytree, mock_efi_setup, + 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, + self.fake_efi_system_part] + environ_mock.get.return_value = '/sbin' + mock_is_md_device.return_value = False + mock_md_get_raid_devices.return_value = {} + + image._install_grub2( + self.fake_dev, root_uuid=self.fake_root_uuid, + efi_system_part_uuid=self.fake_efi_system_part_uuid) + mock_efi_setup.assert_called_once_with(self.fake_dev, + self.fake_efi_system_part_uuid) + mock_copytree.assert_has_calls([ + mock.call(self.fake_dir + '/boot/efi/EFI', + self.fake_dir + '/efi_loader'), + mock.call(self.fake_dir + '/efi_loader', + self.fake_dir + '/boot/efi/EFI')]) + + expected = [mock.call('mount', '/dev/fake2', self.fake_dir), + mock.call('mount', '-o', 'bind', '/dev', + self.fake_dir + '/dev'), + mock.call('mount', '-o', 'bind', '/proc', + self.fake_dir + '/proc'), + mock.call('mount', '-o', 'bind', '/run', + self.fake_dir + '/run'), + mock.call('mount', '-t', 'sysfs', 'none', + self.fake_dir + '/sys'), + mock.call('chroot %s /bin/sh -c "grub2-mkconfig -o ' + '/boot/grub2/grub.cfg"' % self.fake_dir, + shell=True, + env_variables={ + 'PATH': '/sbin:/bin:/usr/sbin:/sbin', + 'GRUB_DISABLE_OS_PROBER': 'true', + 'GRUB_SAVEDEFAULT': 'true'}, + use_standard_locale=True), + mock.call('mount', '-t', 'vfat', '/dev/fake1', + self.fake_dir + '/boot/efi'), + mock.call('umount', self.fake_dir + '/boot/efi'), + mock.call('chroot %s /bin/sh -c "umount -a -t ' + 'vfat"' % self.fake_dir, shell=True, + env_variables={ + 'PATH': '/sbin:/bin:/usr/sbin:/sbin'}), + mock.call('umount', self.fake_dir + '/dev', attempts=3, + delay_on_retry=True), + mock.call('umount', self.fake_dir + '/proc', attempts=3, + delay_on_retry=True), + mock.call('umount', self.fake_dir + '/run', attempts=3, + delay_on_retry=True), + mock.call('umount', self.fake_dir + '/sys', attempts=3, + delay_on_retry=True), + mock.call('umount', self.fake_dir, attempts=3, + delay_on_retry=True)] + mkdir_mock.assert_not_called() + mock_execute.assert_has_calls(expected) + mock_get_part_uuid.assert_any_call(self.fake_dev, + uuid=self.fake_root_uuid) + mock_get_part_uuid.assert_any_call(self.fake_dev, + uuid=self.fake_efi_system_part_uuid) + self.assertFalse(mock_dispatch.called) + + @mock.patch.object(os, 'listdir', lambda *_: ['file1', 'file2']) + @mock.patch.object(image, '_is_bootloader_loaded', lambda *_: False) + @mock.patch.object(shutil, 'copy2', autospec=True) + @mock.patch.object(os.path, 'isfile', autospec=True) + @mock.patch.object(image, '_efi_boot_setup', autospec=True) + @mock.patch.object(shutil, 'copytree', autospec=True) + @mock.patch.object(os.path, 'exists', autospec=True) + @mock.patch.object(hardware, 'is_md_device', autospec=True) + @mock.patch.object(hardware, 'md_get_raid_devices', autospec=True) + @mock.patch.object(os, 'environ', autospec=True) + @mock.patch.object(os, 'makedirs', autospec=True) + @mock.patch.object(image, '_get_partition', autospec=True) + def test__install_grub2_uefi_partition_image_with_loader_with_grubcfg( + self, mock_get_part_uuid, mkdir_mock, + environ_mock, mock_md_get_raid_devices, + mock_is_md_device, mock_exists, + mock_copytree, mock_efi_setup, + mock_isfile, mock_copy2, + 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, + self.fake_efi_system_part] + environ_mock.get.return_value = '/sbin' + mock_is_md_device.return_value = False + mock_md_get_raid_devices.return_value = {} + mock_isfile.side_effect = [True, False, False, True, True, False] + + image._install_grub2( + self.fake_dev, root_uuid=self.fake_root_uuid, + efi_system_part_uuid=self.fake_efi_system_part_uuid) + mock_efi_setup.assert_called_once_with(self.fake_dev, + self.fake_efi_system_part_uuid) + mock_copytree.assert_has_calls([ + mock.call(self.fake_dir + '/boot/efi/EFI', + self.fake_dir + '/efi_loader'), + mock.call(self.fake_dir + '/efi_loader', + self.fake_dir + '/boot/efi/EFI')]) + + expected = [mock.call('mount', '/dev/fake2', self.fake_dir), + mock.call('mount', '-o', 'bind', '/dev', + self.fake_dir + '/dev'), + mock.call('mount', '-o', 'bind', '/proc', + self.fake_dir + '/proc'), + mock.call('mount', '-o', 'bind', '/run', + self.fake_dir + '/run'), + mock.call('mount', '-t', 'sysfs', 'none', + self.fake_dir + '/sys'), + mock.call(('chroot ' + self.fake_dir + ' /bin/sh -c ' + '"grub2-mkconfig -o /boot/grub2/grub.cfg"'), + shell=True, + env_variables={ + 'PATH': '/sbin:/bin:/usr/sbin:/sbin', + 'GRUB_DISABLE_OS_PROBER': 'true', + 'GRUB_SAVEDEFAULT': 'true'}, + use_standard_locale=True), + mock.call('mount', '-t', 'vfat', '/dev/fake1', + self.fake_dir + '/boot/efi'), + mock.call('umount', self.fake_dir + '/boot/efi'), + mock.call(('chroot ' + self.fake_dir + + ' /bin/sh -c "umount -a -t vfat"'), + shell=True, + env_variables={ + 'PATH': '/sbin:/bin:/usr/sbin:/sbin'}), + mock.call('umount', self.fake_dir + '/dev', attempts=3, + delay_on_retry=True), + mock.call('umount', self.fake_dir + '/proc', attempts=3, + delay_on_retry=True), + mock.call('umount', self.fake_dir + '/run', attempts=3, + delay_on_retry=True), + mock.call('umount', self.fake_dir + '/sys', attempts=3, + delay_on_retry=True), + mock.call('umount', self.fake_dir, attempts=3, + delay_on_retry=True)] + mkdir_mock.assert_not_called() + mock_execute.assert_has_calls(expected) + mock_copy2.assert_has_calls([]) + mock_get_part_uuid.assert_any_call(self.fake_dev, + uuid=self.fake_root_uuid) + mock_get_part_uuid.assert_any_call(self.fake_dev, + uuid=self.fake_efi_system_part_uuid) + self.assertFalse(mock_dispatch.called) + + @mock.patch.object(os.path, 'ismount', lambda *_: True) + @mock.patch.object(image, '_is_bootloader_loaded', lambda *_: False) + @mock.patch.object(image, '_preserve_efi_assets', autospec=True) + @mock.patch.object(image, '_efi_boot_setup', autospec=True) + @mock.patch.object(os.path, 'exists', autospec=True) + @mock.patch.object(hardware, 'is_md_device', autospec=True) + @mock.patch.object(hardware, 'md_get_raid_devices', autospec=True) + @mock.patch.object(os, 'environ', autospec=True) + @mock.patch.object(os, 'makedirs', autospec=True) + @mock.patch.object(image, '_get_partition', autospec=True) + def test__install_grub2_uefi_partition_image_with_preserve_failure( + self, mock_get_part_uuid, mkdir_mock, + environ_mock, mock_md_get_raid_devices, + mock_is_md_device, mock_exists, + mock_efi_setup, + mock_preserve_efi_assets, + mock_execute, mock_dispatch): + mock_exists.return_value = True + mock_efi_setup.side_effect = Exception('meow') + mock_get_part_uuid.side_effect = [self.fake_root_part, + self.fake_efi_system_part] + environ_mock.get.return_value = '/sbin' + mock_is_md_device.return_value = False + mock_md_get_raid_devices.return_value = {} + mock_preserve_efi_assets.return_value = False + + image._install_grub2( + self.fake_dev, root_uuid=self.fake_root_uuid, + efi_system_part_uuid=self.fake_efi_system_part_uuid) + self.assertFalse(mock_efi_setup.called) + + expected = [mock.call('mount', '/dev/fake2', self.fake_dir), + mock.call('mount', '-o', 'bind', '/dev', + self.fake_dir + '/dev'), + mock.call('mount', '-o', 'bind', '/proc', + self.fake_dir + '/proc'), + mock.call('mount', '-o', 'bind', '/run', + self.fake_dir + '/run'), + mock.call('mount', '-t', 'sysfs', 'none', + self.fake_dir + '/sys'), + mock.call(('chroot %s /bin/sh -c ' + '"grub2-mkconfig -o ' + '/boot/grub2/grub.cfg"' % self.fake_dir), + shell=True, + env_variables={ + 'PATH': '/sbin:/bin:/usr/sbin:/sbin', + 'GRUB_DISABLE_OS_PROBER': 'true', + 'GRUB_SAVEDEFAULT': 'true'}, + use_standard_locale=True), + mock.call(('chroot %s /bin/sh -c "mount -a -t vfat"' % + (self.fake_dir)), shell=True, + env_variables={ + 'PATH': '/sbin:/bin:/usr/sbin:/sbin'}), + mock.call('mount', self.fake_efi_system_part, + self.fake_dir + '/boot/efi'), + mock.call(('chroot %s /bin/sh -c "grub2-install"' % + self.fake_dir), shell=True, + env_variables={ + 'PATH': '/sbin:/bin:/usr/sbin:/sbin'}), + mock.call(('chroot %s /bin/sh -c ' + '"grub2-install --removable"' % + self.fake_dir), shell=True, + env_variables={ + 'PATH': '/sbin:/bin:/usr/sbin:/sbin'}), + mock.call( + 'umount', self.fake_dir + '/boot/efi', + attempts=3, delay_on_retry=True), + mock.call('mount', self.fake_efi_system_part, + '/tmp/fake-dir/boot/efi'), + mock.call(('chroot %s /bin/sh -c ' + '"grub2-mkconfig -o ' + '/boot/grub2/grub.cfg"' % self.fake_dir), + shell=True, + env_variables={ + 'PATH': '/sbin:/bin:/usr/sbin:/sbin', + 'GRUB_DISABLE_OS_PROBER': 'true', + 'GRUB_SAVEDEFAULT': 'true'}, + use_standard_locale=True), + 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"' % + (self.fake_dir)), shell=True, + env_variables={ + 'PATH': '/sbin:/bin:/usr/sbin:/sbin'}), + mock.call('umount', self.fake_dir + '/dev', + attempts=3, delay_on_retry=True), + mock.call('umount', self.fake_dir + '/proc', + attempts=3, delay_on_retry=True), + mock.call('umount', self.fake_dir + '/run', + attempts=3, delay_on_retry=True), + mock.call('umount', self.fake_dir + '/sys', + attempts=3, delay_on_retry=True), + mock.call('umount', self.fake_dir, attempts=3, + delay_on_retry=True)] + + mkdir_mock.assert_not_called() + mock_execute.assert_has_calls(expected) + mock_get_part_uuid.assert_any_call(self.fake_dev, + uuid=self.fake_root_uuid) + mock_get_part_uuid.assert_any_call(self.fake_dev, + uuid=self.fake_efi_system_part_uuid) + self.assertFalse(mock_dispatch.called) + mock_preserve_efi_assets.assert_called_with( + self.fake_dir, + self.fake_dir + '/boot/efi/EFI', + ['/dev/fake1'], + self.fake_dir + '/boot/efi') + + @mock.patch.object(image, '_is_bootloader_loaded', lambda *_: False) + @mock.patch.object(os, 'listdir', 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) + @mock.patch.object(shutil, 'copytree', autospec=True) + @mock.patch.object(os.path, 'exists', autospec=True) + @mock.patch.object(hardware, 'is_md_device', autospec=True) + @mock.patch.object(hardware, 'md_get_raid_devices', autospec=True) + @mock.patch.object(os, 'environ', autospec=True) + @mock.patch.object(os, 'makedirs', autospec=True) + @mock.patch.object(image, '_get_partition', autospec=True) + def test__install_grub2_uefi_partition_image_with_loader_grubcfg_fails( + self, mock_get_part_uuid, mkdir_mock, + environ_mock, mock_md_get_raid_devices, + mock_is_md_device, mock_exists, + mock_copytree, mock_efi_setup, + mock_isfile, mock_copy2, + mock_oslistdir, 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, + self.fake_efi_system_part] + environ_mock.get.return_value = '/sbin' + mock_is_md_device.return_value = False + mock_md_get_raid_devices.return_value = {} + mock_isfile.side_effect = [True, False, False, True, False, + True, False] + mock_copy2.side_effect = OSError('copy failed') + mock_oslistdir.return_value = ['file1', 'file2'] + + image._install_grub2( + self.fake_dev, root_uuid=self.fake_root_uuid, + efi_system_part_uuid=self.fake_efi_system_part_uuid) + mock_efi_setup.assert_called_once_with(self.fake_dev, + self.fake_efi_system_part_uuid) + mock_copytree.assert_has_calls([ + mock.call(self.fake_dir + '/boot/efi/EFI', + self.fake_dir + '/efi_loader'), + mock.call(self.fake_dir + '/efi_loader', + self.fake_dir + '/boot/efi/EFI')]) + + expected = [mock.call('mount', '/dev/fake2', self.fake_dir), + mock.call('mount', '-o', 'bind', '/dev', + self.fake_dir + '/dev'), + mock.call('mount', '-o', 'bind', '/proc', + self.fake_dir + '/proc'), + mock.call('mount', '-o', 'bind', '/run', + self.fake_dir + '/run'), + mock.call('mount', '-t', 'sysfs', 'none', + self.fake_dir + '/sys'), + mock.call(('chroot ' + self.fake_dir + ' /bin/sh -c ' + '"grub2-mkconfig -o /boot/grub2/grub.cfg"'), + shell=True, + env_variables={ + 'PATH': '/sbin:/bin:/usr/sbin:/sbin', + 'GRUB_DISABLE_OS_PROBER': 'true', + 'GRUB_SAVEDEFAULT': 'true'}, + use_standard_locale=True), + mock.call('mount', '-t', 'vfat', '/dev/fake1', + self.fake_dir + '/boot/efi'), + mock.call('umount', self.fake_dir + '/boot/efi'), + mock.call(('chroot ' + self.fake_dir + + ' /bin/sh -c "umount -a -t vfat"'), + shell=True, + env_variables={ + 'PATH': '/sbin:/bin:/usr/sbin:/sbin'}), + mock.call('umount', self.fake_dir + '/dev', attempts=3, + delay_on_retry=True), + mock.call('umount', self.fake_dir + '/proc', attempts=3, + delay_on_retry=True), + mock.call('umount', self.fake_dir + '/run', attempts=3, + delay_on_retry=True), + mock.call('umount', self.fake_dir + '/sys', attempts=3, + delay_on_retry=True), + mock.call('umount', self.fake_dir, attempts=3, + delay_on_retry=True)] + mkdir_mock.assert_not_called() + mock_execute.assert_has_calls(expected) + self.assertEqual(3, mock_copy2.call_count) + mock_get_part_uuid.assert_any_call(self.fake_dev, + uuid=self.fake_root_uuid) + mock_get_part_uuid.assert_any_call(self.fake_dev, + uuid=self.fake_efi_system_part_uuid) + self.assertFalse(mock_dispatch.called) + self.assertEqual(2, mock_oslistdir.call_count) + + @mock.patch.object(os.path, 'ismount', lambda *_: True) + @mock.patch.object(image, '_is_bootloader_loaded', lambda *_: False) + @mock.patch.object(os, 'listdir', autospec=True) + @mock.patch.object(image, '_efi_boot_setup', autospec=True) + @mock.patch.object(shutil, 'copytree', autospec=True) + @mock.patch.object(os.path, 'exists', autospec=True) + @mock.patch.object(hardware, 'is_md_device', autospec=True) + @mock.patch.object(hardware, 'md_get_raid_devices', autospec=True) + @mock.patch.object(os, 'environ', autospec=True) + @mock.patch.object(os, 'makedirs', autospec=True) + @mock.patch.object(image, '_get_partition', autospec=True) + def test__install_grub2_uefi_partition_image_with_no_loader( + self, mock_get_part_uuid, mkdir_mock, + environ_mock, mock_md_get_raid_devices, + mock_is_md_device, mock_exists, + mock_copytree, mock_efi_setup, + mock_oslistdir, mock_execute, + mock_dispatch): + mock_exists.side_effect = [True, False, False, True, True, True, True] + mock_efi_setup.side_effect = Exception('meow') + mock_oslistdir.return_value = ['file1'] + mock_get_part_uuid.side_effect = [self.fake_root_part, + self.fake_efi_system_part] + environ_mock.get.return_value = '/sbin' + mock_is_md_device.return_value = False + mock_md_get_raid_devices.return_value = {} + + image._install_grub2( + self.fake_dev, root_uuid=self.fake_root_uuid, + efi_system_part_uuid=self.fake_efi_system_part_uuid) + + expected = [mock.call('mount', '/dev/fake2', self.fake_dir), + mock.call('mount', '-o', 'bind', '/dev', + self.fake_dir + '/dev'), + mock.call('mount', '-o', 'bind', '/proc', + self.fake_dir + '/proc'), + mock.call('mount', '-o', 'bind', '/run', + self.fake_dir + '/run'), + mock.call('mount', '-t', 'sysfs', 'none', + self.fake_dir + '/sys'), + mock.call('mount', '-t', 'vfat', '/dev/fake1', + self.fake_dir + '/boot/efi'), + mock.call('umount', self.fake_dir + '/boot/efi'), + + mock.call(('chroot %s /bin/sh -c "mount -a -t vfat"' % + (self.fake_dir)), shell=True, + env_variables={ + 'PATH': '/sbin:/bin:/usr/sbin:/sbin'}), + mock.call('mount', self.fake_efi_system_part, + self.fake_dir + '/boot/efi'), + mock.call(('chroot %s /bin/sh -c "grub2-install"' % + self.fake_dir), shell=True, + env_variables={ + 'PATH': '/sbin:/bin:/usr/sbin:/sbin'}), + mock.call(('chroot %s /bin/sh -c ' + '"grub2-install --removable"' % + self.fake_dir), shell=True, + env_variables={ + 'PATH': '/sbin:/bin:/usr/sbin:/sbin'}), + mock.call( + 'umount', self.fake_dir + '/boot/efi', + attempts=3, delay_on_retry=True), + mock.call('mount', self.fake_efi_system_part, + '/tmp/fake-dir/boot/efi'), + mock.call(('chroot %s /bin/sh -c ' + '"grub2-mkconfig -o ' + '/boot/grub2/grub.cfg"' % self.fake_dir), + shell=True, + env_variables={ + 'PATH': '/sbin:/bin:/usr/sbin:/sbin', + 'GRUB_DISABLE_OS_PROBER': 'true', + 'GRUB_SAVEDEFAULT': 'true'}, + use_standard_locale=True), + 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"' % + (self.fake_dir)), shell=True, + env_variables={ + 'PATH': '/sbin:/bin:/usr/sbin:/sbin'}), + mock.call('umount', self.fake_dir + '/dev', + attempts=3, delay_on_retry=True), + mock.call('umount', self.fake_dir + '/proc', + attempts=3, delay_on_retry=True), + mock.call('umount', self.fake_dir + '/run', + attempts=3, delay_on_retry=True), + mock.call('umount', self.fake_dir + '/sys', + attempts=3, delay_on_retry=True), + mock.call('umount', self.fake_dir, attempts=3, + delay_on_retry=True)] + + mkdir_mock.assert_not_called() + mock_execute.assert_has_calls(expected) + self.assertEqual(2, mock_copytree.call_count) + self.assertTrue(mock_efi_setup.called) + mock_get_part_uuid.assert_any_call(self.fake_dev, + uuid=self.fake_root_uuid) + mock_get_part_uuid.assert_any_call(self.fake_dev, + uuid=self.fake_efi_system_part_uuid) + self.assertFalse(mock_dispatch.called) + @mock.patch.object(image, '_is_bootloader_loaded', lambda *_: False) @mock.patch.object(hardware, 'is_md_device', autospec=True) @mock.patch.object(hardware, 'md_get_raid_devices', autospec=True) @@ -680,28 +1155,25 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n self.fake_dir + '/run'), mock.call('mount', '-t', 'sysfs', 'none', self.fake_dir + '/sys'), - mock.call('mount', self.fake_efi_system_part, - self.fake_dir + '/boot/efi'), + mock.call('mount', '/dev/fake2', self.fake_dir), mock.call(('chroot %s /bin/sh -c "mount -a -t vfat"' % (self.fake_dir)), shell=True, env_variables={ 'PATH': '/sbin:/bin:/usr/sbin:/sbin'}), + mock.call('mount', '/dev/fake1', + self.fake_dir + '/boot/efi'), mock.call(('chroot %s /bin/sh -c ' - '"grub-install %s"' % - (self.fake_dir, self.fake_dev)), shell=True, - env_variables={'PATH': - '/sbin:/bin:/usr/sbin:/sbin'}), - mock.call(('chroot %s /bin/sh -c ' - '"grub-install %s --removable"' % - (self.fake_dir, self.fake_dev)), shell=True, - env_variables={'PATH': - '/sbin:/bin:/usr/sbin:/sbin'}), - mock.call(('chroot %s /bin/sh -c ' - '"grub-mkconfig -o ' - '/boot/grub/grub.cfg"' % self.fake_dir), + '"grub-install"' % self.fake_dir), shell=True, env_variables={'PATH': '/sbin:/bin:/usr/sbin:/sbin'}), + mock.call(('chroot %s /bin/sh -c ' + '"grub-install --removable"' % + self.fake_dir), shell=True, + env_variables={'PATH': + '/sbin:/bin:/usr/sbin:/sbin'}), + mock.call('umount', self.fake_dir + '/boot/efi', + attempts=3, delay_on_retry=True), mock.call('umount', self.fake_dir + '/boot/efi', attempts=3, delay_on_retry=True)] mock_execute.assert_has_calls(expected) diff --git a/releasenotes/notes/preserve-efi-folder-contents-ea1e278b3093ec55.yaml b/releasenotes/notes/preserve-efi-folder-contents-ea1e278b3093ec55.yaml new file mode 100644 index 000000000..272548211 --- /dev/null +++ b/releasenotes/notes/preserve-efi-folder-contents-ea1e278b3093ec55.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Fixes the agent's EFI boot handling such that EFI assets from a partition + image are preserved and used instead of overridden. This should permit + operators to use Secure Boot with partition images IF the assets are + already present in the partition image.