def get_os_install_device(self):
        cached_node = get_cached_node()
        root_device_hints = None
        if cached_node is not None:
            root_device_hints = cached_node['properties'].get('root_device')

        block_devices = self.list_block_devices()
        if not root_device_hints:
            return utils.guess_root_disk(block_devices).name
        else:
            serialized_devs = [dev.serialize() for dev in block_devices]
            try:
                device = il_utils.match_root_device_hints(serialized_devs,
                                                          root_device_hints)
            except ValueError as e:
                # NOTE(lucasagomes): Just playing on the safe side
                # here, this exception should never be raised because
                # Ironic should validate the root device hints before the
                # deployment starts.
                raise errors.DeviceNotFound(
                    'No devices could be found using the root device hints '
                    '%(hints)s because they failed to validate. Error: '
                    '%(error)s' % {'hints': root_device_hints, 'error': e})

            if not device:
                raise errors.DeviceNotFound(
                    "No suitable device was found for "
                    "deployment using these hints %s" % root_device_hints)

            return device['name']
Beispiel #2
0
    def get_os_install_device(self):
        cached_node = get_cached_node()
        root_device_hints = None
        if cached_node is not None:
            root_device_hints = cached_node['properties'].get('root_device')

        block_devices = self.list_block_devices()
        if not root_device_hints:
            return utils.guess_root_disk(block_devices).name
        else:

            def match(hint, current_value, device):
                hint_value = root_device_hints[hint]
                if hint_value != current_value:
                    LOG.debug(
                        "Root device hint %(hint)s=%(value)s does not "
                        "match the device %(device)s value of "
                        "%(current)s", {
                            'hint': hint,
                            'value': hint_value,
                            'device': device,
                            'current': current_value
                        })
                    return False
                return True

            def check_device_attrs(device):
                for key in ('model', 'wwn', 'serial', 'vendor',
                            'wwn_with_extension', 'wwn_vendor_extension',
                            'name'):
                    if key not in root_device_hints:
                        continue

                    value = getattr(device, key)
                    if not value:
                        return False
                    value = utils.normalize(value)
                    if not match(key, value, device.name):
                        return False

                return True

            for dev in block_devices:
                # TODO(lucasagomes): Add support for operators <, >, =, etc...
                # to better deal with sizes.
                if 'size' in root_device_hints:
                    # Since we don't support units yet we expect the size
                    # in GiB for now
                    size = dev.size / units.Gi
                    if not match('size', size, dev.name):
                        continue

                if check_device_attrs(dev):
                    return dev.name

            else:
                raise errors.DeviceNotFound("No suitable device was found for "
                                            "deployment using these hints %s" %
                                            root_device_hints)
    def test_evaluate_hw_waits_for_disks(self, mocked_root_dev, mocked_sleep,
                                         mocked_block_dev):
        mocked_root_dev.side_effect = [errors.DeviceNotFound('boom'), None]

        result = self.hardware.evaluate_hardware_support()

        self.assertEqual(hardware.HardwareSupport.GENERIC, result)
        mocked_root_dev.assert_called_with(mocked_block_dev.return_value)
        self.assertEqual(2, mocked_root_dev.call_count)
        mocked_sleep.assert_called_once_with(hardware._DISK_WAIT_DELAY)
