def _write_image(image_info, device): """Writes an image to the specified device. :param image_info: Image information dictionary. :param device: The disk name, as a string, on which to store the image. Example: '/dev/sda' :raises: ImageWriteError if the command to write the image encounters an error. """ starttime = time.time() image = _image_location(image_info) uuids = {} if image_info.get('image_type') == 'partition': uuids = _write_partition_image(image, image_info, device) else: _write_whole_disk_image(image, image_info, device) totaltime = time.time() - starttime LOG.info('Image {} written to device {} in {} seconds'.format( image, device, totaltime)) try: disk_utils.fix_gpt_partition(device, node_uuid=None) except exception.InstanceDeployFailure: # Note: the catch internal to the helper method logs any errors. pass return uuids
def _stream_raw_image_onto_device(self, image_info, device): """Streams raw image data to specified local device. :param image_info: Image information dictionary. :param device: The disk name, as a string, on which to store the image. Example: '/dev/sda' :raises: ImageDownloadError if the image download encounters an error. :raises: ImageChecksumError if the checksum of the local image does not match the checksum as reported by glance in image_info. """ starttime = time.time() image_download = ImageDownload(image_info, time_obj=starttime) with open(device, 'wb+') as f: try: for chunk in image_download: f.write(chunk) except Exception as e: msg = 'Unable to write image to device {}. Error: {}'.format( device, str(e)) raise errors.ImageDownloadError(image_info['id'], msg) totaltime = time.time() - starttime LOG.info("Image streamed onto device {} in {} " "seconds".format(device, totaltime)) # Verify if the checksum of the streamed image is correct image_download.verify_image(device) # Fix any gpt partition try: disk_utils.fix_gpt_partition(device, node_uuid=None) except exception.InstanceDeployFailure: # Note: the catch internal to the helper method logs any errors. pass
def _stream_raw_image_onto_device(self, image_info, device): """Streams raw image data to specified local device. :param image_info: Image information dictionary. :param device: The disk name, as a string, on which to store the image. Example: '/dev/sda' :raises: ImageDownloadError if the image download encounters an error. :raises: ImageChecksumError if the checksum of the local image does not match the checksum as reported by glance in image_info. """ starttime = time.time() total_retries = CONF.image_download_connection_retries for attempt in range(total_retries + 1): try: image_download = ImageDownload(image_info, time_obj=starttime) with open(device, 'wb+') as f: try: for chunk in image_download: f.write(chunk) except Exception as e: msg = ('Unable to write image to device {}. ' 'Error: {}').format(device, str(e)) raise errors.ImageDownloadError(image_info['id'], msg) except errors.ImageDownloadError as e: if attempt == CONF.image_download_connection_retries: raise else: LOG.warning('Image download failed, %(attempt)s of ' '%(total)s: %(error)s', {'attempt': attempt, 'total': total_retries, 'error': e}) time.sleep(CONF.image_download_connection_retry_interval) else: break totaltime = time.time() - starttime LOG.info("Image streamed onto device {} in {} " "seconds".format(device, totaltime)) # Verify if the checksum of the streamed image is correct image_download.verify_image(device) # Fix any gpt partition try: disk_utils.fix_gpt_partition(device, node_uuid=None) except exception.InstanceDeployFailure: # Note: the catch internal to the helper method logs any errors. pass # Fix the root partition UUID root_uuid = disk_utils.block_uuid(device) LOG.info("{} UUID is now {}".format(device, root_uuid)) self.partition_uuids['root uuid'] = root_uuid
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)