def destroy_disk_metadata(dev, node_uuid):
    """Destroy metadata structures on node's disk.

    Ensure that node's disk magic strings are wiped without zeroing the
    entire drive. To do this we use the wipefs tool from util-linux.

    :param dev: Path for the device to work on.
    :param node_uuid: Node's uuid. Used for logging.
    """
    # NOTE(NobodyCam): This is needed to work around bug:
    # https://bugs.launchpad.net/ironic/+bug/1317647
    LOG.debug("Start destroy disk metadata for node %(node)s.",
              {'node': node_uuid})
    try:
        utils.execute('wipefs', '--force', '--all', dev,
                      run_as_root=True,
                      use_standard_locale=True)
    except processutils.ProcessExecutionError as e:
        # NOTE(zhenguo): Check if --force option is supported for wipefs,
        # if not, we should try without it.
        if '--force' in str(e):
            utils.execute('wipefs', '--all', dev,
                          run_as_root=True,
                          use_standard_locale=True)
        else:
            raise e
    LOG.info(_LI("Disk metadata on %(dev)s successfully destroyed for node "
                 "%(node)s"), {'dev': dev, 'node': node_uuid})
def destroy_disk_metadata(dev, node_uuid):
    """Destroy metadata structures on node's disk.

       Ensure that node's disk appears to be blank without zeroing the entire
       drive. To do this we will zero the first 18KiB to clear MBR / GPT data
       and the last 18KiB to clear GPT and other metadata like LVM, veritas,
       MDADM, DMRAID, etc.
    """
    # NOTE(NobodyCam): This is needed to work around bug:
    # https://bugs.launchpad.net/ironic/+bug/1317647
    LOG.debug("Start destroy disk metadata for node %(node)s.",
              {'node': node_uuid})
    try:
        utils.dd('/dev/zero', dev, 'bs=512', 'count=36')
    except processutils.ProcessExecutionError as err:
        with excutils.save_and_reraise_exception():
            LOG.error(
                _LE("Failed to erase beginning of disk for node "
                    "%(node)s. Command: %(command)s. Error: %(error)s."), {
                        'node': node_uuid,
                        'command': err.cmd,
                        'error': err.stderr
                    })

    # now wipe the end of the disk.
    # get end of disk seek value
    try:
        block_sz = get_dev_block_size(dev)
    except processutils.ProcessExecutionError as err:
        with excutils.save_and_reraise_exception():
            LOG.error(
                _LE("Failed to get disk block count for node %(node)s. "
                    "Command: %(command)s. Error: %(error)s."), {
                        'node': node_uuid,
                        'command': err.cmd,
                        'error': err.stderr
                    })
    else:
        seek_value = block_sz - 36
        try:
            utils.dd('/dev/zero', dev, 'bs=512', 'count=36',
                     'seek=%d' % seek_value)
        except processutils.ProcessExecutionError as err:
            with excutils.save_and_reraise_exception():
                LOG.error(
                    _LE("Failed to erase the end of the disk on node "
                        "%(node)s. Command: %(command)s. "
                        "Error: %(error)s."), {
                            'node': node_uuid,
                            'command': err.cmd,
                            'error': err.stderr
                        })
    LOG.info(
        _LI("Disk metadata on %(dev)s successfully destroyed for node "
            "%(node)s"), {
                'dev': dev,
                'node': node_uuid
            })