Beispiel #4
0
def _get_partition(device, uuid):
    """Find the partition of a given device."""
    LOG.debug("Find the partition %(uuid)s on device %(dev)s", {
        'dev': device,
        'uuid': uuid
    })

    try:
        # Try to tell the kernel to re-read the partition table
        try:
            utils.execute('partx',
                          '-u',
                          device,
                          attempts=3,
                          delay_on_retry=True)
            utils.execute('udevadm', 'settle')
        except processutils.ProcessExecutionError:
            LOG.warning("Couldn't re-read the partition table "
                        "on device %s", device)

        report = utils.execute('lsblk', '-PbioKNAME,UUID,TYPE', device)[0]
        for line in report.split('\n'):
            part = {}
            # Split into KEY=VAL pairs
            vals = shlex.split(line)
            for key, val in (v.split('=', 1) for v in vals):
                part[key] = val.strip()
            # Ignore non partition
            if part.get('TYPE') != 'part':
                continue

            if part.get('UUID') == uuid:
                LOG.debug("Partition %(uuid)s found on device "
                          "%(dev)s", {
                              'uuid': uuid,
                              'dev': device
                          })
                return '/dev/' + part.get('KNAME')
        else:
            error_msg = ("No partition with UUID %(uuid)s found on "
                         "device %(dev)s" % {
                             'uuid': uuid,
                             'dev': device
                         })
            LOG.error(error_msg)
            raise errors.DeviceNotFound(error_msg)
    except processutils.ProcessExecutionError as e:
        error_msg = ('Finding the partition with UUID %(uuid)s on '
                     'device %(dev)s failed with %(err)s' % {
                         'uuid': uuid,
                         'dev': device,
                         'err': e
                     })
        LOG.error(error_msg)
        raise errors.CommandExecutionError(error_msg)
    def test_evaluate_hw_disks_timeout(self, mocked_root_dev, mocked_sleep,
                                       mocked_block_dev):
        mocked_root_dev.side_effect = errors.DeviceNotFound('boom')

        result = self.hardware.evaluate_hardware_support()

        self.assertEqual(hardware.HardwareSupport.GENERIC, result)
        mocked_root_dev.assert_called_with(mocked_block_dev.return_value)
        self.assertEqual(hardware._DISK_WAIT_ATTEMPTS,
                         mocked_root_dev.call_count)
        mocked_sleep.assert_called_with(hardware._DISK_WAIT_DELAY)
def guess_root_disk(block_devices, min_size_required=4 * units.Gi):
    """Find suitable disk provided that root device hints are not given.

    If no hints are passed find the first device larger than min_size_required,
    assume it is the OS disk
    """
    # TODO(russellhaering): This isn't a valid assumption in
    # all cases, is there a more reasonable default behavior?
    block_devices.sort(key=lambda device: device.size)
    if not block_devices or block_devices[-1].size < min_size_required:
        raise errors.DeviceNotFound(
            "No suitable device was found "
            "for deployment - root device hints were not provided "
            "and all found block devices are smaller than %iB." %
            min_size_required)
    for device in block_devices:
        if device.size >= min_size_required:
            return device
Beispiel #7
0
def find_partition_with_path(path, device=None):
    """Find a partition with the given path.

    :param path: Expected path.
    :param device: Target device. If None, the root device is used.
    :returns: A context manager that will unmount and delete the temporary
        mount point on exit.
    """
    if device is None:
        device = hardware.dispatch_to_managers('get_os_install_device')
    partitions = disk_utils.list_partitions(device)
    # Make os.path.join work as expected
    lookup_path = path.lstrip('/')

    for part in partitions:
        if 'lvm' in part['flags']:
            LOG.debug('Skipping LVM partition %s', part)
            continue

        # TODO(dtantsur): switch to ironic-lib instead:
        # https://review.opendev.org/c/openstack/ironic-lib/+/774502
        part_template = '%s%s'
        if 'nvme' in device:
            part_template = '%sp%s'
        part_path = part_template % (device, part['number'])

        LOG.debug('Inspecting partition %s for path %s', part, path)
        try:
            with ironic_utils.mounted(part_path) as local_path:
                found_path = os.path.join(local_path, lookup_path)
                if not os.path.isdir(found_path):
                    continue

                LOG.info('Path %s has been found on partition %s', path, part)
                yield found_path
                return
        except processutils.ProcessExecutionError as exc:
            LOG.warning('Failure when inspecting partition %s: %s', part, exc)

    raise errors.DeviceNotFound(
        "No partition found with path %s, scanned: %s" % (path, partitions))
