Ejemplo n.º 1
0
 def test_get_efi_part_on_device_without_fs(self, mocked_execute):
     parted_ret = PARTED_OUTPUT_UNFORMATTED_NOFS.format('gpt')
     mocked_execute.side_effect = [(parted_ret, None)]
     ret = utils.get_efi_part_on_device('/dev/sda')
     mocked_execute.assert_has_calls(
         [mock.call('parted', '-s', '/dev/sda', '--', 'print')])
     self.assertEqual('1', ret)
Ejemplo n.º 2
0
 def test_get_efi_part_on_device(self, mocked_type, mocked_parts):
     mocked_parts.return_value = [
         {'number': '1', 'flags': ''},
         {'number': '14', 'flags': 'bios_grub'},
         {'number': '15', 'flags': 'esp, boot'},
     ]
     ret = utils.get_efi_part_on_device('/dev/sda')
     self.assertEqual('15', ret)
Ejemplo n.º 3
0
 def test_get_efi_part_on_device_only_boot_flag_mbr(self, mocked_type,
                                                    mocked_parts):
     mocked_type.return_value = 'msdos'
     mocked_parts.return_value = [
         {'number': '1', 'flags': ''},
         {'number': '14', 'flags': 'bios_grub'},
         {'number': '15', 'flags': 'boot'},
     ]
     self.assertIsNone(utils.get_efi_part_on_device('/dev/sda'))
Ejemplo n.º 4
0
 def test_get_efi_part_on_device_not_found(self, mocked_type, mocked_parts):
     mocked_parts.return_value = [
         {
             'number': '1',
             'flags': ''
         },
         {
             'number': '14',
             'flags': 'bios_grub'
         },
     ]
     self.assertIsNone(utils.get_efi_part_on_device('/dev/sda'))
Ejemplo n.º 5
0
def _manage_uefi(device, efi_system_part_uuid=None):
    """Manage the device looking for valid efi bootloaders to update the nvram.

    This method checks for valid efi bootloaders in the device, if they exists
    it updates the nvram using the efibootmgr.

    :param device: the device to be checked.
    :param efi_system_part_uuid: efi partition uuid.
    :return: True - if it founds any efi bootloader and the nvram was updated
             using the efibootmgr.
             False - if no efi bootloader is found.
    """
    efi_partition = None
    efi_partition_mount_point = None
    efi_mounted = False

    try:
        # Force UEFI to rescan the device. Required if the deployment
        # was over iscsi.
        _rescan_device(device)

        local_path = tempfile.mkdtemp()
        # Trust the contents on the disk in the event of a whole disk image.
        efi_partition = utils.get_efi_part_on_device(device)
        if not efi_partition:
            # _get_partition returns <device>+<partition> and we only need the
            # partition number
            partition = _get_partition(device, uuid=efi_system_part_uuid)
            efi_partition = int(partition.replace(device, ""))

        if efi_partition:
            efi_partition_mount_point = os.path.join(local_path, "boot/efi")
            if not os.path.exists(efi_partition_mount_point):
                os.makedirs(efi_partition_mount_point)

            # The mount needs the device with the partition, in case the
            # device ends with a digit we add a `p` and the partition number we
            # found, otherwise we just join the device and the partition number
            if device[-1].isdigit():
                efi_device_part = '{}p{}'.format(device, efi_partition)
                utils.execute('mount', efi_device_part,
                              efi_partition_mount_point)
            else:
                efi_device_part = '{}{}'.format(device, efi_partition)
                utils.execute('mount', efi_device_part,
                              efi_partition_mount_point)
            efi_mounted = True
        else:
            # If we can't find the partition we need to decide what should
            # happen
            return False
        valid_efi_bootloaders = _get_efi_bootloaders(efi_partition_mount_point)
        if valid_efi_bootloaders:
            _run_efibootmgr(valid_efi_bootloaders, device, efi_partition)
            return True
        else:
            return False

    except processutils.ProcessExecutionError as e:
        error_msg = ('Could not verify uefi on 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 %(local_path)s. Error: %(error)s"

        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)

        else:
            # If umounting the binds succeed then we can try to delete it
            try:
                utils.execute('sync')
            except processutils.ProcessExecutionError as e:
                LOG.warning(umount_warn_msg, {'path': local_path, 'error': e})
            else:
                # After everything is umounted we can then remove the
                # temporary directory
                shutil.rmtree(local_path)