def destroy_disk_metadata(dev, node_uuid):
    """Destroy metadata structures on node's disk.

       Ensure that node's disk appears to be blank without zeroing the entire
       drive. To do this we will zero the first 18KiB to clear MBR / GPT data
       and the last 18KiB to clear GPT and other metadata like LVM, veritas,
       MDADM, DMRAID, etc.
    """
    # NOTE(NobodyCam): This is needed to work around bug:
    # https://bugs.launchpad.net/ironic/+bug/1317647
    LOG.debug("Start destroy disk metadata for node %(node)s.",
              {'node': node_uuid})
    try:
        utils.dd('/dev/zero', dev, 'bs=512', 'count=36')
    except processutils.ProcessExecutionError as err:
        with excutils.save_and_reraise_exception():
            LOG.error(_LE("Failed to erase beginning of disk for node "
                          "%(node)s. Command: %(command)s. Error: %(error)s."),
                      {'node': node_uuid,
                       'command': err.cmd,
                       'error': err.stderr})

    # now wipe the end of the disk.
    # get end of disk seek value
    try:
        block_sz = get_dev_block_size(dev)
    except processutils.ProcessExecutionError as err:
        with excutils.save_and_reraise_exception():
            LOG.error(_LE("Failed to get disk block count for node %(node)s. "
                          "Command: %(command)s. Error: %(error)s."),
                      {'node': node_uuid,
                       'command': err.cmd,
                       'error': err.stderr})
    else:
        seek_value = block_sz - 36
        try:
            utils.dd('/dev/zero', dev, 'bs=512', 'count=36',
                     'seek=%d' % seek_value)
        except processutils.ProcessExecutionError as err:
            with excutils.save_and_reraise_exception():
                LOG.error(_LE("Failed to erase the end of the disk on node "
                              "%(node)s. Command: %(command)s. "
                              "Error: %(error)s."),
                          {'node': node_uuid,
                           'command': err.cmd,
                           'error': err.stderr})
    LOG.info(_LI("Disk metadata on %(dev)s successfully destroyed for node "
                 "%(node)s"), {'dev': dev, 'node': node_uuid})
def destroy_disk_metadata(dev, node_uuid):
    """Destroy metadata structures on node's disk.

    Ensure that node's disk magic strings are wiped without zeroing the
    entire drive. To do this we use the wipefs tool from util-linux.

    :param dev: Path for the device to work on.
    :param node_uuid: Node's uuid. Used for logging.
    """
    # NOTE(NobodyCam): This is needed to work around bug:
    # https://bugs.launchpad.net/ironic/+bug/1317647
    LOG.debug("Start destroy disk metadata for node %(node)s.",
              {'node': node_uuid})
    try:
        utils.execute('wipefs',
                      '--force',
                      '--all',
                      dev,
                      run_as_root=True,
                      use_standard_locale=True)
    except processutils.ProcessExecutionError as e:
        with excutils.save_and_reraise_exception() as ctxt:
            # NOTE(zhenguo): Check if --force option is supported for wipefs,
            # if not, we should try without it.
            if '--force' in str(e):
                ctxt.reraise = False
                utils.execute('wipefs',
                              '--all',
                              dev,
                              run_as_root=True,
                              use_standard_locale=True)

    LOG.info(
        _LI("Disk metadata on %(dev)s successfully destroyed for node "
            "%(node)s"), {
                'dev': dev,
                'node': node_uuid
            })