Beispiel #8
0
def guess_root_disk(block_devices, min_size_required=4 * units.Gi):
    """Find suitable disk provided that root device hints are not given.

    If no hints are passed, order the devices by size (primary key) and
    name (secondary key), and return the first device larger than
    min_size_required as the root disk.
    """
    # NOTE(arne_wiebalck): Order devices by size and name. Secondary
    # ordering by name is done to increase chances of successful
    # booting for BIOSes which try only one (the "first") disk.
    block_devices.sort(key=lambda device: (device.size, device.name))

    if not block_devices or block_devices[-1].size < min_size_required:
        raise errors.DeviceNotFound(
            "No suitable device was found "
            "for deployment - root device hints were not provided "
            "and all found block devices are smaller than %iB." %
            min_size_required)
    for device in block_devices:
        if device.size >= min_size_required:
            return device
Beispiel #9
0
def parse_root_device_hints():
    """Parse the root device hints.

    Parse the root device hints given by Ironic via kernel cmdline
    or vmedia.

    :returns: A dict with the hints or an empty dict if no hints are
              passed.
    :raises: DeviceNotFound if there are unsupported hints.

    """
    root_device = get_agent_params().get('root_device')
    if not root_device:
        return {}

    hints = dict((item.split('=') for item in root_device.split(',')))

    # Find invalid hints for logging
    not_supported = set(hints) - SUPPORTED_ROOT_DEVICE_HINTS
    if not_supported:
        error_msg = ('No device can be found because the following hints: '
                     '"%(not_supported)s" are not supported by this version '
                     'of IPA. Supported hints are: "%(supported)s"', {
                         'not_supported': ', '.join(not_supported),
                         'supported': ', '.join(SUPPORTED_ROOT_DEVICE_HINTS)
                     })
        raise errors.DeviceNotFound(error_msg)

    # Normalise the values
    hints = {k: normalize(v) for k, v in hints.items()}

    if 'size' in hints:
        # NOTE(lucasagomes): Ironic should validate before passing to
        # the deploy ramdisk
        hints['size'] = int(hints['size'])

    return hints
Beispiel #10
0
def _get_partition(device, uuid):
    """Find the partition of a given device."""
    LOG.debug("Find the partition %(uuid)s on device %(dev)s",
              {'dev': device, 'uuid': uuid})

    try:
        _rescan_device(device)

        # If the deploy device is an md device, we want to install on
        # the first partition. We clearly take a shortcut here for now.
        # TODO(arne_wiebalck): Would it possible to use the partition
        #                      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)):
                error_msg = ("Could not find partition %(part)s on md "
                             "device %(dev)s" % {'part': md_partition,
                                                 'dev': device})
                LOG.error(error_msg)
                raise errors.DeviceNotFound(error_msg)
            LOG.debug("Found md device with partition %s", md_partition)
            return md_partition

        lsblk = utils.execute('lsblk', '-PbioKNAME,UUID,PARTUUID,TYPE', device)
        report = lsblk[0]
        for line in report.split('\n'):
            part = {}
            # Split into KEY=VAL pairs
            vals = shlex.split(line)
            for key, val in (v.split('=', 1) for v in vals):
                part[key] = val.strip()
            # Ignore non partition
            if part.get('TYPE') != 'part':
                # NOTE(TheJulia): This techincally creates an edge failure
                # case where a filesystem on a whole block device sans
                # partitioning would behave differently.
                continue

            if part.get('UUID') == uuid:
                LOG.debug("Partition %(uuid)s found on device "
                          "%(dev)s", {'uuid': uuid, 'dev': device})
                return '/dev/' + part.get('KNAME')
            if part.get('PARTUUID') == uuid:
                LOG.debug("Partition %(uuid)s found on device "
                          "%(dev)s", {'uuid': uuid, 'dev': device})
                return '/dev/' + part.get('KNAME')
        else:
            # NOTE(TheJulia): We may want to consider moving towards using
            # findfs in the future, if we're comfortable with the execution
            # and interaction. There is value in either way though.
            try:
                findfs, stderr = utils.execute('findfs', 'UUID=%s' % uuid)
                return findfs.strip()
            except processutils.ProcessExecutionError as e:
                LOG.debug('First fallback detection attempt for locating '
                          'partition via UUID %(uuid)s failed. '
                          'Error: %(err)s',
                          {'uuid': uuid,
                           'err': e})
                try:
                    findfs, stderr = utils.execute(
                        'findfs', 'PARTUUID=%s' % uuid)
                    return findfs.strip()
                except processutils.ProcessExecutionError as e:
                    LOG.debug('Secondary fallback detection attempt for '
                              'locating partition via UUID %(uuid)s failed. '
                              'Error: %(err)s',
                              {'uuid': uuid,
                               'err': e})
            error_msg = ("No partition with UUID %(uuid)s found on "
                         "device %(dev)s" % {'uuid': uuid, 'dev': device})
            LOG.error(error_msg)
            raise errors.DeviceNotFound(error_msg)
    except processutils.ProcessExecutionError as e:
        error_msg = ('Finding the partition with UUID %(uuid)s on '
                     'device %(dev)s failed with %(err)s' %
                     {'uuid': uuid, 'dev': device, 'err': e})
        LOG.error(error_msg)
        raise errors.CommandExecutionError(error_msg)
