def _validate_partitioning(device): """Validate the final partition table. Check if after writing the image to disk we have a valid partition table by trying to read it. This will fail if the disk is junk. """ try: # Ensure we re-read the partition table before we try to list # partitions utils.execute('partprobe', device, run_as_root=True, attempts=CONF.disk_utils.partprobe_attempts) except (processutils.UnknownArgumentError, processutils.ProcessExecutionError, OSError) as e: LOG.warning("Unable to probe for partitions on device %(device)s " "after writing the image, the partitioning table may " "be broken. Error: %(error)s", {'device': device, 'error': e}) try: nparts = len(disk_utils.list_partitions(device)) except (processutils.UnknownArgumentError, processutils.ProcessExecutionError, OSError) as e: msg = ("Unable to find a valid partition table on the disk after " "writing the image. Error {}".format(e)) raise exception.InstanceDeployFailure(msg) # Check if there is at least one partition in the partition table after # deploy if not nparts: msg = ("No partitions found on the device {} after writing " "the image.".format(device)) raise exception.InstanceDeployFailure(msg)
def partition_with_path(path): root_dev = hardware.dispatch_to_managers('get_os_install_device') partitions = disk_utils.list_partitions(root_dev) local_path = tempfile.mkdtemp() for part in partitions: if 'esp' in part['flags'] or 'lvm' in part['flags']: LOG.debug('Skipping partition %s', part) continue part_path = partition_index_to_name(root_dev, part['number']) try: with utils.mounted(part_path) as local_path: conf_path = os.path.join(local_path, path) LOG.debug('Checking for path %s on %s', conf_path, part_path) if not os.path.isdir(conf_path): continue LOG.info('Path found: %s on %s', conf_path, part_path) yield conf_path return except processutils.ProcessExecutionError as exc: LOG.warning('Failure when inspecting partition %s: %s', part, exc) raise RuntimeError("No partition found with path %s, scanned: %s" % (path, partitions))
def test_incorrect(self, log_mock, execute_mock): output = """ BYT; /dev/sda:500107862016B:scsi:512:4096:msdos:ATA HGST HTS725050A7:; 1:XX1076MiB:---:524MiB:ext4::boot; """ execute_mock.return_value = (output, '') self.assertEqual([], disk_utils.list_partitions('/dev/fake')) self.assertEqual(1, log_mock.call_count)
def get_efi_part_on_device(device): """Looks for the efi partition on a given device. A boot partition on a GPT disk is assumed to be an EFI partition as well. :param device: lock device upon which to check for the efi partition :return: the efi partition or None """ is_gpt = scan_partition_table_type(device) == 'gpt' for part in disk_utils.list_partitions(device): flags = {x.strip() for x in part['flags'].split(',')} if 'esp' in flags or ('boot' in flags and is_gpt): LOG.debug("Found EFI partition %s on device %s.", part, device) return part['number'] else: LOG.debug("No efi partition found on device %s", device)
def test_correct(self, execute_mock): output = """ BYT; /dev/sda:500107862016B:scsi:512:4096:msdos:ATA HGST HTS725050A7:; 1:1.00MiB:501MiB:500MiB:ext4::boot; 2:501MiB:476940MiB:476439MiB:::; """ expected = [ {'number': 1, 'start': 1, 'end': 501, 'size': 500, 'filesystem': 'ext4', 'flags': 'boot'}, {'number': 2, 'start': 501, 'end': 476940, 'size': 476439, 'filesystem': '', 'flags': ''}, ] execute_mock.return_value = (output, '') result = disk_utils.list_partitions('/dev/fake') self.assertEqual(expected, result) execute_mock.assert_called_once_with( 'parted', '-s', '-m', '/dev/fake', 'unit', 'MiB', 'print', use_standard_locale=True, run_as_root=True)
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))
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, disk_utils.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}) disk_utils.fix_gpt_partition(device, 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 disk_utils.list_partitions(device)) if disk_utils.get_partition_table_type(device) == 'gpt': 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 = disk_utils.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("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) # Trigger device rescan disk_utils.trigger_device_rescan(device) upd_parts = set(part['number'] for part in disk_utils.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}) config_drive_part = disk_utils.partition_index_to_path( device, new_part.pop()) disk_utils.udev_settle() # NOTE(vsaienko): check that devise actually exists, # it is not handled by udevadm when using ISCSI, for more info see: # https://bugs.launchpad.net/ironic/+bug/1673731 # Do not use 'udevadm settle --exit-if-exist' here 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('test', '-e', config_drive_part, attempts=15, delay_on_retry=True) disk_utils.dd(confdrive_file, config_drive_part) LOG.info("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)