def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format,
                 image_path, node_uuid, preserve_ephemeral=False,
                 configdrive=None, boot_option="netboot", boot_mode="bios",
                 tempdir=None, disk_label=None):
    """Create partitions and copy an image to the root partition.

    :param dev: Path for the device to work on.
    :param root_mb: Size of the root partition in megabytes.
    :param swap_mb: Size of the swap partition in megabytes.
    :param ephemeral_mb: Size of the ephemeral partition in megabytes. If 0,
        no ephemeral partition will be created.
    :param ephemeral_format: The type of file system to format the ephemeral
        partition.
    :param image_path: Path for the instance's disk image.
    :param node_uuid: node's uuid. Used for logging.
    :param preserve_ephemeral: If True, no filesystem is written to the
        ephemeral block device, preserving whatever content it had (if the
        partition table has not changed).
    :param configdrive: Optional. Base64 encoded Gzipped configdrive content
                        or configdrive HTTP URL.
    :param boot_option: Can be "local" or "netboot". "netboot" by default.
    :param boot_mode: Can be "bios" or "uefi". "bios" by default.
    :param tempdir: A temporary directory
    :param disk_label: The disk label to be used when creating the
        partition table. Valid values are: "msdos", "gpt" or None; If None
        Ironic will figure it out according to the boot_mode parameter.
    :returns: a dictionary containing the following keys:
        'root uuid': UUID of root partition
        'efi system partition uuid': UUID of the uefi system partition
                                     (if boot mode is uefi).
        NOTE: If key exists but value is None, it means partition doesn't
              exist.
    """
    # the only way for preserve_ephemeral to be set to true is if we are
    # rebuilding an instance with --preserve_ephemeral.
    commit = not preserve_ephemeral
    # now if we are committing the changes to disk clean first.
    if commit:
        destroy_disk_metadata(dev, node_uuid)

    try:
        # If requested, get the configdrive file and determine the size
        # of the configdrive partition
        configdrive_mb = 0
        configdrive_file = None
        if configdrive:
            configdrive_mb, configdrive_file = _get_configdrive(
                configdrive, node_uuid, tempdir=tempdir)

        part_dict = make_partitions(dev, root_mb, swap_mb, ephemeral_mb,
                                    configdrive_mb, node_uuid,
                                    commit=commit,
                                    boot_option=boot_option,
                                    boot_mode=boot_mode,
                                    disk_label=disk_label)
        LOG.info(_LI("Successfully completed the disk device"
                     " %(dev)s partitioning for node %(node)s"),
                 {'dev': dev, "node": node_uuid})

        ephemeral_part = part_dict.get('ephemeral')
        swap_part = part_dict.get('swap')
        configdrive_part = part_dict.get('configdrive')
        root_part = part_dict.get('root')

        if not is_block_device(root_part):
            raise exception.InstanceDeployFailure(
                _("Root device '%s' not found") % root_part)

        for part in ('swap', 'ephemeral', 'configdrive',
                     'efi system partition'):
            part_device = part_dict.get(part)
            LOG.debug("Checking for %(part)s device (%(dev)s) on node "
                      "%(node)s.", {'part': part, 'dev': part_device,
                                    'node': node_uuid})
            if part_device and not is_block_device(part_device):
                raise exception.InstanceDeployFailure(
                    _("'%(partition)s' device '%(part_device)s' not found") %
                    {'partition': part, 'part_device': part_device})

        # If it's a uefi localboot, then we have created the efi system
        # partition.  Create a fat filesystem on it.
        if boot_mode == "uefi" and boot_option == "local":
            efi_system_part = part_dict.get('efi system partition')
            mkfs(dev=efi_system_part, fs='vfat', label='efi-part')

        if configdrive_part:
            # Copy the configdrive content to the configdrive partition
            dd(configdrive_file, configdrive_part)
            LOG.info(_LI("Configdrive for node %(node)s successfully copied "
                         "onto partition %(partition)s"),
                     {'node': node_uuid, 'partition': configdrive_part})

    finally:
        # If the configdrive was requested make sure we delete the file
        # after copying the content to the partition
        if configdrive_file:
            utils.unlink_without_raise(configdrive_file)

    populate_image(image_path, root_part)
    LOG.info(_LI("Image for %(node)s successfully populated"),
             {'node': node_uuid})

    if swap_part:
        mkfs(dev=swap_part, fs='swap', label='swap1')
        LOG.info(_LI("Swap partition %(swap)s successfully formatted "
                     "for node %(node)s"),
                 {'swap': swap_part, 'node': node_uuid})

    if ephemeral_part and not preserve_ephemeral:
        mkfs(dev=ephemeral_part, fs=ephemeral_format, label="ephemeral0")
        LOG.info(_LI("Ephemeral partition %(ephemeral)s successfully "
                     "formatted for node %(node)s"),
                 {'ephemeral': ephemeral_part, 'node': node_uuid})

    uuids_to_return = {
        'root uuid': root_part,
        'efi system partition uuid': part_dict.get('efi system partition')
    }

    try:
        for part, part_dev in uuids_to_return.items():
            if part_dev:
                uuids_to_return[part] = block_uuid(part_dev)

    except processutils.ProcessExecutionError:
        with excutils.save_and_reraise_exception():
            LOG.error(_LE("Failed to detect %s"), part)

    return uuids_to_return