Beispiel #11
0
def _get_partition(device, uuid):
    """Find the partition of a given device."""
    LOG.debug("Find the partition %(uuid)s on device %(dev)s", {
        'dev': device,
        'uuid': uuid
    })

    try:
        _rescan_device(device)
        lsblk = utils.execute('lsblk', '-PbioKNAME,UUID,PARTUUID,TYPE', device)
        report = lsblk[0]
        for line in report.split('\n'):
            part = {}
            # Split into KEY=VAL pairs
            vals = shlex.split(line)
            for key, val in (v.split('=', 1) for v in vals):
                part[key] = val.strip()
            # Ignore non partition
            if part.get('TYPE') not in ['md', 'part']:
                # NOTE(TheJulia): This technically creates an edge failure
                # case where a filesystem on a whole block device sans
                # partitioning would behave differently.
                continue

            if part.get('UUID') == uuid:
                LOG.debug("Partition %(uuid)s found on device "
                          "%(dev)s", {
                              'uuid': uuid,
                              'dev': device
                          })
                return '/dev/' + part.get('KNAME')
            if part.get('PARTUUID') == uuid:
                LOG.debug("Partition %(uuid)s found on device "
                          "%(dev)s", {
                              'uuid': uuid,
                              'dev': device
                          })
                return '/dev/' + part.get('KNAME')
        else:
            # NOTE(TheJulia): We may want to consider moving towards using
            # findfs in the future, if we're comfortable with the execution
            # and interaction. There is value in either way though.
            # NOTE(rg): alternative: blkid -l -t UUID=/PARTUUID=
            try:
                findfs, stderr = utils.execute('findfs', 'UUID=%s' % uuid)
                return findfs.strip()
            except processutils.ProcessExecutionError as e:
                LOG.debug(
                    'First fallback detection attempt for locating '
                    'partition via UUID %(uuid)s failed. '
                    'Error: %(err)s', {
                        'uuid': uuid,
                        'err': e
                    })
                try:
                    findfs, stderr = utils.execute('findfs',
                                                   'PARTUUID=%s' % uuid)
                    return findfs.strip()
                except processutils.ProcessExecutionError as e:
                    LOG.debug(
                        'Secondary fallback detection attempt for '
                        'locating partition via UUID %(uuid)s failed. '
                        'Error: %(err)s', {
                            'uuid': uuid,
                            'err': e
                        })

            # Last fallback: In case we cannot find the partition by UUID
            # and the deploy device is an md device, we check if the md
            # device has a partition (which we assume to contain the root fs).
            if hardware.is_md_device(device):
                md_partition = device + 'p1'
                if (os.path.exists(md_partition)
                        and stat.S_ISBLK(os.stat(md_partition).st_mode)):
                    LOG.debug("Found md device with partition %s",
                              md_partition)
                    return md_partition
                else:
                    LOG.debug(
                        'Could not find partition %(part)s on md '
                        'device %(dev)s', {
                            'part': md_partition,
                            'dev': device
                        })

            # Partition not found, time to escalate.
            error_msg = ("No partition with UUID %(uuid)s found on "
                         "device %(dev)s" % {
                             'uuid': uuid,
                             'dev': device
                         })
            LOG.error(error_msg)
            raise errors.DeviceNotFound(error_msg)
    except processutils.ProcessExecutionError as e:
        error_msg = ('Finding the partition with UUID %(uuid)s on '
                     'device %(dev)s failed with %(err)s' % {
                         'uuid': uuid,
                         'dev': device,
                         'err': e
                     })
        LOG.error(error_msg)
        raise errors.CommandExecutionError(error_msg)
