def _install_grub2(device, root_uuid, efi_system_part_uuid=None, prep_boot_part_uuid=None, target_boot_mode='bios'): """Install GRUB2 bootloader on a given device.""" LOG.debug("Installing GRUB2 bootloader on device %s", device) efi_partitions = None efi_part = None efi_partition_mount_point = None efi_mounted = False efi_preserved = False holders = None 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 # doesn't exactly like the partition disappearing. root_partition = _get_partition(device, uuid=root_uuid) # If the root device is an md device (or partition), restart the device # (to help grub finding it) and identify the underlying holder disks # to install grub. if hardware.is_md_device(device): # If the root device is an md device (or partition), # restart the device to help grub find it later on. hardware.md_restart(device) # If an md device, we need to rescan the devices anyway to pickup # the md device partition. _rescan_device(device) elif (_is_bootloader_loaded(device) and not (efi_system_part_uuid or prep_boot_part_uuid)): # We always need to put the bootloader in place with software raid # so it is okay to elif into the skip doing a bootloader step. LOG.info( "Skipping installation of bootloader on device %s " "as it is already marked bootable.", device) return try: # Mount the partition and binds path = tempfile.mkdtemp() if efi_system_part_uuid: efi_part = _get_partition(device, uuid=efi_system_part_uuid) efi_partitions = [efi_part] if hardware.is_md_device(device): holders = hardware.get_holder_disks(device) efi_partitions = _prepare_boot_partitions_for_softraid( device, holders, efi_part, target_boot_mode) 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 if prep_boot_part_uuid: device = _get_partition(device, uuid=prep_boot_part_uuid) # If the root device is an md device (or partition), # identify the underlying holder disks to install grub. if hardware.is_md_device(device): disks = holders else: disks = [device] utils.execute('mount', root_partition, path) _mount_for_chroot(path) # 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: _append_uefi_to_fstab(path, efi_system_part_uuid) # 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(path): 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')): binary_name = "grub2" # Mount all vfat partitions listed in the fstab of the root partition. # This is to make sure grub2 finds all files it needs, as some of them # may not be inside the root partition but in the ESP (like grub2env). LOG.debug("Mounting all partitions inside the image ...") utils.execute('chroot %(path)s /bin/sh -c "mount -a -t vfat"' % {'path': path}, shell=True, env_variables={'PATH': path_variable}) 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 # 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) # 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) if efi_mounted: _append_uefi_to_fstab(path, efi_system_part_uuid) LOG.info("GRUB2 successfully installed on %s", device) except processutils.ProcessExecutionError as e: error_msg = ('Installing GRUB2 boot loader to device %(dev)s ' 'failed with %(err)s.' % { 'dev': device, 'err': e }) LOG.error(error_msg) 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" # If umount fails for efi partition, then we cannot be sure that all # the changes were written back to the filesystem. try: if efi_mounted: utils.execute('umount', efi_partition_mount_point, attempts=3, delay_on_retry=True) except processutils.ProcessExecutionError as e: error_msg = ('Umounting efi system partition failed. ' 'Attempted 3 times. Error: %s' % e) LOG.error(error_msg) 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): try: utils.execute('umount', path, attempts=3, delay_on_retry=True) except processutils.ProcessExecutionError as e: LOG.warning(umount_warn_msg, {'path': path, 'error': e}) else: # After everything is umounted we can then remove the # temporary directory shutil.rmtree(path)
def _install_grub2(device, root_uuid, efi_system_part_uuid=None, prep_boot_part_uuid=None): """Install GRUB2 bootloader on a given device.""" LOG.debug("Installing GRUB2 bootloader on device %s", device) efi_partition = None efi_partition_mount_point = None efi_mounted = False # NOTE(TheJulia): Seems we need to get this before ever possibly # restart the device in the case of multi-device RAID as pyudev # doesn't exactly like the partition disappearing. root_partition = _get_partition(device, uuid=root_uuid) # If the root device is an md device (or partition), restart the device # (to help grub finding it) and identify the underlying holder disks # to install grub. if hardware.is_md_device(device): hardware.md_restart(device) # If an md device, we need to rescan the devices anyway to pickup # the md device partition. _rescan_device(device) elif (_is_bootloader_loaded(device) and not (efi_system_part_uuid or prep_boot_part_uuid)): # We always need to put the bootloader in place with software raid # so it is okay to elif into the skip doing a bootloader step. LOG.info("Skipping installation of bootloader on device %s " "as it is already marked bootable.", device) return try: # Mount the partition and binds path = tempfile.mkdtemp() if efi_system_part_uuid: efi_partition = _get_partition(device, uuid=efi_system_part_uuid) efi_partition_mount_point = os.path.join(path, "boot/efi") # For power we want to install grub directly onto the PreP partition if prep_boot_part_uuid: device = _get_partition(device, uuid=prep_boot_part_uuid) # If the root device is an md device (or partition), # identify the underlying holder disks to install grub. if hardware.is_md_device(device): disks = hardware.get_holder_disks(device) else: 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') 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 binary_name = "grub" if os.path.exists(os.path.join(path, 'usr/sbin/grub2-install')): binary_name = "grub2" # 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' % 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) # 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}) # 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}) LOG.info("GRUB2 successfully installed on %s", device) except processutils.ProcessExecutionError as e: error_msg = ('Installing GRUB2 boot loader to device %(dev)s ' 'failed with %(err)s.' % {'dev': device, 'err': e}) LOG.error(error_msg) raise errors.CommandExecutionError(error_msg) finally: umount_warn_msg = "Unable to umount %(path)s. Error: %(error)s" # Umount binds and partition umount_binds_fail = False # If umount fails for efi partition, then we cannot be sure that all # the changes were written back to the filesystem. try: if efi_mounted: utils.execute('umount', efi_partition_mount_point, attempts=3, delay_on_retry=True) except processutils.ProcessExecutionError as e: error_msg = ('Umounting efi system partition failed. ' 'Attempted 3 times. Error: %s' % e) LOG.error(error_msg) raise errors.CommandExecutionError(error_msg) for fs in BIND_MOUNTS: try: utils.execute('umount', path + fs, attempts=3, delay_on_retry=True) except processutils.ProcessExecutionError as e: umount_binds_fail = True LOG.warning(umount_warn_msg, {'path': path + fs, 'error': e}) try: utils.execute('umount', path + '/sys', attempts=3, delay_on_retry=True) except processutils.ProcessExecutionError as e: umount_binds_fail = True LOG.warning(umount_warn_msg, {'path': path + '/sys', 'error': e}) # If umounting the binds succeed then we can try to delete it if not umount_binds_fail: try: utils.execute('umount', path, attempts=3, delay_on_retry=True) except processutils.ProcessExecutionError as e: LOG.warning(umount_warn_msg, {'path': path, 'error': e}) else: # After everything is umounted we can then remove the # temporary directory shutil.rmtree(path)
def _install_grub2(device, root_uuid, efi_system_part_uuid=None, prep_boot_part_uuid=None): """Install GRUB2 bootloader on a given device.""" LOG.debug("Installing GRUB2 bootloader on device %s", device) root_partition = _get_partition(device, uuid=root_uuid) efi_partition = None efi_partition_mount_point = None efi_mounted = False try: # Mount the partition and binds path = tempfile.mkdtemp() if efi_system_part_uuid: efi_partition = _get_partition(device, uuid=efi_system_part_uuid) efi_partition_mount_point = os.path.join(path, "boot/efi") # For power we want to install grub directly onto the PreP partition if prep_boot_part_uuid: device = _get_partition(device, uuid=prep_boot_part_uuid) # If the root device is an md device (or partition), restart the device # (to help grub finding it) and identify the underlying holder disks # to install grub. if hardware.is_md_device(device): hardware.md_restart(device) disks = hardware.get_holder_disks(device) else: 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') 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 binary_name = "grub" if os.path.exists(os.path.join(path, 'usr/sbin/grub2-install')): binary_name = "grub2" # 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' % 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) # 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. 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}) # 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}) LOG.info("GRUB2 successfully installed on %s", device) except processutils.ProcessExecutionError as e: error_msg = ('Installing GRUB2 boot loader to device %(dev)s ' 'failed with %(err)s.' % {'dev': device, 'err': e}) LOG.error(error_msg) raise errors.CommandExecutionError(error_msg) finally: umount_warn_msg = "Unable to umount %(path)s. Error: %(error)s" # Umount binds and partition umount_binds_fail = False # If umount fails for efi partition, then we cannot be sure that all # the changes were written back to the filesystem. try: if efi_mounted: utils.execute('umount', efi_partition_mount_point, attempts=3, delay_on_retry=True) except processutils.ProcessExecutionError as e: error_msg = ('Umounting efi system partition failed. ' 'Attempted 3 times. Error: %s' % e) LOG.error(error_msg) raise errors.CommandExecutionError(error_msg) for fs in BIND_MOUNTS: try: utils.execute('umount', path + fs, attempts=3, delay_on_retry=True) except processutils.ProcessExecutionError as e: umount_binds_fail = True LOG.warning(umount_warn_msg, {'path': path + fs, 'error': e}) try: utils.execute('umount', path + '/sys', attempts=3, delay_on_retry=True) except processutils.ProcessExecutionError as e: umount_binds_fail = True LOG.warning(umount_warn_msg, {'path': path + '/sys', 'error': e}) # If umounting the binds succeed then we can try to delete it if not umount_binds_fail: try: utils.execute('umount', path, attempts=3, delay_on_retry=True) except processutils.ProcessExecutionError as e: LOG.warning(umount_warn_msg, {'path': path, 'error': e}) else: # After everything is umounted we can then remove the # temporary directory shutil.rmtree(path)