def work_on_disk(dev,
                 root_mb,
                 swap_mb,
                 ephemeral_mb,
                 ephemeral_format,
                 image_path,
                 node_uuid,
                 preserve_ephemeral=False,
                 configdrive=None,
                 boot_option="netboot",
                 boot_mode="bios",
                 tempdir=None,
                 disk_label=None):
    """Create partitions and copy an image to the root partition.

    :param dev: Path for the device to work on.
    :param root_mb: Size of the root partition in megabytes.
    :param swap_mb: Size of the swap partition in megabytes.
    :param ephemeral_mb: Size of the ephemeral partition in megabytes. If 0,
        no ephemeral partition will be created.
    :param ephemeral_format: The type of file system to format the ephemeral
        partition.
    :param image_path: Path for the instance's disk image.
    :param node_uuid: node's uuid. Used for logging.
    :param preserve_ephemeral: If True, no filesystem is written to the
        ephemeral block device, preserving whatever content it had (if the
        partition table has not changed).
    :param configdrive: Optional. Base64 encoded Gzipped configdrive content
                        or configdrive HTTP URL.
    :param boot_option: Can be "local" or "netboot". "netboot" by default.
    :param boot_mode: Can be "bios" or "uefi". "bios" by default.
    :param tempdir: A temporary directory
    :param disk_label: The disk label to be used when creating the
        partition table. Valid values are: "msdos", "gpt" or None; If None
        Ironic will figure it out according to the boot_mode parameter.
    :returns: a dictionary containing the following keys:
        'root uuid': UUID of root partition
        'efi system partition uuid': UUID of the uefi system partition
                                     (if boot mode is uefi).
        NOTE: If key exists but value is None, it means partition doesn't
              exist.
    """
    # the only way for preserve_ephemeral to be set to true is if we are
    # rebuilding an instance with --preserve_ephemeral.
    commit = not preserve_ephemeral
    # now if we are committing the changes to disk clean first.
    if commit:
        destroy_disk_metadata(dev, node_uuid)

    try:
        # If requested, get the configdrive file and determine the size
        # of the configdrive partition
        configdrive_mb = 0
        configdrive_file = None
        if configdrive:
            configdrive_mb, configdrive_file = _get_configdrive(
                configdrive, node_uuid, tempdir=tempdir)

        part_dict = make_partitions(dev,
                                    root_mb,
                                    swap_mb,
                                    ephemeral_mb,
                                    configdrive_mb,
                                    node_uuid,
                                    commit=commit,
                                    boot_option=boot_option,
                                    boot_mode=boot_mode,
                                    disk_label=disk_label)
        LOG.info(
            _LI("Successfully completed the disk device"
                " %(dev)s partitioning for node %(node)s"), {
                    'dev': dev,
                    "node": node_uuid
                })

        ephemeral_part = part_dict.get('ephemeral')
        swap_part = part_dict.get('swap')
        configdrive_part = part_dict.get('configdrive')
        root_part = part_dict.get('root')

        if not is_block_device(root_part):
            raise exception.InstanceDeployFailure(
                _("Root device '%s' not found") % root_part)

        for part in ('swap', 'ephemeral', 'configdrive',
                     'efi system partition'):
            part_device = part_dict.get(part)
            LOG.debug(
                "Checking for %(part)s device (%(dev)s) on node "
                "%(node)s.", {
                    'part': part,
                    'dev': part_device,
                    'node': node_uuid
                })
            if part_device and not is_block_device(part_device):
                raise exception.InstanceDeployFailure(
                    _("'%(partition)s' device '%(part_device)s' not found") % {
                        'partition': part,
                        'part_device': part_device
                    })

        # If it's a uefi localboot, then we have created the efi system
        # partition.  Create a fat filesystem on it.
        if boot_mode == "uefi" and boot_option == "local":
            efi_system_part = part_dict.get('efi system partition')
            mkfs(dev=efi_system_part, fs='vfat', label='efi-part')

        if configdrive_part:
            # Copy the configdrive content to the configdrive partition
            dd(configdrive_file, configdrive_part)
            LOG.info(
                _LI("Configdrive for node %(node)s successfully copied "
                    "onto partition %(partition)s"), {
                        'node': node_uuid,
                        'partition': configdrive_part
                    })

    finally:
        # If the configdrive was requested make sure we delete the file
        # after copying the content to the partition
        if configdrive_file:
            utils.unlink_without_raise(configdrive_file)

    populate_image(image_path, root_part)
    LOG.info(_LI("Image for %(node)s successfully populated"),
             {'node': node_uuid})

    if swap_part:
        mkfs(dev=swap_part, fs='swap', label='swap1')
        LOG.info(
            _LI("Swap partition %(swap)s successfully formatted "
                "for node %(node)s"), {
                    'swap': swap_part,
                    'node': node_uuid
                })

    if ephemeral_part and not preserve_ephemeral:
        mkfs(dev=ephemeral_part, fs=ephemeral_format, label="ephemeral0")
        LOG.info(
            _LI("Ephemeral partition %(ephemeral)s successfully "
                "formatted for node %(node)s"), {
                    'ephemeral': ephemeral_part,
                    'node': node_uuid
                })

    uuids_to_return = {
        'root uuid': root_part,
        'efi system partition uuid': part_dict.get('efi system partition')
    }

    try:
        for part, part_dev in uuids_to_return.items():
            if part_dev:
                uuids_to_return[part] = block_uuid(part_dev)

    except processutils.ProcessExecutionError:
        with excutils.save_and_reraise_exception():
            LOG.error(_LE("Failed to detect %s"), part)

    return uuids_to_return