Beispiel #12
0
    def get_os_install_device(self):
        cached_node = get_cached_node()
        root_device_hints = None
        if cached_node is not None:
            root_device_hints = cached_node['properties'].get('root_device')

        block_devices = self.list_block_devices()
        if not root_device_hints:
            return utils.guess_root_disk(block_devices).name
        else:

            def match(hint, current_value, device):
                hint_value = root_device_hints[hint]

                if hint == 'rotational':
                    hint_value = strutils.bool_from_string(hint_value)

                elif hint == 'size':
                    try:
                        hint_value = int(hint_value)
                    except (ValueError, TypeError):
                        LOG.warning(
                            'Root device hint "size" is not an integer. '
                            'Current value: "%(value)s"; and type: "%(type)s"',
                            {
                                'value': hint_value,
                                'type': type(hint_value)
                            })
                        return False

                if hint_value != current_value:
                    LOG.debug(
                        "Root device hint %(hint)s=%(value)s does not "
                        "match the device %(device)s value of "
                        "%(current)s", {
                            'hint': hint,
                            'value': hint_value,
                            'device': device,
                            'current': current_value
                        })
                    return False
                return True

            def check_device_attrs(device):
                for key in ('model', 'wwn', 'serial', 'vendor',
                            'wwn_with_extension', 'wwn_vendor_extension',
                            'name', 'rotational', 'size'):
                    if key not in root_device_hints:
                        continue

                    value = getattr(device, key)
                    if value is None:
                        return False

                    if isinstance(value, six.string_types):
                        value = utils.normalize(value)

                    if key == 'size':
                        # Since we don't support units yet we expect the size
                        # in GiB for now
                        value = value / units.Gi

                    if not match(key, value, device.name):
                        return False

                return True

            for dev in block_devices:
                if check_device_attrs(dev):
                    return dev.name

            else:
                raise errors.DeviceNotFound("No suitable device was found for "
                                            "deployment using these hints %s" %
                                            root_device_hints)