Ejemplo n.º 6
0
def _prepare_boot_partitions_for_softraid(device, holders, efi_part,
                                          target_boot_mode):
    """Prepare boot partitions when relevant.

    Create either efi partitions or bios boot partitions for softraid,
    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 efi partition paths on softraid disk holders when target
     boot mode is uefi, empty list otherwise.
    """
    efi_partitions = []

    # 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 = utils.get_efi_part_on_device(device)
            if efi_part:
                efi_part = '{}p{}'.format(device, efi_part)

        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-'
        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]

            LOG.debug("EFI partition %s created on holder disk %s",
                      target_part, holder)

            if efi_part:
                LOG.debug("Relocating EFI %s to holder part %s", efi_part,
                          target_part)
                # Blockdev copy
                utils.execute("cp", efi_part, target_part)
            else:
                # Creating a label is just to make life easier
                if number == 0:
                    fslabel = 'efi-part'
                else:
                    # bak, label is limited to 11 chars
                    fslabel = 'efi-part-b'
                ilib_utils.mkfs(fs='vfat', path=target_part, label=fslabel)
            efi_partitions.append(target_part)
            # TBD: Would not hurt to destroy source efi part when defined,
            # for clarity.

    elif target_boot_mode == 'bios':
        partlabel_prefix = 'bios-boot-part-'
        for number, holder in enumerate(holders):
            label = utils.scan_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.

    # Just an empty list if not uefi boot mode, nvm, not used anyway
    return efi_partitions
Ejemplo n.º 7
0
 def test_get_efi_part_on_device_not_found(self, mocked_execute):
     mocked_execute.side_effect = [(PARTED_OUTPUT_NO_EFI, None)]
     self.assertIsNone(utils.get_efi_part_on_device('/dev/sda'))
     mocked_execute.assert_has_calls(
         [mock.call('parted', '-s', '/dev/sda', '--', 'print')])
Ejemplo n.º 8
0
def _manage_uefi(device, efi_system_part_uuid=None):
    """Manage the device looking for valid efi bootloaders to update the nvram.

    This method checks for valid efi bootloaders in the device, if they exists
    it updates the nvram using the efibootmgr.

    :param device: the device to be checked.
    :param efi_system_part_uuid: efi partition uuid.
    :raises: DeviceNotFound if the efi partition cannot be found.
    :return: True - if it founds any efi bootloader and the nvram was updated
             using the efibootmgr.
             False - if no efi bootloader is found.
    """
    efi_partition_mount_point = None
    efi_mounted = False

    try:
        # Force UEFI to rescan the device. Required if the deployment
        # was over iscsi.
        _rescan_device(device)

        local_path = tempfile.mkdtemp()
        # Trust the contents on the disk in the event of a whole disk image.
        efi_partition = utils.get_efi_part_on_device(device)
        if not efi_partition and efi_system_part_uuid:
            # _get_partition returns <device>+<partition> and we only need the
            # partition number
            partition = _get_partition(device, uuid=efi_system_part_uuid)
            efi_partition = int(partition.replace(device, ""))

        if not efi_partition:
            # NOTE(dtantsur): we cannot have a valid EFI deployment without an
            # EFI partition at all. This code path is easily hit when using an
            # image that is not UEFI compatible (which sadly applies to most
            # cloud images out there, with a nice exception of Ubuntu).
            raise errors.DeviceNotFound(
                "No EFI partition could be detected on device %s and "
                "EFI partition UUID has not been recorded during deployment "
                "(which is often the case for whole disk images). "
                "Are you using a UEFI-compatible image?" % device)

        efi_partition_mount_point = os.path.join(local_path, "boot/efi")
        if not os.path.exists(efi_partition_mount_point):
            os.makedirs(efi_partition_mount_point)

        # The mount needs the device with the partition, in case the
        # device ends with a digit we add a `p` and the partition number we
        # found, otherwise we just join the device and the partition number
        if device[-1].isdigit():
            efi_device_part = '{}p{}'.format(device, efi_partition)
            utils.execute('mount', efi_device_part, efi_partition_mount_point)
        else:
            efi_device_part = '{}{}'.format(device, efi_partition)
            utils.execute('mount', efi_device_part, efi_partition_mount_point)
        efi_mounted = True

        valid_efi_bootloaders = _get_efi_bootloaders(efi_partition_mount_point)
        if valid_efi_bootloaders:
            _run_efibootmgr(valid_efi_bootloaders, device, efi_partition)
            return True
        else:
            # NOTE(dtantsur): if we have an empty EFI partition, try to use
            # grub-install to populate it.
            return False

    except processutils.ProcessExecutionError as e:
        error_msg = ('Could not verify uefi on device %(dev)s'
                     'failed with %(err)s.' % {
                         'dev': device,
                         'err': e
                     })
        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:
            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)

        else:
            # If umounting the binds succeed then we can try to delete it
            try:
                utils.execute('sync')
            except processutils.ProcessExecutionError as e:
                LOG.warning(umount_warn_msg, {'path': local_path, 'error': e})
            else:
                # After everything is umounted we can then remove the
                # temporary directory
                shutil.rmtree(local_path)