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)
Exemple #2
0
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)
Exemple #3
0
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)