Beispiel #13
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)
    def get_os_install_device(self):
        block_devices = self.list_block_devices()
        root_device_hints = utils.parse_root_device_hints()

        if not root_device_hints:
            # If no hints are passed find the first device larger than
            # 4GB, assume it is the OS disk
            # TODO(russellhaering): This isn't a valid assumption in
            # all cases, is there a more reasonable default behavior?
            block_devices.sort(key=lambda device: device.size)
            for device in block_devices:
                if device.size >= (4 * pow(1024, 3)):
                    return device.name
        else:

            def match(hint, current_value, device):
                hint_value = root_device_hints[hint]
                if hint_value != current_value:
                    LOG.debug(
                        "Root device hint %(hint)s=%(value)s does not "
                        "match the device %(device)s value of "
                        "%(current)s", {
                            'hint': hint,
                            'value': hint_value,
                            'device': device,
                            'current': current_value
                        })
                    return False
                return True

            context = pyudev.Context()
            for dev in block_devices:
                try:
                    udev = pyudev.Device.from_device_file(context, dev.name)
                except (ValueError, EnvironmentError) as e:
                    LOG.warning(
                        "Device %(dev)s is inaccessible, skipping... "
                        "Error: %(error)s", {
                            'dev': dev,
                            'error': e
                        })
                    continue

                # TODO(lucasagomes): Add support for operators <, >, =, etc...
                # to better deal with sizes.
                if 'size' in root_device_hints:
                    # Since we don't support units yet we expect the size
                    # in GiB for now
                    size = dev.size / units.Gi
                    if not match('size', size, dev.name):
                        continue

                if 'model' in root_device_hints:
                    model = udev.get('ID_MODEL', None)
                    if not model:
                        continue
                    model = utils.normalize(model)
                    if not match('model', model, dev.name):
                        continue

                if 'wwn' in root_device_hints:
                    wwn = udev.get('ID_WWN', None)
                    if not wwn:
                        continue
                    wwn = utils.normalize(wwn)
                    if not match('wwn', wwn, dev.name):
                        continue

                if 'serial' in root_device_hints:
                    # TODO(lucasagomes): Since lsblk only supports
                    # returning the short serial we are using
                    # ID_SERIAL_SHORT here to keep compatibility with the
                    # bash deploy ramdisk
                    serial = udev.get('ID_SERIAL_SHORT', None)
                    if not serial:
                        continue
                    serial = utils.normalize(serial)
                    if not match('serial', serial, dev.name):
                        continue

                if 'vendor' in root_device_hints:
                    vendor = self._get_device_vendor(dev.name)
                    if not vendor:
                        continue
                    vendor = utils.normalize(vendor)
                    if not match('vendor', vendor, dev.name):
                        continue

                return dev.name

            else:
                raise errors.DeviceNotFound("No suitable device was found for "
                                            "deployment using these hints %s" %
                                            root_device_hints)
Beispiel #15
0
def _get_partition(device, uuid):
    """Find the partition of a given device."""
    LOG.debug("Find the partition %(uuid)s on device %(dev)s",
              {'dev': device, 'uuid': uuid})

    try:
        # Try to tell the kernel to re-read the partition table
        try:
            utils.execute('partx', '-u', device, attempts=3,
                          delay_on_retry=True)
            utils.execute('udevadm', 'settle')
        except processutils.ProcessExecutionError:
            LOG.warning("Couldn't re-read the partition table "
                        "on device %s", device)

        # If the deploy device is an md device, we want to install on
        # the first partition. We clearly take a shortcut here for now.
        # TODO(arne_wiebalck): Would it possible to use the partition
        #                      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)):
                error_msg = ("Could not find partition %(part)s on md "
                             "device %(dev)s" % {'part': md_partition,
                                                 'dev': device})
                LOG.error(error_msg)
                raise errors.DeviceNotFound(error_msg)
            LOG.debug("Found md device with partition %s", md_partition)
            return md_partition

        lsblk = utils.execute('lsblk', '-PbioKNAME,UUID,PARTUUID,TYPE', device)
        report = lsblk[0]
        for line in report.split('\n'):
            part = {}
            # Split into KEY=VAL pairs
            vals = shlex.split(line)
            for key, val in (v.split('=', 1) for v in vals):
                part[key] = val.strip()
            # Ignore non partition
            if part.get('TYPE') != 'part':
                continue

            if part.get('UUID') == uuid:
                LOG.debug("Partition %(uuid)s found on device "
                          "%(dev)s", {'uuid': uuid, 'dev': device})
                return '/dev/' + part.get('KNAME')
            if part.get('PARTUUID') == uuid:
                LOG.debug("Partition %(uuid)s found on device "
                          "%(dev)s", {'uuid': uuid, 'dev': device})
                return '/dev/' + part.get('KNAME')
        else:
            error_msg = ("No partition with UUID %(uuid)s found on "
                         "device %(dev)s" % {'uuid': uuid, 'dev': device})
            LOG.error(error_msg)
            raise errors.DeviceNotFound(error_msg)
    except processutils.ProcessExecutionError as e:
        error_msg = ('Finding the partition with UUID %(uuid)s on '
                     'device %(dev)s failed with %(err)s' %
                     {'uuid': uuid, 'dev': device, 'err': e})
        LOG.error(error_msg)
        raise errors.CommandExecutionError(error_msg)