def create_volume_from_snapshot(self, volume, snapshot): """Create volume from snapshot. InfiniBox does not yet support detached clone so use dd to copy data. This could be a lengthy operation. - create a clone from snapshot and map it - create a volume and map it - copy data from clone to volume - unmap volume and clone and delete the clone """ infinidat_snapshot = self._get_infinidat_snapshot(snapshot) clone_name = self._make_volume_name(volume) + '-internal' infinidat_clone = infinidat_snapshot.create_snapshot(name=clone_name) # we need a cinder-volume-like object to map the clone by name # (which is derived from the cinder id) but the clone is internal # so there is no such object. mock one clone = mock.Mock(id=str(volume.id) + '-internal') try: infinidat_volume = self._create_volume(volume) try: src_ctx = self._device_connect_context(clone) dst_ctx = self._device_connect_context(volume) with src_ctx as src_dev, dst_ctx as dst_dev: dd_block_size = self.configuration.volume_dd_blocksize volume_utils.copy_volume(src_dev['device']['path'], dst_dev['device']['path'], snapshot.volume.size * units.Ki, dd_block_size, sparse=True) except Exception: infinidat_volume.delete() raise finally: infinidat_clone.delete()
def create_cloned_volume(self, volume, src_vref): """Create a clone from source volume. InfiniBox does not yet support detached clone so use dd to copy data. This could be a lengthy operation. * map source volume * create and map new volume * copy data from source to new volume * unmap both volumes """ self._asssert_volume_not_mapped(src_vref) infinidat_volume = self._create_volume(volume) try: src_ctx = self._device_connect_context(src_vref) dst_ctx = self._device_connect_context(volume) with src_ctx as src_dev, dst_ctx as dst_dev: dd_block_size = self.configuration.volume_dd_blocksize volume_utils.copy_volume(src_dev['device']['path'], dst_dev['device']['path'], src_vref.size * units.Ki, dd_block_size, sparse=True) except Exception: infinidat_volume.delete() raise
def create_volume_from_snapshot(self, volume, snapshot): """Creates a volume from a snapshot.""" if self.configuration.lvm_type == 'thin': self.vg.create_lv_snapshot(volume['name'], self._escape_snapshot(snapshot['name']), self.configuration.lvm_type) if volume['size'] > snapshot['volume_size']: LOG.debug("Resize the new volume to %s.", volume['size']) self.extend_volume(volume, volume['size']) # Some configurations of LVM do not automatically activate # ThinLVM snapshot LVs. self.vg.activate_lv(snapshot['name'], is_snapshot=True) self.vg.activate_lv(volume['name'], is_snapshot=True, permanent=True) return self._create_volume(volume['name'], self._sizestr(volume['size']), self.configuration.lvm_type, self.configuration.lvm_mirrors) # Some configurations of LVM do not automatically activate # ThinLVM snapshot LVs. self.vg.activate_lv(snapshot['name'], is_snapshot=True) # copy_volume expects sizes in MiB, we store integer GiB # be sure to convert before passing in volume_utils.copy_volume(self.local_path(snapshot), self.local_path(volume), snapshot['volume_size'] * units.Ki, self.configuration.volume_dd_blocksize, execute=self._execute, sparse=self._sparse_copy_volume)
def _dd_copy(self, vol_params, src_snap, src_lun=None): """Creates a volume via copying a Unity snapshot. It attaches the `volume` and `snap`, then use `dd` to copy the data from the Unity snapshot to the `volume`. """ dest_lun = self.client.create_lun( name=vol_params.name, size=vol_params.size, pool=vol_params.pool, description=vol_params.description, io_limit_policy=vol_params.io_limit_policy, is_thin=False if vol_params.is_thick else None, is_compressed=vol_params.is_compressed) src_id = src_snap.get_id() try: conn_props = cinder_utils.brick_get_connector_properties() with self._connect_resource(dest_lun, conn_props, vol_params.volume_id) as dest_info, \ self._connect_resource(src_snap, conn_props, src_id) as src_info: if src_lun is None: # If size is not specified, need to get the size from LUN # of snapshot. size_in_m = utils.byte_to_mib(src_snap.size) else: size_in_m = utils.byte_to_mib(src_lun.size_total) volume_utils.copy_volume( src_info['device']['path'], dest_info['device']['path'], size_in_m, self.driver.configuration.volume_dd_blocksize, sparse=True) except Exception: with excutils.save_and_reraise_exception(): utils.ignore_exception(self.client.delete_lun, dest_lun.get_id()) LOG.error( 'Failed to create cloned volume: %(vol_id)s, ' 'from source unity snapshot: %(snap_name)s.', { 'vol_id': vol_params.volume_id, 'snap_name': src_snap.name }) return dest_lun
def create_cloned_volume(self, volume, src_vref): """Creates a clone of the specified volume.""" if self.configuration.lvm_type == 'thin': self.vg.create_lv_snapshot(volume['name'], src_vref['name'], self.configuration.lvm_type) if volume['size'] > src_vref['size']: LOG.debug("Resize the new volume to %s.", volume['size']) self.extend_volume(volume, volume['size']) self.vg.activate_lv(volume['name'], is_snapshot=True, permanent=True) return mirror_count = 0 if self.configuration.lvm_mirrors: mirror_count = self.configuration.lvm_mirrors LOG.info('Creating clone of volume: %s', src_vref['id']) volume_name = src_vref['name'] temp_id = 'tmp-snap-%s' % volume['id'] temp_snapshot = {'volume_name': volume_name, 'size': src_vref['size'], 'volume_size': src_vref['size'], 'name': 'clone-snap-%s' % volume['id'], 'id': temp_id} self.create_snapshot(temp_snapshot) # copy_volume expects sizes in MiB, we store integer GiB # be sure to convert before passing in try: self._create_volume(volume['name'], self._sizestr(volume['size']), self.configuration.lvm_type, mirror_count) self.vg.activate_lv(temp_snapshot['name'], is_snapshot=True) volume_utils.copy_volume( self.local_path(temp_snapshot), self.local_path(volume), src_vref['size'] * units.Ki, self.configuration.volume_dd_blocksize, execute=self._execute, sparse=self._sparse_copy_volume) finally: self.delete_snapshot(temp_snapshot)
def _copy_vdisk_data(self, src_vdisk_name, src_vdisk_id, dest_vdisk_name, dest_vdisk_id): """Copy data from src vdisk to dest vdisk. To be able to copy data between vdisks, we must ensure that both vdisks have been mapped to host. If vdisk has not been mapped, it must be mapped firstly. When data copy completed, vdisk should be restored to previous mapped or non-mapped status. """ LOG.debug('enter: _copy_vdisk_data: %(src)s -> %(dest)s.', { 'src': src_vdisk_name, 'dest': dest_vdisk_name }) connector = volume_utils.brick_get_connector_properties() (src_map, src_lun_id) = self._is_vdisk_map(src_vdisk_name, connector) (dest_map, dest_lun_id) = self._is_vdisk_map(dest_vdisk_name, connector) src_map_device = None src_properties = None dest_map_device = None dest_properties = None try: if not src_map: src_lun_id = self._map_vdisk_to_host(src_vdisk_name, connector) if not dest_map: dest_lun_id = self._map_vdisk_to_host(dest_vdisk_name, connector) src_properties = self._get_vdisk_map_properties( connector, src_lun_id, src_vdisk_name, src_vdisk_id, self._get_vdisk_params(None)) src_map_device = self._scan_device(src_properties) dest_properties = self._get_vdisk_map_properties( connector, dest_lun_id, dest_vdisk_name, dest_vdisk_id, self._get_vdisk_params(None)) dest_map_device = self._scan_device(dest_properties) src_vdisk_attr = self._get_vdisk_attributes(src_vdisk_name) # vdisk capacity is bytes, translate into MB size_in_mb = int(src_vdisk_attr['capacity']) / units.Mi volume_utils.copy_volume(src_map_device['path'], dest_map_device['path'], size_in_mb, self.configuration.volume_dd_blocksize) except Exception: with excutils.save_and_reraise_exception(): LOG.error('Failed to copy %(src)s to %(dest)s.', { 'src': src_vdisk_name, 'dest': dest_vdisk_name }) finally: if not dest_map: self._unmap_vdisk_from_host(dest_vdisk_name, connector) self._remove_device(dest_properties, dest_map_device) if not src_map: self._unmap_vdisk_from_host(src_vdisk_name, connector) self._remove_device(src_properties, src_map_device) LOG.debug('leave: _copy_vdisk_data: %(src)s -> %(dest)s.', { 'src': src_vdisk_name, 'dest': dest_vdisk_name })
def fetch_to_volume_format(context, image_service, image_id, dest, volume_format, blocksize, volume_subformat=None, user_id=None, project_id=None, size=None, run_as_root=True): qemu_img = True image_meta = image_service.show(context, image_id) allow_image_compression = CONF.allow_compression_on_image_upload if image_meta and (image_meta.get('container_format') == 'compressed'): if allow_image_compression is False: compression_param = { 'container_format': image_meta.get('container_format') } raise exception.ImageUnacceptable( image_id=image_id, reason=_("Image compression disallowed, " "but container_format is " "%(container_format)s.") % compression_param) # NOTE(avishay): I'm not crazy about creating temp files which may be # large and cause disk full errors which would confuse users. # Unfortunately it seems that you can't pipe to 'qemu-img convert' because # it seeks. Maybe we can think of something for a future version. with temporary_file() as tmp: has_meta = False if not image_meta else True try: format_raw = True if image_meta['disk_format'] == 'raw' else False except TypeError: format_raw = False data = get_qemu_data(image_id, has_meta, format_raw, tmp, run_as_root) if data is None: qemu_img = False tmp_images = TemporaryImages.for_image_service(image_service) tmp_image = tmp_images.get(context, image_id) if tmp_image: tmp = tmp_image else: fetch(context, image_service, image_id, tmp, user_id, project_id) if is_xenserver_format(image_meta): replace_xenserver_image_with_coalesced_vhd(tmp) if not qemu_img: # qemu-img is not installed but we do have a RAW image. As a # result we only need to copy the image to the destination and then # return. LOG.debug( 'Copying image from %(tmp)s to volume %(dest)s - ' 'size: %(size)s', { 'tmp': tmp, 'dest': dest, 'size': image_meta['size'] }) image_size_m = math.ceil(float(image_meta['size']) / units.Mi) volume_utils.copy_volume(tmp, dest, image_size_m, blocksize) return data = qemu_img_info(tmp, run_as_root=run_as_root) # NOTE(xqueralt): If the image virtual size doesn't fit in the # requested volume there is no point on resizing it because it will # generate an unusable image. if size is not None: check_virtual_size(data.virtual_size, size, image_id) fmt = data.file_format if fmt is None: raise exception.ImageUnacceptable( reason=_("'qemu-img info' parsing failed."), image_id=image_id) backing_file = data.backing_file if backing_file is not None: raise exception.ImageUnacceptable( image_id=image_id, reason=_("fmt=%(fmt)s backed by:%(backing_file)s") % { 'fmt': fmt, 'backing_file': backing_file, }) # NOTE(ZhengMa): This is used to do image decompression on image # downloading with 'compressed' container_format. It is a # transparent level between original image downloaded from # Glance and Cinder image service. So the source file path is # the same with destination file path. if image_meta.get('container_format') == 'compressed': LOG.debug("Found image with compressed container format") if not accelerator.is_gzip_compressed(tmp): raise exception.ImageUnacceptable( image_id=image_id, reason=_("Unsupported compressed image format found. " "Only gzip is supported currently")) accel = accelerator.ImageAccel(tmp, tmp) accel.decompress_img(run_as_root=run_as_root) # NOTE(jdg): I'm using qemu-img convert to write # to the volume regardless if it *needs* conversion or not # TODO(avishay): We can speed this up by checking if the image is raw # and if so, writing directly to the device. However, we need to keep # check via 'qemu-img info' that what we copied was in fact a raw # image and not a different format with a backing file, which may be # malicious. disk_format = fixup_disk_format(image_meta['disk_format']) LOG.debug("%s was %s, converting to %s", image_id, fmt, volume_format) convert_image(tmp, dest, volume_format, out_subformat=volume_subformat, src_format=disk_format, run_as_root=run_as_root)
def migrate_volume(self, ctxt, volume, host, thin=False, mirror_count=0): """Optimize the migration if the destination is on the same server. If the specified host is another back-end on the same server, and the volume is not attached, we can do the migration locally without going through iSCSI. """ false_ret = (False, None) if volume['status'] != 'available': return false_ret if 'location_info' not in host['capabilities']: return false_ret info = host['capabilities']['location_info'] try: (dest_type, dest_hostname, dest_vg, lvm_type, lvm_mirrors) =\ info.split(':') lvm_mirrors = int(lvm_mirrors) except ValueError: return false_ret if (dest_type != 'LVMVolumeDriver' or dest_hostname != self.hostname): return false_ret if dest_vg == self.vg.vg_name: message = (_("Refusing to migrate volume ID: %(id)s. Please " "check your configuration because source and " "destination are the same Volume Group: %(name)s.") % { 'id': volume['id'], 'name': self.vg.vg_name }) LOG.error(message) raise exception.VolumeBackendAPIException(data=message) vg_list = volume_utils.get_all_volume_groups() try: next(vg for vg in vg_list if vg['name'] == dest_vg) except StopIteration: LOG.error("Destination Volume Group %s does not exist", dest_vg) return false_ret helper = utils.get_root_helper() lvm_conf_file = self.configuration.lvm_conf_file if lvm_conf_file.lower() == 'none': lvm_conf_file = None dest_vg_ref = lvm.LVM(dest_vg, helper, lvm_type=lvm_type, executor=self._execute, lvm_conf=lvm_conf_file) self._create_volume(volume['name'], self._sizestr(volume['size']), lvm_type, lvm_mirrors, dest_vg_ref) # copy_volume expects sizes in MiB, we store integer GiB # be sure to convert before passing in size_in_mb = int(volume['size']) * units.Ki try: volume_utils.copy_volume(self.local_path(volume), self.local_path(volume, vg=dest_vg), size_in_mb, self.configuration.volume_dd_blocksize, execute=self._execute, sparse=self._sparse_copy_volume) except Exception as e: with excutils.save_and_reraise_exception(): LOG.error( "Volume migration failed due to " "exception: %(reason)s.", {'reason': six.text_type(e)}, resource=volume) dest_vg_ref.delete(volume) self._delete_volume(volume) return (True, None)