def create_config_drive_partition(node_uuid, device, configdrive):
    """Create a partition for config drive

    Checks if the device is GPT or MBR partitioned and creates config drive
    partition accordingly.

    :param node_uuid: UUID of the Node.
    :param device: The device path.
    :param configdrive: Base64 encoded Gzipped configdrive content or
        configdrive HTTP URL.
    :raises: InstanceDeployFailure if config drive size exceeds maximum limit
        or if it fails to create config drive.
    """
    confdrive_file = None
    try:
        config_drive_part = _get_labelled_partition(device,
                                                    CONFIGDRIVE_LABEL,
                                                    node_uuid)

        confdrive_mb, confdrive_file = _get_configdrive(configdrive,
                                                        node_uuid)
        if confdrive_mb > MAX_CONFIG_DRIVE_SIZE_MB:
                raise exception.InstanceDeployFailure(
                    _('Config drive size exceeds maximum limit of 64MiB. '
                      'Size of the given config drive is %(size)d MiB for '
                      'node %(node)s.')
                    % {'size': confdrive_mb, 'node': node_uuid})

        LOG.debug("Adding config drive partition %(size)d MiB to "
                  "device: %(dev)s for node %(node)s",
                  {'dev': device, 'size': confdrive_mb, 'node': node_uuid})

        if config_drive_part:
            LOG.debug("Configdrive for node %(node)s exists at "
                      "%(part)s",
                      {'node': node_uuid, 'part': config_drive_part})
        else:
            cur_parts = set(part['number'] for part in list_partitions(device))

            if _is_disk_gpt_partitioned(device, node_uuid):
                _fix_gpt_structs(device, node_uuid)
                create_option = '0:-%dMB:0' % MAX_CONFIG_DRIVE_SIZE_MB
                utils.execute('sgdisk', '-n', create_option, device,
                              run_as_root=True)
            else:
                # Check if the disk has 4 partitions. The MBR based disk
                # cannot have more than 4 partitions.
                # TODO(stendulker): One can use logical partitions to create
                # a config drive if there are 3 primary partitions.
                # https://bugs.launchpad.net/ironic/+bug/1561283
                try:
                    pp_count, lp_count = count_mbr_partitions(device)
                except ValueError as e:
                    raise exception.InstanceDeployFailure(
                        _('Failed to check the number of primary partitions '
                          'present on %(dev)s for node %(node)s. Error: '
                          '%(error)s') % {'dev': device, 'node': node_uuid,
                                          'error': e})
                if pp_count > 3:
                    raise exception.InstanceDeployFailure(
                        _('Config drive cannot be created for node %(node)s. '
                          'Disk (%(dev)s) uses MBR partitioning and already '
                          'has %(parts)d primary partitions.')
                        % {'node': node_uuid, 'dev': device,
                           'parts': pp_count})

                # Check if disk size exceeds 2TB msdos limit
                startlimit = '-%dMiB' % MAX_CONFIG_DRIVE_SIZE_MB
                endlimit = '-0'
                if _is_disk_larger_than_max_size(device, node_uuid):
                    # Need to create a small partition at 2TB limit
                    LOG.warning(_LW("Disk size is larger than 2TB for "
                                    "node %(node)s. Creating config drive "
                                    "at the end of the disk %(disk)s."),
                                {'node': node_uuid, 'disk': device})
                    startlimit = (MAX_DISK_SIZE_MB_SUPPORTED_BY_MBR -
                                  MAX_CONFIG_DRIVE_SIZE_MB - 1)
                    endlimit = MAX_DISK_SIZE_MB_SUPPORTED_BY_MBR - 1

                utils.execute('parted', '-a', 'optimal', '-s', '--', device,
                              'mkpart', 'primary', 'ext2', startlimit,
                              endlimit, run_as_root=True)

            upd_parts = set(part['number'] for part in list_partitions(device))
            new_part = set(upd_parts) - set(cur_parts)
            if len(new_part) != 1:
                raise exception.InstanceDeployFailure(
                    _('Disk partitioning failed on device %(device)s. '
                      'Unable to retrieve config drive partition information.')
                    % {'device': device})

            if is_iscsi_device(device, node_uuid):
                config_drive_part = '%s-part%s' % (device, new_part.pop())
            else:
                config_drive_part = '%s%s' % (device, new_part.pop())

            # NOTE(vdrok): the partition was created successfully, let's wait
            # for it to appear in /dev.
            LOG.debug('Waiting for the config drive partition %(part)s '
                      'on node %(node)s to be ready for writing.',
                      {'part': config_drive_part, 'node': node_uuid})
            utils.execute('udevadm', 'settle',
                          '--exit-if-exists=%s' % config_drive_part)

        dd(confdrive_file, config_drive_part)
        LOG.info(_LI("Configdrive for node %(node)s successfully "
                     "copied onto partition %(part)s"),
                 {'node': node_uuid, 'part': config_drive_part})

    except (processutils.UnknownArgumentError,
            processutils.ProcessExecutionError, OSError) as e:
        msg = (_('Failed to create config drive on disk %(disk)s '
                 'for node %(node)s. Error: %(error)s') %
               {'disk': device, 'node': node_uuid, 'error': e})
        LOG.error(msg)
        raise exception.InstanceDeployFailure(msg)
    finally:
        # If the configdrive was requested make sure we delete the file
        # after copying the content to the partition
        if confdrive_file:
            utils.unlink_without_raise(confdrive_file)
def create_config_drive_partition(node_uuid, device, configdrive):
    """Create a partition for config drive

    Checks if the device is GPT or MBR partitioned and creates config drive
    partition accordingly.

    :param node_uuid: UUID of the Node.
    :param device: The device path.
    :param configdrive: Base64 encoded Gzipped configdrive content or
        configdrive HTTP URL.
    :raises: InstanceDeployFailure if config drive size exceeds maximum limit
        or if it fails to create config drive.
    """
    confdrive_file = None
    try:
        config_drive_part = _get_labelled_partition(device, CONFIGDRIVE_LABEL,
                                                    node_uuid)

        confdrive_mb, confdrive_file = _get_configdrive(configdrive, node_uuid)
        if confdrive_mb > MAX_CONFIG_DRIVE_SIZE_MB:
            raise exception.InstanceDeployFailure(
                _('Config drive size exceeds maximum limit of 64MiB. '
                  'Size of the given config drive is %(size)d MiB for '
                  'node %(node)s.') % {
                      'size': confdrive_mb,
                      'node': node_uuid
                  })

        LOG.debug(
            "Adding config drive partition %(size)d MiB to "
            "device: %(dev)s for node %(node)s", {
                'dev': device,
                'size': confdrive_mb,
                'node': node_uuid
            })

        if config_drive_part:
            LOG.debug("Configdrive for node %(node)s exists at "
                      "%(part)s", {
                          'node': node_uuid,
                          'part': config_drive_part
                      })
        else:
            cur_parts = set(part['number'] for part in list_partitions(device))

            if _is_disk_gpt_partitioned(device, node_uuid):
                _fix_gpt_structs(device, node_uuid)
                create_option = '0:-%dMB:0' % MAX_CONFIG_DRIVE_SIZE_MB
                utils.execute('sgdisk',
                              '-n',
                              create_option,
                              device,
                              run_as_root=True)
            else:
                # Check if the disk has 4 partitions. The MBR based disk
                # cannot have more than 4 partitions.
                # TODO(stendulker): One can use logical partitions to create
                # a config drive if there are 3 primary partitions.
                # https://bugs.launchpad.net/ironic/+bug/1561283
                try:
                    pp_count, lp_count = count_mbr_partitions(device)
                except ValueError as e:
                    raise exception.InstanceDeployFailure(
                        _('Failed to check the number of primary partitions '
                          'present on %(dev)s for node %(node)s. Error: '
                          '%(error)s') % {
                              'dev': device,
                              'node': node_uuid,
                              'error': e
                          })
                if pp_count > 3:
                    raise exception.InstanceDeployFailure(
                        _('Config drive cannot be created for node %(node)s. '
                          'Disk (%(dev)s) uses MBR partitioning and already '
                          'has %(parts)d primary partitions.') % {
                              'node': node_uuid,
                              'dev': device,
                              'parts': pp_count
                          })

                # Check if disk size exceeds 2TB msdos limit
                startlimit = '-%dMiB' % MAX_CONFIG_DRIVE_SIZE_MB
                endlimit = '-0'
                if _is_disk_larger_than_max_size(device, node_uuid):
                    # Need to create a small partition at 2TB limit
                    LOG.warning(
                        _LW("Disk size is larger than 2TB for "
                            "node %(node)s. Creating config drive "
                            "at the end of the disk %(disk)s."), {
                                'node': node_uuid,
                                'disk': device
                            })
                    startlimit = (MAX_DISK_SIZE_MB_SUPPORTED_BY_MBR -
                                  MAX_CONFIG_DRIVE_SIZE_MB - 1)
                    endlimit = MAX_DISK_SIZE_MB_SUPPORTED_BY_MBR - 1

                utils.execute('parted',
                              '-a',
                              'optimal',
                              '-s',
                              '--',
                              device,
                              'mkpart',
                              'primary',
                              'fat32',
                              startlimit,
                              endlimit,
                              run_as_root=True)

            upd_parts = set(part['number'] for part in list_partitions(device))
            new_part = set(upd_parts) - set(cur_parts)
            if len(new_part) != 1:
                raise exception.InstanceDeployFailure(
                    _('Disk partitioning failed on device %(device)s. '
                      'Unable to retrieve config drive partition information.')
                    % {'device': device})

            if is_iscsi_device(device, node_uuid):
                config_drive_part = '%s-part%s' % (device, new_part.pop())
            else:
                config_drive_part = '%s%s' % (device, new_part.pop())

            # NOTE(vdrok): the partition was created successfully, let's wait
            # for it to appear in /dev.
            LOG.debug(
                'Waiting for the config drive partition %(part)s '
                'on node %(node)s to be ready for writing.', {
                    'part': config_drive_part,
                    'node': node_uuid
                })
            utils.execute('udevadm', 'settle',
                          '--exit-if-exists=%s' % config_drive_part)

        dd(confdrive_file, config_drive_part)
        LOG.info(
            _LI("Configdrive for node %(node)s successfully "
                "copied onto partition %(part)s"), {
                    'node': node_uuid,
                    'part': config_drive_part
                })

    except (processutils.UnknownArgumentError,
            processutils.ProcessExecutionError, OSError) as e:
        msg = (_('Failed to create config drive on disk %(disk)s '
                 'for node %(node)s. Error: %(error)s') % {
                     'disk': device,
                     'node': node_uuid,
                     'error': e
                 })
        LOG.error(msg)
        raise exception.InstanceDeployFailure(msg)
    finally:
        # If the configdrive was requested make sure we delete the file
        # after copying the content to the partition
        if confdrive_file:
            utils.unlink_without_raise(confdrive_file)