def test_copy_image_to_volume(self): """resize_image common case usage.""" mox = self._mox drv = self._driver TEST_IMG_SOURCE = 'foo.img' volume = {'size': self.TEST_SIZE_IN_GB, 'name': TEST_IMG_SOURCE} def fake_local_path(volume): return volume['name'] self.stubs.Set(drv, 'local_path', fake_local_path) mox.StubOutWithMock(image_utils, 'fetch_to_raw') image_utils.fetch_to_raw(None, None, None, TEST_IMG_SOURCE) mox.StubOutWithMock(image_utils, 'resize_image') image_utils.resize_image(TEST_IMG_SOURCE, self.TEST_SIZE_IN_GB) mox.StubOutWithMock(image_utils, 'qemu_img_info') data = mox_lib.MockAnything() data.virtual_size = 1024 ** 3 image_utils.qemu_img_info(TEST_IMG_SOURCE).AndReturn(data) mox.ReplayAll() drv.copy_image_to_volume(None, volume, None, None) mox.VerifyAll()
def test_clone_image_cloneableshare_raw(self): drv = self._driver mox = self.mox volume = {'name': 'vol', 'size': '20'} mox.StubOutWithMock(drv, '_find_image_in_cache') mox.StubOutWithMock(drv, '_is_cloneable_share') mox.StubOutWithMock(drv, '_get_mount_point_for_share') mox.StubOutWithMock(image_utils, 'qemu_img_info') mox.StubOutWithMock(drv, '_clone_volume') mox.StubOutWithMock(drv, '_discover_file_till_timeout') mox.StubOutWithMock(drv, '_set_rw_permissions_for_all') mox.StubOutWithMock(drv, '_resize_image_file') mox.StubOutWithMock(drv, '_is_share_vol_compatible') drv._find_image_in_cache(IgnoreArg()).AndReturn([]) drv._is_cloneable_share(IgnoreArg()).AndReturn('127.0.0.1:/share') drv._is_share_vol_compatible(IgnoreArg(), IgnoreArg()).AndReturn(True) drv._get_mount_point_for_share(IgnoreArg()).AndReturn('/mnt') image_utils.qemu_img_info('/mnt/img-id').AndReturn( self.get_img_info('raw')) drv._clone_volume('img-id', 'vol', share='127.0.0.1:/share', volume_id=None) drv._get_mount_point_for_share(IgnoreArg()).AndReturn('/mnt') drv._discover_file_till_timeout(IgnoreArg()).AndReturn(True) drv._set_rw_permissions_for_all('/mnt/vol') drv._resize_image_file({'name': 'vol'}, IgnoreArg()) mox.ReplayAll() drv.clone_image(volume, ('nfs://127.0.0.1:/share/img-id', None), 'image_id', {}) mox.VerifyAll()
def test_copy_image_to_volume(self): """resize_image common case usage.""" mox = self._mox drv = self._driver TEST_IMG_SOURCE = "foo.img" volume = {"size": self.TEST_SIZE_IN_GB, "name": TEST_IMG_SOURCE} def fake_local_path(volume): return volume["name"] self.stubs.Set(drv, "local_path", fake_local_path) mox.StubOutWithMock(image_utils, "fetch_to_raw") image_utils.fetch_to_raw(None, None, None, TEST_IMG_SOURCE, mox_lib.IgnoreArg(), size=self.TEST_SIZE_IN_GB) mox.StubOutWithMock(image_utils, "resize_image") image_utils.resize_image(TEST_IMG_SOURCE, self.TEST_SIZE_IN_GB) mox.StubOutWithMock(image_utils, "qemu_img_info") data = mox_lib.MockAnything() data.virtual_size = 1 * units.GiB image_utils.qemu_img_info(TEST_IMG_SOURCE).AndReturn(data) mox.ReplayAll() drv.copy_image_to_volume(None, volume, None, None) mox.VerifyAll()
def test_clone_image_cloneableshare_raw(self): drv = self._driver mox = self.mox volume = {'name': 'vol', 'size': '20'} mox.StubOutWithMock(drv, '_find_image_in_cache') mox.StubOutWithMock(drv, '_is_cloneable_share') mox.StubOutWithMock(drv, '_get_mount_point_for_share') mox.StubOutWithMock(image_utils, 'qemu_img_info') mox.StubOutWithMock(drv, '_clone_volume') mox.StubOutWithMock(drv, '_discover_file_till_timeout') mox.StubOutWithMock(drv, '_set_rw_permissions_for_all') mox.StubOutWithMock(drv, '_resize_image_file') mox.StubOutWithMock(drv, '_is_share_vol_compatible') drv._find_image_in_cache(IgnoreArg()).AndReturn([]) drv._is_cloneable_share(IgnoreArg()).AndReturn('127.0.0.1:/share') drv._is_share_vol_compatible(IgnoreArg(), IgnoreArg()).AndReturn(True) drv._get_mount_point_for_share(IgnoreArg()).AndReturn('/mnt') image_utils.qemu_img_info('/mnt/img-id').AndReturn( self.get_img_info('raw')) drv._clone_volume( 'img-id', 'vol', share='127.0.0.1:/share', volume_id=None) drv._get_mount_point_for_share(IgnoreArg()).AndReturn('/mnt') drv._discover_file_till_timeout(IgnoreArg()).AndReturn(True) drv._set_rw_permissions_for_all('/mnt/vol') drv._resize_image_file({'name': 'vol'}, IgnoreArg()) mox.ReplayAll() drv. clone_image( volume, ('nfs://127.0.0.1:/share/img-id', None), 'image_id', {}) mox.VerifyAll()
def test_backup_volume(self): self.mox.StubOutWithMock(self._driver, 'db') self.mox.StubOutWithMock(self._driver.db, 'volume_get') volume = {'id': '2', 'name': self.TEST_VOLNAME} self._driver.db.volume_get(context, volume['id']).AndReturn(volume) info = imageutils.QemuImgInfo() info.file_format = 'raw' self.mox.StubOutWithMock(image_utils, 'qemu_img_info') image_utils.qemu_img_info(self.TEST_VOLPATH).AndReturn(info) self.mox.StubOutWithMock(utils, 'temporary_chown') mock_tempchown = self.mox.CreateMockAnything() utils.temporary_chown(self.TEST_VOLPATH).AndReturn(mock_tempchown) mock_tempchown.__enter__() mock_tempchown.__exit__(None, None, None) self.mox.StubOutWithMock(fileutils, 'file_open') mock_fileopen = self.mox.CreateMockAnything() fileutils.file_open(self.TEST_VOLPATH).AndReturn(mock_fileopen) mock_fileopen.__enter__() mock_fileopen.__exit__(None, None, None) backup = {'volume_id': volume['id']} mock_servicebackup = self.mox.CreateMockAnything() mock_servicebackup.backup(backup, mox_lib.IgnoreArg()) self.mox.ReplayAll() self._driver.backup_volume(context, backup, mock_servicebackup) self.mox.VerifyAll()
def test_initialize_connection(self): (mox, drv) = self._mox, self._driver volume = self._simple_volume() vol_dir = os.path.join(self.TEST_MNT_POINT_BASE, drv._get_hash_str(self.TEST_QUOBYTE_VOLUME)) vol_path = os.path.join(vol_dir, volume['name']) qemu_img_output = """image: %s file format: raw virtual size: 1.0G (1073741824 bytes) disk size: 173K """ % volume['name'] img_info = imageutils.QemuImgInfo(qemu_img_output) mox.StubOutWithMock(drv, 'get_active_image_from_info') mox.StubOutWithMock(image_utils, 'qemu_img_info') drv.get_active_image_from_info(volume).AndReturn(volume['name']) image_utils.qemu_img_info(vol_path).AndReturn(img_info) mox.ReplayAll() conn_info = drv.initialize_connection(volume, None) mox.VerifyAll() self.assertEqual(conn_info['data']['format'], 'raw') self.assertEqual(conn_info['driver_volume_type'], 'quobyte') self.assertEqual(conn_info['data']['name'], volume['name']) self.assertEqual(conn_info['mount_point_base'], self.TEST_MNT_POINT_BASE)
def _direct_nfs_clone(self, volume, image_location, image_id): """Clone directly in nfs share.""" LOG.info(_("Cloning image %s directly in share"), image_id) cloned = False image_location = self._construct_image_nfs_url(image_location) share = self._is_cloneable_share(image_location) if share and self._is_share_eligible(share, volume["size"]): LOG.debug(_("Share is cloneable %s"), share) volume["provider_location"] = share (__, ___, img_file) = image_location.rpartition("/") dir_path = self._get_mount_point_for_share(share) img_path = "%s/%s" % (dir_path, img_file) img_info = image_utils.qemu_img_info(img_path) if img_info.file_format == "raw": LOG.debug(_("Image is raw %s"), image_id) self._clone_volume(img_file, volume["name"], volume_id=None, share=share) cloned = True else: LOG.info(_("Image will locally be converted to raw %s"), image_id) dst = "%s/%s" % (dir_path, volume["name"]) image_utils.convert_image(img_path, dst, "raw") data = image_utils.qemu_img_info(dst) if data.file_format != "raw": raise exception.InvalidResults(_("Converted to raw, but" " format is now %s") % data.file_format) else: cloned = True self._register_image_in_cache(volume, image_id) return cloned
def test_extend_volume(self): (mox, drv) = self._mox, self._driver volume = self._simple_volume() volume_path = '%s/%s/volume-%s' % ( self.TEST_MNT_POINT_BASE, drv._get_hash_str(self.TEST_QUOBYTE_VOLUME), self.VOLUME_UUID) qemu_img_info_output = """image: volume-%s file format: qcow2 virtual size: 1.0G (1073741824 bytes) disk size: 473K """ % self.VOLUME_UUID img_info = imageutils.QemuImgInfo(qemu_img_info_output) mox.StubOutWithMock(drv, '_execute') mox.StubOutWithMock(drv, 'get_active_image_from_info') mox.StubOutWithMock(image_utils, 'qemu_img_info') mox.StubOutWithMock(image_utils, 'resize_image') drv.get_active_image_from_info(volume).AndReturn(volume['name']) image_utils.qemu_img_info(volume_path).AndReturn(img_info) image_utils.resize_image(volume_path, 3) mox.ReplayAll() drv.extend_volume(volume, 3) mox.VerifyAll()
def test_copy_volume_from_snapshot(self): (mox, drv) = self._mox, self._driver mox.StubOutWithMock(image_utils, 'convert_image') mox.StubOutWithMock(drv, '_read_info_file') mox.StubOutWithMock(image_utils, 'qemu_img_info') mox.StubOutWithMock(drv, '_set_rw_permissions_for_all') dest_volume = self._simple_volume( 'c1073000-0000-0000-0000-0000000c1073') src_volume = self._simple_volume() vol_dir = os.path.join(self.TEST_MNT_POINT_BASE, drv._get_hash_str(self.TEST_QUOBYTE_VOLUME)) src_vol_path = os.path.join(vol_dir, src_volume['name']) dest_vol_path = os.path.join(vol_dir, dest_volume['name']) info_path = os.path.join(vol_dir, src_volume['name']) + '.info' snapshot = { 'volume_name': src_volume['name'], 'name': 'clone-snap-%s' % src_volume['id'], 'size': src_volume['size'], 'volume_size': src_volume['size'], 'volume_id': src_volume['id'], 'id': 'tmp-snap-%s' % src_volume['id'], 'volume': src_volume } snap_file = dest_volume['name'] + '.' + snapshot['id'] snap_path = os.path.join(vol_dir, snap_file) size = dest_volume['size'] drv._read_info_file(info_path).AndReturn({ 'active': snap_file, snapshot['id']: snap_file }) qemu_img_output = """image: %s file format: raw virtual size: 1.0G (1073741824 bytes) disk size: 173K backing file: %s """ % (snap_file, src_volume['name']) img_info = imageutils.QemuImgInfo(qemu_img_output) image_utils.qemu_img_info(snap_path).AndReturn(img_info) image_utils.convert_image(src_vol_path, dest_vol_path, 'raw', run_as_root=self.execute_as_root) drv._set_rw_permissions_for_all(dest_vol_path) mox.ReplayAll() drv._copy_volume_from_snapshot(snapshot, dest_volume, size) mox.VerifyAll()
def test_clone_image_cloneableshare_notraw(self): drv = self._driver mox = self.mox volume = {"name": "vol", "size": "20"} mox.StubOutWithMock(drv, "_find_image_in_cache") mox.StubOutWithMock(drv, "_is_cloneable_share") mox.StubOutWithMock(drv, "_get_mount_point_for_share") mox.StubOutWithMock(image_utils, "qemu_img_info") mox.StubOutWithMock(drv, "_clone_volume") mox.StubOutWithMock(drv, "_discover_file_till_timeout") mox.StubOutWithMock(drv, "_set_rw_permissions_for_all") mox.StubOutWithMock(drv, "_resize_image_file") mox.StubOutWithMock(image_utils, "convert_image") mox.StubOutWithMock(drv, "_register_image_in_cache") mox.StubOutWithMock(drv, "_is_share_vol_compatible") drv._find_image_in_cache(IgnoreArg()).AndReturn([]) drv._is_cloneable_share("nfs://127.0.0.1/share/img-id").AndReturn("127.0.0.1:/share") drv._is_share_vol_compatible(IgnoreArg(), IgnoreArg()).AndReturn(True) drv._get_mount_point_for_share("127.0.0.1:/share").AndReturn("/mnt") image_utils.qemu_img_info("/mnt/img-id").AndReturn(self.get_img_info("notraw")) image_utils.convert_image(IgnoreArg(), IgnoreArg(), "raw") image_utils.qemu_img_info("/mnt/vol").AndReturn(self.get_img_info("raw")) drv._register_image_in_cache(IgnoreArg(), IgnoreArg()) drv._get_mount_point_for_share("127.0.0.1:/share").AndReturn("/mnt") drv._discover_file_till_timeout(IgnoreArg()).AndReturn(True) drv._set_rw_permissions_for_all("/mnt/vol") drv._resize_image_file({"name": "vol"}, IgnoreArg()) mox.ReplayAll() drv.clone_image(volume, ("nfs://127.0.0.1/share/img-id", None), "image_id") mox.VerifyAll()
def test_copy_image_to_volume(self): """resize_image common case usage.""" mox = self._mox drv = self._driver TEST_IMG_SOURCE = 'foo.img' volume = {'size': self.TEST_SIZE_IN_GB, 'name': TEST_IMG_SOURCE} def fake_local_path(volume): return volume['name'] self.stubs.Set(drv, 'local_path', fake_local_path) mox.StubOutWithMock(image_utils, 'fetch_to_raw') image_utils.fetch_to_raw(None, None, None, TEST_IMG_SOURCE) mox.StubOutWithMock(image_utils, 'resize_image') image_utils.resize_image(TEST_IMG_SOURCE, self.TEST_SIZE_IN_GB) mox.StubOutWithMock(image_utils, 'qemu_img_info') data = mox_lib.MockAnything() data.virtual_size = 1024**3 image_utils.qemu_img_info(TEST_IMG_SOURCE).AndReturn(data) mox.ReplayAll() drv.copy_image_to_volume(None, volume, None, None) mox.VerifyAll()
def test_backup_volume(self): self.mox = mox_lib.Mox() self._driver.db = self.mox.CreateMockAnything() self.mox.StubOutWithMock(self._driver.db, 'volume_get') volume = {'id': '2', 'name': self.TEST_VOLNAME} self._driver.db.volume_get(context, volume['id']).AndReturn(volume) info = imageutils.QemuImgInfo() info.file_format = 'raw' self.mox.StubOutWithMock(image_utils, 'qemu_img_info') image_utils.qemu_img_info(self.TEST_VOLPATH).AndReturn(info) self.mox.StubOutWithMock(utils, 'temporary_chown') mock_tempchown = mock.MagicMock() utils.temporary_chown(self.TEST_VOLPATH).AndReturn(mock_tempchown) self.mox.StubOutWithMock(fileutils, 'file_open') mock_fileopen = mock.MagicMock() fileutils.file_open(self.TEST_VOLPATH).AndReturn(mock_fileopen) backup = {'volume_id': volume['id']} mock_servicebackup = self.mox.CreateMockAnything() mock_servicebackup.backup(backup, mox_lib.IgnoreArg()) self.mox.ReplayAll() self._driver.backup_volume(context, backup, mock_servicebackup)
def _direct_nfs_clone(self, volume, image_location, image_id): """Clone directly in nfs share.""" LOG.info(_('Cloning image %s directly in share'), image_id) cloned = False image_location = self._construct_image_nfs_url(image_location) share = self._is_cloneable_share(image_location) if share and self._is_share_eligible(share, volume['size']): LOG.debug(_('Share is cloneable %s'), share) volume['provider_location'] = share (__, ___, img_file) = image_location.rpartition('/') dir_path = self._get_mount_point_for_share(share) img_path = '%s/%s' % (dir_path, img_file) img_info = image_utils.qemu_img_info(img_path) if img_info.file_format == 'raw': LOG.debug(_('Image is raw %s'), image_id) self._clone_volume(img_file, volume['name'], volume_id=None, share=share) cloned = True else: LOG.info(_('Image will locally be converted to raw %s'), image_id) dst = '%s/%s' % (dir_path, volume['name']) image_utils.convert_image(img_path, dst, 'raw') data = image_utils.qemu_img_info(dst) if data.file_format != "raw": raise exception.InvalidResults( _("Converted to raw, but" " format is now %s") % data.file_format) else: cloned = True self._register_image_in_cache(volume, image_id) return cloned
def test_backup_volume(self): self.mox.StubOutWithMock(self._driver, "db") self.mox.StubOutWithMock(self._driver.db, "volume_get") volume = {"id": "2", "name": self.TEST_VOLNAME} self._driver.db.volume_get(context, volume["id"]).AndReturn(volume) info = imageutils.QemuImgInfo() info.file_format = "raw" self.mox.StubOutWithMock(image_utils, "qemu_img_info") image_utils.qemu_img_info(self.TEST_VOLPATH).AndReturn(info) self.mox.StubOutWithMock(utils, "temporary_chown") mock_tempchown = self.mox.CreateMockAnything() utils.temporary_chown(self.TEST_VOLPATH).AndReturn(mock_tempchown) mock_tempchown.__enter__() mock_tempchown.__exit__(None, None, None) self.mox.StubOutWithMock(fileutils, "file_open") mock_fileopen = self.mox.CreateMockAnything() fileutils.file_open(self.TEST_VOLPATH).AndReturn(mock_fileopen) mock_fileopen.__enter__() mock_fileopen.__exit__(None, None, None) backup = {"volume_id": volume["id"]} mock_servicebackup = self.mox.CreateMockAnything() mock_servicebackup.backup(backup, mox_lib.IgnoreArg()) self.mox.ReplayAll() self._driver.backup_volume(context, backup, mock_servicebackup) self.mox.VerifyAll()
def test_extend_volume(self): (mox, drv) = self._mox, self._driver volume = self._simple_volume() volume_path = '%s/%s/volume-%s' % (self.TEST_MNT_POINT_BASE, drv._get_hash_str( self.TEST_QUOBYTE_VOLUME), self.VOLUME_UUID) qemu_img_info_output = """image: volume-%s file format: qcow2 virtual size: 1.0G (1073741824 bytes) disk size: 473K """ % self.VOLUME_UUID img_info = imageutils.QemuImgInfo(qemu_img_info_output) mox.StubOutWithMock(drv, '_execute') mox.StubOutWithMock(drv, 'get_active_image_from_info') mox.StubOutWithMock(image_utils, 'qemu_img_info') mox.StubOutWithMock(image_utils, 'resize_image') drv.get_active_image_from_info(volume).AndReturn(volume['name']) image_utils.qemu_img_info(volume_path).AndReturn(img_info) image_utils.resize_image(volume_path, 3) mox.ReplayAll() drv.extend_volume(volume, 3) mox.VerifyAll()
def _cache_vol_2_2(self, context, vol, image_meta, image_service): image_id = image_meta['id'] # Pull down image and determine if valid with image_utils.TemporaryImages.fetch(image_service, context, image_id) as tmp_image: data = image_utils.qemu_img_info(tmp_image) 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, }) vsize = int( math.ceil(float(data.virtual_size) / units.Gi)) vol['size'] = vsize vtype = vol['volume_type_id'] LOG.info("Creating cached image with volume type: %(vtype)s and " "size %(size)s", {'vtype': vtype, 'size': vsize}) self._create_volume_2_2(vol) with self._connect_vol(context, vol) as device: LOG.debug("Moving image %s to volume %s", image_meta['id'], datc.get_name(vol)) image_utils.convert_image(tmp_image, device, 'raw', run_as_root=True) LOG.debug("Finished moving image %s to volume %s", image_meta['id'], datc.get_name(vol)) data = image_utils.qemu_img_info(device, run_as_root=True) if data.file_format != 'raw': raise exception.ImageUnacceptable( image_id=image_id, reason=_( "Converted to %(vol_format)s, but format is " "now %(file_format)s") % { 'vol_format': 'raw', 'file_format': data.file_format}) # TODO(_alastor_): Remove this snapshot creation when we fix # "created_at" attribute in the frontend # We don't actually care about the snapshot uuid, we just want # a single snapshot snapshot = {'id': str(uuid.uuid4()), 'volume_id': vol['id'], 'project_id': vol['project_id']} self._create_snapshot_2_2(snapshot) metadata = {'type': 'cached_image'} tenant = self.get_tenant(vol['project_id']) ai = self.cvol_to_ai(vol, tenant=tenant) ai.metadata.set(tenant=tenant, **metadata) # Cloning offline AI is ~4 seconds faster than cloning online AI self._detach_volume_2_2(None, vol)
def test_copy_volume_from_snapshot(self): (mox, drv) = self._mox, self._driver mox.StubOutWithMock(image_utils, 'convert_image') mox.StubOutWithMock(drv, '_read_info_file') mox.StubOutWithMock(image_utils, 'qemu_img_info') mox.StubOutWithMock(drv, '_set_rw_permissions_for_all') dest_volume = self._simple_volume( 'c1073000-0000-0000-0000-0000000c1073') src_volume = self._simple_volume() vol_dir = os.path.join(self.TEST_MNT_POINT_BASE, drv._get_hash_str(self.TEST_QUOBYTE_VOLUME)) src_vol_path = os.path.join(vol_dir, src_volume['name']) dest_vol_path = os.path.join(vol_dir, dest_volume['name']) info_path = os.path.join(vol_dir, src_volume['name']) + '.info' snapshot = {'volume_name': src_volume['name'], 'name': 'clone-snap-%s' % src_volume['id'], 'size': src_volume['size'], 'volume_size': src_volume['size'], 'volume_id': src_volume['id'], 'id': 'tmp-snap-%s' % src_volume['id'], 'volume': src_volume} snap_file = dest_volume['name'] + '.' + snapshot['id'] snap_path = os.path.join(vol_dir, snap_file) size = dest_volume['size'] drv._read_info_file(info_path).AndReturn( {'active': snap_file, snapshot['id']: snap_file} ) qemu_img_output = """image: %s file format: raw virtual size: 1.0G (1073741824 bytes) disk size: 173K backing file: %s """ % (snap_file, src_volume['name']) img_info = imageutils.QemuImgInfo(qemu_img_output) image_utils.qemu_img_info(snap_path).AndReturn(img_info) image_utils.convert_image(src_vol_path, dest_vol_path, 'raw', run_as_root=self.execute_as_root) drv._set_rw_permissions_for_all(dest_vol_path) mox.ReplayAll() drv._copy_volume_from_snapshot(snapshot, dest_volume, size) mox.VerifyAll()
def _direct_clone(self, volume, image_location, image_id, image_name): """Clones directly in nfs share.""" LOG.info(_LI('Checking image clone %s from glance share.'), image_id) cloned = False image_location = self._get_image_nfs_url(image_location) share = self._is_cloneable_share(image_location) run_as_root = self._execute_as_root dst_share = None for dst in self._mounted_shares: if dst and self._is_share_vol_compatible(volume, dst): dst_share = dst LOG.debug('Image dst share: %s', dst) break if not dst_share: return cloned LOG.debug('Share is cloneable %s', dst_share) volume['provider_location'] = dst_share (__, ___, img_file) = image_location.rpartition('/') dir_path = self._get_mount_point_for_share(share) dst_path = self._get_mount_point_for_share(dst_share) img_path = '%s/%s' % (dir_path, img_file) img_info = image_utils.qemu_img_info(img_path, run_as_root=run_as_root) if img_info.file_format == 'raw': LOG.debug('Image is raw %s', image_id) self._clone_volume_to_volume(img_file, volume['name'], image_name, volume_id=None, share=share, dst=dst_share, image_id=image_id) cloned = True else: LOG.info(_LI('Image will locally be converted to raw %s'), image_id) dst = '%s/%s' % (dst_path, volume['name']) image_utils.convert_image(img_path, dst, 'raw', run_as_root=run_as_root) data = image_utils.qemu_img_info(dst, run_as_root=run_as_root) if data.file_format != "raw": raise exception.InvalidResults( _('Converted to raw, but ' 'format is now %s') % data.file_format) else: cloned = True self._create_image_snapshot(volume['name'], volume['provider_location'], image_id, image_name) return cloned
def _gpfs_fetch_to_raw(self, context, image_service, image_id, dest, user_id=None, project_id=None): if (self.configuration.image_conversion_dir and not os.path.exists(self.configuration.image_conversion_dir)): os.makedirs(self.configuration.image_conversion_dir) tmp = "%s.part" % dest with fileutils.remove_path_on_error(tmp): self._gpfs_fetch(context, image_service, image_id, tmp, user_id, project_id) data = image_utils.qemu_img_info(tmp) fmt = data.file_format backing_file = data.backing_file if backing_file is not None: msg = (_("fmt = %(fmt)s backed by: %(backing_file)s") % { 'fmt': fmt, 'backing_file': backing_file }) LOG.error(msg) raise exception.ImageUnacceptable(image_id=image_id, reason=msg) if fmt is None: msg = _("'qemu-img info' parsing failed.") LOG.error(msg) raise exception.ImageUnacceptable(reason=msg, image_id=image_id) elif fmt == 'raw': # already in raw format - just rename to dest self._execute('mv', tmp, dest, run_as_root=True) else: # conversion to raw format required LOG.debug("%s was %s, converting to raw" % (image_id, fmt)) image_utils.convert_image(tmp, dest, 'raw') os.unlink(tmp) data = image_utils.qemu_img_info(dest) if data.file_format != "raw": msg = (_("Expected image to be in raw format, but is %s") % data.file_format) LOG.error(msg) raise exception.ImageUnacceptable(image_id=image_id, reason=msg) return {'size': math.ceil(data.virtual_size / 1024.0**3)}
def _direct_clone(self, volume, image_location, image_id, image_name): """Clones directly in nfs share.""" LOG.info('Checking image clone %s from glance share.', image_id) cloned = False image_location = self._get_image_nfs_url(image_location) share = self._is_cloneable_share(image_location) run_as_root = self._execute_as_root dst_share = None for dst in self._mounted_shares: if dst and self._is_share_vol_compatible(volume, dst): dst_share = dst LOG.debug('Image dst share: %s', dst) break if not dst_share: return cloned LOG.debug('Share is cloneable %s', dst_share) volume['provider_location'] = dst_share (__, ___, img_file) = image_location.rpartition('/') dir_path = self._get_mount_point_for_share(share) dst_path = self._get_mount_point_for_share(dst_share) img_path = '%s/%s' % (dir_path, img_file) img_info = image_utils.qemu_img_info(img_path, run_as_root=run_as_root) if img_info.file_format == 'raw': LOG.debug('Image is raw %s', image_id) self._clone_volume_to_volume( img_file, volume['name'], image_name, volume_id=None, share=share, dst=dst_share, image_id=image_id) cloned = True else: LOG.info('Image will locally be converted to raw %s', image_id) dst = '%s/%s' % (dst_path, volume['name']) image_utils.convert_image(img_path, dst, 'raw', run_as_root=run_as_root) data = image_utils.qemu_img_info(dst, run_as_root=run_as_root) if data.file_format != "raw": raise exception.InvalidResults( _('Converted to raw, but ' 'format is now %s') % data.file_format) else: cloned = True self._create_image_snapshot( volume['name'], volume['provider_location'], image_id, image_name) return cloned
def copy_image_to_volume(self, context, volume, image_service, image_id): """Fetch the image from image_service and write it to the volume.""" volume_format = self.get_volume_format(volume, qemu_format=True) image_meta = image_service.show(context, image_id) qemu_version = self.get_qemu_version() if (qemu_version < [1, 7] and (volume_format == self._DISK_FORMAT_VHDX and image_meta['disk_format'] != volume_format)): err_msg = _("Unsupported volume format: vhdx. qemu-img 1.7 or " "higher is required in order to properly support this " "format.") raise exception.InvalidVolume(err_msg) image_utils.fetch_to_volume_format( context, image_service, image_id, self.local_path(volume), volume_format, self.configuration.volume_dd_blocksize) self._do_extend_volume(self.local_path(volume), volume['size'], volume['name']) data = image_utils.qemu_img_info(self.local_path(volume)) virt_size = data.virtual_size / units.Gi if virt_size != volume['size']: raise exception.ImageUnacceptable( image_id=image_id, reason=(_("Expected volume size was %d") % volume['size']) + (_(" but size is now %d.") % virt_size))
def optimize_volume(self, volume): """Optimizes a volume for Quobyte This optimization is normally done during creation but volumes created from e.g. snapshots require additional grooming. :param volume: volume reference """ volume_path = self.local_path(volume) volume_size = volume.size data = image_utils.qemu_img_info(self.local_path(volume), run_as_root=self._execute_as_root) if data.disk_size >= (volume_size * units.Gi): LOG.debug( "Optimization of volume %(volpath)s is not required, " "skipping this step.", {'volpath': volume_path}) return LOG.debug("Optimizing volume %(optpath)s", {'optpath': volume_path}) if (self.configuration.quobyte_qcow2_volumes or self.configuration.quobyte_sparsed_volumes): self._execute('truncate', '-s', '%sG' % volume_size, volume_path, run_as_root=self._execute_as_root) else: self._create_regular_file(volume_path, volume_size)
def copy_image_to_volume(self, context, volume, image_service, image_id): """Fetch the image from image_service and write it to the volume.""" image_utils.fetch_to_raw(context, image_service, image_id, self.local_path(volume), self.configuration.volume_dd_blocksize, size=volume['size']) # NOTE (leseb): Set the virtual size of the image # the raw conversion overwrote the destination file # (which had the correct size) # with the fetched glance image size, # thus the initial 'size' parameter is not honored # this sets the size to the one asked in the first place by the user # and then verify the final virtual size image_utils.resize_image(self.local_path(volume), volume['size']) data = image_utils.qemu_img_info(self.local_path(volume)) virt_size = data.virtual_size / units.GiB if virt_size != volume['size']: raise exception.ImageUnacceptable( image_id=image_id, reason=(_("Expected volume size was %d") % volume['size']) + (_(" but size is now %d") % virt_size))
def _qemu_img_info_base(self, path, volume_name, basedir): """Sanitize image_utils' qemu_img_info. This code expects to deal only with relative filenames. """ info = image_utils.qemu_img_info(path) if info.image: info.image = os.path.basename(info.image) if info.backing_file: backing_file_template = \ "(%(basedir)s/[0-9a-f]+/)?%" \ "(volname)s(.(tmp-snap-)?[0-9a-f-]+)?$" % { 'basedir': basedir, 'volname': volume_name } if not re.match(backing_file_template, info.backing_file): msg = _("File %(path)s has invalid backing file " "%(bfile)s, aborting.") % {'path': path, 'bfile': info.backing_file} raise exception.RemoteFSException(msg) info.backing_file = os.path.basename(info.backing_file) return info
def _qemu_img_info_base(self, path, volume_name, basedir): """Sanitize image_utils' qemu_img_info. This code expects to deal only with relative filenames. """ info = image_utils.qemu_img_info(path) if info.image: info.image = os.path.basename(info.image) if info.backing_file: backing_file_template = \ "(%(basedir)s/[0-9a-f]+/)?%" \ "(volname)s(.(tmp-snap-)?[0-9a-f-]+)?$" % { 'basedir': basedir, 'volname': volume_name } if not re.match(backing_file_template, info.backing_file): msg = _("File %(path)s has invalid backing file " "%(bfile)s, aborting.") % { 'path': path, 'bfile': info.backing_file } raise exception.GlusterfsException(msg) info.backing_file = os.path.basename(info.backing_file) return info
def optimize_volume(self, volume): """Optimizes a volume for Quobyte This optimization is normally done during creation but volumes created from e.g. snapshots require additional grooming. :param volume: volume reference """ volume_path = self.local_path(volume) volume_size = volume.size data = image_utils.qemu_img_info(self.local_path(volume), run_as_root=self._execute_as_root) if data.disk_size >= (volume_size * units.Gi): LOG.debug("Optimization of volume %(volpath)s is not required, " "skipping this step.", {'volpath': volume_path}) return LOG.debug("Optimizing volume %(optpath)s", {'optpath': volume_path}) if (self.configuration.quobyte_qcow2_volumes or self.configuration.quobyte_sparsed_volumes): self._execute('truncate', '-s', '%sG' % volume_size, volume_path, run_as_root=self._execute_as_root) else: self._create_regular_file(volume_path, volume_size)
def copy_image_to_volume(self, context, volume, image_service, image_id): """Fetch the image from image_service and write it to the volume.""" image_utils.fetch_to_raw(context, image_service, image_id, self.local_path(volume), self.configuration.volume_dd_blocksize, size=volume['size']) # NOTE (leseb): Set the virtual size of the image # the raw conversion overwrote the destination file # (which had the correct size) # with the fetched glance image size, # thus the initial 'size' parameter is not honored # this sets the size to the one asked in the first place by the user # and then verify the final virtual size image_utils.resize_image(self.local_path(volume), volume['size']) data = image_utils.qemu_img_info(self.local_path(volume)) virt_size = data.virtual_size / units.Gi if virt_size != volume['size']: raise exception.ImageUnacceptable( image_id=image_id, reason=(_("Expected volume size was %d") % volume['size']) + (_(" but size is now %d") % virt_size))
def copy_image_to_volume(self, context, volume, image_service, image_id): """Fetch the image from image_service and write it to the volume.""" volume_format = self.get_volume_format(volume, qemu_format=True) image_meta = image_service.show(context, image_id) qemu_version = self.get_qemu_version() if qemu_version < [1, 7] and ( volume_format == self._DISK_FORMAT_VHDX and image_meta["disk_format"] != volume_format ): err_msg = _( "Unsupported volume format: vhdx. qemu-img 1.7 or " "higher is required in order to properly support this " "format." ) raise exception.InvalidVolume(err_msg) image_utils.fetch_to_volume_format( context, image_service, image_id, self.local_path(volume), volume_format, self.configuration.volume_dd_blocksize, ) self._do_extend_volume(self.local_path(volume), volume["size"], volume["name"]) data = image_utils.qemu_img_info(self.local_path(volume)) virt_size = data.virtual_size / units.Gi if virt_size != volume["size"]: raise exception.ImageUnacceptable( image_id=image_id, reason=(_("Expected volume size was %d") % volume["size"]) + (_(" but size is now %d.") % virt_size), )
def create_volume_from_snapshot(self, volume, snapshot): """Creates a GPFS volume from a snapshot.""" volume_path = self.local_path(volume) snapshot_path = self.local_path(snapshot) self._create_gpfs_copy(src=snapshot_path, dest=volume_path) self._gpfs_redirect(volume_path) data = image_utils.qemu_img_info(volume_path) return {'size': math.ceil(data.virtual_size / 1024.0 ** 3)}
def test_clone_image_resizefails(self): drv = self._driver mox = self.mox volume = {'name': 'vol', 'size': '20'} mox.StubOutWithMock(drv, '_find_image_in_cache') mox.StubOutWithMock(drv, '_is_cloneable_share') mox.StubOutWithMock(drv, '_get_mount_point_for_share') mox.StubOutWithMock(image_utils, 'qemu_img_info') mox.StubOutWithMock(drv, '_clone_volume') mox.StubOutWithMock(drv, '_discover_file_till_timeout') mox.StubOutWithMock(drv, '_set_rw_permissions_for_all') mox.StubOutWithMock(drv, '_resize_image_file') mox.StubOutWithMock(image_utils, 'convert_image') mox.StubOutWithMock(drv, '_register_image_in_cache') mox.StubOutWithMock(drv, '_is_share_vol_compatible') mox.StubOutWithMock(drv, 'local_path') mox.StubOutWithMock(os.path, 'exists') mox.StubOutWithMock(drv, '_delete_file') drv._find_image_in_cache(IgnoreArg()).AndReturn([]) drv._is_cloneable_share('nfs://127.0.0.1/share/img-id').AndReturn( '127.0.0.1:/share') drv._is_share_vol_compatible(IgnoreArg(), IgnoreArg()).AndReturn(True) drv._get_mount_point_for_share('127.0.0.1:/share').AndReturn('/mnt') image_utils.qemu_img_info('/mnt/img-id').AndReturn( self.get_img_info('notraw')) image_utils.convert_image(IgnoreArg(), IgnoreArg(), 'raw') image_utils.qemu_img_info('/mnt/vol').AndReturn( self.get_img_info('raw')) drv._register_image_in_cache(IgnoreArg(), IgnoreArg()) drv.local_path(IgnoreArg()).AndReturn('/mnt/vol') drv._discover_file_till_timeout(IgnoreArg()).AndReturn(True) drv._set_rw_permissions_for_all('/mnt/vol') drv._resize_image_file(IgnoreArg(), IgnoreArg()).AndRaise( exception.InvalidResults()) drv.local_path(IgnoreArg()).AndReturn('/mnt/vol') os.path.exists('/mnt/vol').AndReturn(True) drv._delete_file('/mnt/vol') mox.ReplayAll() vol_dict, result = drv.clone_image( volume, ('nfs://127.0.0.1/share/img-id', None), 'image_id', {}) mox.VerifyAll() self.assertFalse(result) self.assertFalse(vol_dict['bootable']) self.assertIsNone(vol_dict['provider_location'])
def _is_file_size_equal(self, path, size): """Checks if file size at path is equal to size.""" data = image_utils.qemu_img_info(path) virt_size = data.virtual_size / units.GiB if virt_size == size: return True else: return False
def test_clone_image_resizefails(self): drv = self._driver mox = self.mox volume = {'name': 'vol', 'size': '20'} mox.StubOutWithMock(drv, '_find_image_in_cache') mox.StubOutWithMock(drv, '_is_cloneable_share') mox.StubOutWithMock(drv, '_get_mount_point_for_share') mox.StubOutWithMock(image_utils, 'qemu_img_info') mox.StubOutWithMock(drv, '_clone_volume') mox.StubOutWithMock(drv, '_discover_file_till_timeout') mox.StubOutWithMock(drv, '_set_rw_permissions_for_all') mox.StubOutWithMock(drv, '_resize_image_file') mox.StubOutWithMock(image_utils, 'convert_image') mox.StubOutWithMock(drv, '_register_image_in_cache') mox.StubOutWithMock(drv, '_is_share_vol_compatible') mox.StubOutWithMock(drv, 'local_path') mox.StubOutWithMock(os.path, 'exists') mox.StubOutWithMock(drv, '_delete_file') drv._find_image_in_cache(IgnoreArg()).AndReturn([]) drv._is_cloneable_share('nfs://127.0.0.1/share/img-id').AndReturn( '127.0.0.1:/share') drv._is_share_vol_compatible(IgnoreArg(), IgnoreArg()).AndReturn(True) drv._get_mount_point_for_share('127.0.0.1:/share').AndReturn('/mnt') image_utils.qemu_img_info('/mnt/img-id').AndReturn( self.get_img_info('notraw')) image_utils.convert_image(IgnoreArg(), IgnoreArg(), 'raw') image_utils.qemu_img_info('/mnt/vol').AndReturn( self.get_img_info('raw')) drv._register_image_in_cache(IgnoreArg(), IgnoreArg()) drv.local_path(IgnoreArg()).AndReturn('/mnt/vol') drv._discover_file_till_timeout(IgnoreArg()).AndReturn(True) drv._set_rw_permissions_for_all('/mnt/vol') drv._resize_image_file( IgnoreArg(), IgnoreArg()).AndRaise(exception.InvalidResults()) drv.local_path(IgnoreArg()).AndReturn('/mnt/vol') os.path.exists('/mnt/vol').AndReturn(True) drv._delete_file('/mnt/vol') mox.ReplayAll() vol_dict, result = drv. clone_image( volume, ('nfs://127.0.0.1/share/img-id', None), 'image_id', {}) mox.VerifyAll() self.assertFalse(result) self.assertFalse(vol_dict['bootable']) self.assertIsNone(vol_dict['provider_location'])
def _gpfs_fetch_to_raw(self, context, image_service, image_id, dest, user_id=None, project_id=None): if (self.configuration.image_conversion_dir and not os.path.exists(self.configuration.image_conversion_dir)): os.makedirs(self.configuration.image_conversion_dir) tmp = "%s.part" % dest with fileutils.remove_path_on_error(tmp): self._gpfs_fetch(context, image_service, image_id, tmp, user_id, project_id) data = image_utils.qemu_img_info(tmp) fmt = data.file_format backing_file = data.backing_file if backing_file is not None: msg = (_("fmt = %(fmt)s backed by: %(backing_file)s") % {'fmt': fmt, 'backing_file': backing_file}) LOG.error(msg) raise exception.ImageUnacceptable( image_id=image_id, reason=msg) if fmt is None: msg = _("'qemu-img info' parsing failed.") LOG.error(msg) raise exception.ImageUnacceptable( reason=msg, image_id=image_id) elif fmt == 'raw': # already in raw format - just rename to dest self._execute('mv', tmp, dest, run_as_root=True) else: # conversion to raw format required LOG.debug("%s was %s, converting to raw" % (image_id, fmt)) image_utils.convert_image(tmp, dest, 'raw') os.unlink(tmp) data = image_utils.qemu_img_info(dest) if data.file_format != "raw": msg = (_("Expected image to be in raw format, but is %s") % data.file_format) LOG.error(msg) raise exception.ImageUnacceptable( image_id=image_id, reason=msg) return {'size': math.ceil(data.virtual_size / 1024.0 ** 3)}
def _direct_nfs_clone(self, volume, image_location, image_id): """Clone directly in nfs share.""" LOG.info(_LI('Checking image clone %s from glance share.'), image_id) cloned = False image_locations = self._construct_image_nfs_url(image_location) run_as_root = self._execute_as_root for loc in image_locations: share = self._is_cloneable_share(loc) if share and self._is_share_clone_compatible(volume, share): LOG.debug('Share is cloneable %s', share) volume['provider_location'] = share (__, ___, img_file) = loc.rpartition('/') dir_path = self._get_mount_point_for_share(share) img_path = '%s/%s' % (dir_path, img_file) img_info = image_utils.qemu_img_info(img_path, run_as_root=run_as_root) if img_info.file_format == 'raw': LOG.debug('Image is raw %s', image_id) self._clone_backing_file_for_volume(img_file, volume['name'], volume_id=None, share=share) cloned = True break else: LOG.info(_LI('Image will locally be converted to raw %s'), image_id) dst = '%s/%s' % (dir_path, volume['name']) image_utils.convert_image(img_path, dst, 'raw', run_as_root=run_as_root) data = image_utils.qemu_img_info(dst, run_as_root=run_as_root) if data.file_format != "raw": raise exception.InvalidResults( _("Converted to raw, but" " format is now %s") % data.file_format) else: cloned = True self._register_image_in_cache(volume, image_id) break return cloned
def test_extend_volume(self): new_vol_size = 15 mox = mox_lib.Mox() volume = test_utils.create_volume(self.context, host=CONF.host) volpath = os.path.join(self.volumes_path, volume['name']) qemu_img_info_output = """image: %s file format: raw virtual size: %sG (%s bytes) backing file: %s """ % (volume['name'], new_vol_size, new_vol_size * units.GiB, volpath) mox.StubOutWithMock(image_utils, 'resize_image') image_utils.resize_image(volpath, new_vol_size) mox.StubOutWithMock(image_utils, 'qemu_img_info') img_info = imageutils.QemuImgInfo(qemu_img_info_output) image_utils.qemu_img_info(volpath).AndReturn(img_info) mox.ReplayAll() self.driver.extend_volume(volume, new_vol_size) mox.VerifyAll()
def test_qemu_img_info_alt(self): """Test a slightly different variation of qemu-img output. (Based on Fedora 19's qemu-img 1.4.2.) """ TEST_PATH = "img/qemu.qcow2" TEST_RETURN = "image: qemu.qcow2\n"\ "backing file: qemu.qcow2 (actual path: qemu.qcow2)\n"\ "file format: qcow2\n"\ "virtual size: 50M (52428800 bytes)\n"\ "cluster_size: 65536\n"\ "disk size: 196K (200704 bytes)\n"\ "Snapshot list:\n"\ "ID TAG VM SIZE DATE VM CLOCK\n"\ "1 snap1 1.7G 2011-10-04 19:04:00 32:06:34.974" TEST_STR = "image: qemu.qcow2\n"\ "file_format: qcow2\n"\ "virtual_size: 52428800\n"\ "disk_size: 200704\n"\ "cluster_size: 65536\n"\ "backing_file: qemu.qcow2\n"\ "snapshots: [{'date': '2011-10-04', "\ "'vm_clock': '19:04:00 32:06:34.974', "\ "'vm_size': '1.7G', 'tag': 'snap1', 'id': '1'}]" mox = self._mox mox.StubOutWithMock(utils, 'execute') cmd = ['env', 'LC_ALL=C', 'LANG=C', 'qemu-img', 'info', TEST_PATH] utils.execute(*cmd, run_as_root=True).AndReturn( (TEST_RETURN, 'ignored')) mox.ReplayAll() inf = image_utils.qemu_img_info(TEST_PATH) self.assertEquals(inf.image, 'qemu.qcow2') self.assertEquals(inf.backing_file, 'qemu.qcow2') self.assertEquals(inf.file_format, 'qcow2') self.assertEquals(inf.virtual_size, 52428800) self.assertEquals(inf.cluster_size, 65536) self.assertEquals(inf.disk_size, 200704) self.assertEquals(inf.snapshots[0]['id'], '1') self.assertEquals(inf.snapshots[0]['tag'], 'snap1') self.assertEquals(inf.snapshots[0]['vm_size'], '1.7G') self.assertEquals(inf.snapshots[0]['date'], '2011-10-04') self.assertEquals(inf.snapshots[0]['vm_clock'], '19:04:00 32:06:34.974') self.assertEquals(str(inf), TEST_STR)
def _qemu_img_info(self, path): """Sanitize image_utils' qemu_img_info. This code expects to deal only with relative filenames. """ info = image_utils.qemu_img_info(path) if info.image: info.image = os.path.basename(info.image) if info.backing_file: info.backing_file = os.path.basename(info.backing_file) return info
def _resize_volume_file(self, volume, new_size): """Resize volume file to new size.""" vol_path = self.local_path(volume) try: image_utils.resize_image(vol_path, new_size) except processutils.ProcessExecutionError as exc: LOG.error(_("Failed to resize volume " "%(volume_id)s, error: %(error)s") % {'volume_id': volume['id'], 'error': exc.stderr}) raise exception.VolumeBackendAPIException(data=exc.stderr) data = image_utils.qemu_img_info(vol_path) return data.virtual_size
def _clone_image(self, volume, image_location, image_id): """Attempt to create a volume by efficiently copying image to volume. If both source and target are backed by gpfs storage and the source image is in raw format move the image to create a volume using either gpfs clone operation or with a file copy. If the image format is not raw, convert it to raw at the volume path. """ # Check if GPFS is mounted self._verify_gpfs_path_state(self.configuration.gpfs_mount_point_base) cloneable_image, reason, image_path = self._is_cloneable(image_id) if not cloneable_image: LOG.debug('Image %(img)s not cloneable: %(reas)s.' % { 'img': image_id, 'reas': reason }) return (None, False) vol_path = self.local_path(volume) data = image_utils.qemu_img_info(image_path) # if image format is already raw either clone it or # copy it depending on config file settings if data.file_format == 'raw': if (self.configuration.gpfs_images_share_mode == 'copy_on_write'): LOG.debug('Clone image to vol %s using mmclone.' % volume['id']) # if the image is not already a GPFS snap file make it so if not self._is_gpfs_parent_file(image_path): self._create_gpfs_snap(image_path) self._create_gpfs_copy(image_path, vol_path) elif self.configuration.gpfs_images_share_mode == 'copy': LOG.debug('Clone image to vol %s using copyfile.' % volume['id']) shutil.copyfile(image_path, vol_path) # if image is not raw convert it to raw into vol_path destination else: LOG.debug('Clone image to vol %s using qemu convert.' % volume['id']) image_utils.convert_image(image_path, vol_path, 'raw') self._set_rw_permission(vol_path) self._resize_volume_file(volume, volume['size']) return {'provider_location': None}, True
def _direct_nfs_clone(self, volume, image_location, image_id): """Clone directly in nfs share.""" LOG.info(_LI('Checking image clone %s from glance share.'), image_id) cloned = False image_locations = self._construct_image_nfs_url(image_location) run_as_root = self._execute_as_root for loc in image_locations: share = self._is_cloneable_share(loc) if share and self._is_share_clone_compatible(volume, share): LOG.debug('Share is cloneable %s', share) volume['provider_location'] = share (__, ___, img_file) = loc.rpartition('/') dir_path = self._get_mount_point_for_share(share) img_path = '%s/%s' % (dir_path, img_file) img_info = image_utils.qemu_img_info(img_path, run_as_root=run_as_root) if img_info.file_format == 'raw': LOG.debug('Image is raw %s', image_id)
def copy_image_to_volume(self, context, volume, image_service, image_id): """Fetch the image from image_service and write it to the volume.""" volume_format = self.get_volume_format(volume, qemu_format=True) image_utils.fetch_to_volume_format( context, image_service, image_id, self.local_path(volume), volume_format, self.configuration.volume_dd_blocksize) self._do_extend_volume(self.local_path(volume), volume['size'], volume['name']) data = image_utils.qemu_img_info(self.local_path(volume)) virt_size = data.virtual_size / units.Gi if virt_size != volume['size']: raise exception.ImageUnacceptable( image_id=image_id, reason=(_("Expected volume size was %d") % volume['size']) + (_(" but size is now %d.") % virt_size))
def backup_volume(self, context, backup, backup_service): """Create a new backup from an existing volume.""" volume = self.db.volume_get(context, backup['volume_id']) volume_local_path = self.local_path(volume) LOG.info(_LI('Begin backup of volume %s.') % volume['name']) qemu_img_info = image_utils.qemu_img_info(volume_local_path) if qemu_img_info.file_format != 'raw': msg = _('Backup is only supported for raw-formatted ' 'SOFS volumes.') raise exception.InvalidVolume(msg) if qemu_img_info.backing_file is not None: msg = _('Backup is only supported for SOFS volumes ' 'without backing file.') raise exception.InvalidVolume(msg) with utils.temporary_chown(volume_local_path): with fileutils.file_open(volume_local_path) as volume_file: backup_service.backup(backup, volume_file)
def copy_image_to_volume(self, context, volume, image_service, image_id): """Fetch the image from image_service and write it to the volume.""" img_cache_volume = {'name': 'img-%s' % image_id} img_cache_snapshot = {'volume_name': img_cache_volume['name'], 'name': 'snap'} if self._list_snapshots(volume): # If the volume has snapshots, there is no efficient way to replace # the contents. Do things the inefficient way. image_utils.fetch_to_raw(context, image_service, image_id, self.local_path(volume), self.configuration.volume_dd_blocksize, size=volume['size']) else: if not self.snapshot_exists(img_cache_snapshot): with image_utils.temporary_file() as tmp: image_utils.fetch_verify_image(context, image_service, image_id, tmp) qemu_info = image_utils.qemu_img_info(tmp) virtual_size_gb = math.ceil( qemu_info.virtual_size / (1024.0 ** 3)) img_cache_volume['size'] = virtual_size_gb self.create_volume(img_cache_volume) image_utils.convert_image( tmp, self.local_path(img_cache_volume), 'raw', None, sparse=64 * 1024) self.create_snapshot(img_cache_snapshot) self.delete_volume(volume) self.create_volume_from_snapshot(volume, img_cache_snapshot) self.extend_volume(volume, volume['size'])
def _create_from_image_cache_or_download(self, context, volume, image_location, image_id, image_meta, image_service): # Try and use the image cache. should_create_cache_entry = False cloned = False model_update = None if self.image_volume_cache: internal_context = cinder_context.get_internal_tenant_context() if not internal_context: LOG.info('Unable to get Cinder internal context, will ' 'not use image-volume cache.') else: model_update, cloned = self._create_from_image_cache( context, internal_context, volume, image_id, image_meta ) # Don't cache encrypted volume. if not cloned and not volume.encryption_key_id: should_create_cache_entry = True # cleanup consistencygroup field in the volume, # because when creating cache entry, it will need # to update volume object. self._cleanup_cg_in_volume(volume) # Fall back to default behavior of creating volume, # download the image data and copy it into the volume. original_size = volume.size backend_name = volume_utils.extract_host(volume.service_topic_queue) try: if not cloned: try: with image_utils.TemporaryImages.fetch( image_service, context, image_id, backend_name) as tmp_image: # Try to create the volume as the minimal size, # then we can extend once the image has been # downloaded. data = image_utils.qemu_img_info(tmp_image) virtual_size = image_utils.check_virtual_size( data.virtual_size, volume.size, image_id) if should_create_cache_entry: if virtual_size and virtual_size != original_size: volume.size = virtual_size volume.save() model_update = self._create_from_image_download( context, volume, image_location, image_meta, image_service ) except exception.ImageTooBig as e: with excutils.save_and_reraise_exception(): self.message.create( context, message_field.Action.COPY_IMAGE_TO_VOLUME, resource_uuid=volume.id, detail= message_field.Detail.NOT_ENOUGH_SPACE_FOR_IMAGE, exception=e) if should_create_cache_entry: # Update the newly created volume db entry before we clone it # for the image-volume creation. if model_update: volume.update(model_update) volume.save() self.manager._create_image_cache_volume_entry(internal_context, volume, image_id, image_meta) finally: # If we created the volume as the minimal size, extend it back to # what was originally requested. If an exception has occurred or # extending it back failed, we still need to put this back before # letting it be raised further up the stack. if volume.size != original_size: try: self.driver.extend_volume(volume, original_size) finally: volume.size = original_size volume.save() return model_update
def _copy_from_img_service(self, context, volume, image_service, image_id): """Copies from the image service using copy offload.""" LOG.debug("Trying copy from image service using copy offload.") image_loc = image_service.get_location(context, image_id) locations = self._construct_image_nfs_url(image_loc) src_ip = None selected_loc = None cloned = False # this will match the first location that has a valid IP on cluster for location in locations: conn, dr = self._check_get_nfs_path_segs(location) if conn: try: src_ip = self._get_ip_verify_on_cluster(conn.split(':')[0]) selected_loc = location break except exception.NotFound: pass if src_ip is None: raise exception.NotFound(_("Source host details not found.")) (__, ___, img_file) = selected_loc.rpartition('/') src_path = os.path.join(dr, img_file) dst_ip, vol_path = self._get_destination_ip_and_path(volume) share_path = vol_path.rsplit("/", 1)[0] dst_share = dst_ip + ':' + share_path # tmp file is required to deal with img formats tmp_img_file = six.text_type(uuid.uuid4()) col_path = self.configuration.netapp_copyoffload_tool_path img_info = image_service.show(context, image_id) self._check_share_can_hold_size(dst_share, img_info['size']) run_as_root = self._execute_as_root dst_dir = self._get_mount_point_for_share(dst_share) dst_img_local = os.path.join(dst_dir, tmp_img_file) try: dst_img_serv_path = os.path.join(share_path, tmp_img_file) # Always run copy offload as regular user, it's sufficient # and rootwrap doesn't allow copy offload to run as root # anyways. self._execute(col_path, src_ip, dst_ip, src_path, dst_img_serv_path, run_as_root=False, check_exit_code=0) self._discover_file_till_timeout(dst_img_local, timeout=120) LOG.debug('Copied image %(img)s to tmp file %(tmp)s.', { 'img': image_id, 'tmp': tmp_img_file }) dst_img_cache_local = os.path.join(dst_dir, 'img-cache-%s' % image_id) if img_info['disk_format'] == 'raw': LOG.debug('Image is raw %s.', image_id) self._clone_file_dst_exists(dst_share, tmp_img_file, volume['name'], dest_exists=True) self._move_nfs_file(dst_img_local, dst_img_cache_local) LOG.debug('Copied raw image %(img)s to volume %(vol)s.', { 'img': image_id, 'vol': volume['id'] }) else: LOG.debug('Image will be converted to raw %s.', image_id) img_conv = six.text_type(uuid.uuid4()) dst_img_conv_local = os.path.join(dst_dir, img_conv) # Checking against image size which is approximate check self._check_share_can_hold_size(dst_share, img_info['size']) try: image_utils.convert_image(dst_img_local, dst_img_conv_local, 'raw', run_as_root=run_as_root) data = image_utils.qemu_img_info(dst_img_conv_local, run_as_root=run_as_root) if data.file_format != "raw": raise exception.InvalidResults( _("Converted to raw, but format is now %s.") % data.file_format) else: self._clone_file_dst_exists(dst_share, img_conv, volume['name'], dest_exists=True) self._move_nfs_file(dst_img_conv_local, dst_img_cache_local) LOG.debug( 'Copied locally converted raw image' ' %(img)s to volume %(vol)s.', { 'img': image_id, 'vol': volume['id'] }) finally: if os.path.exists(dst_img_conv_local): self._delete_file_at_path(dst_img_conv_local) cloned = True finally: if os.path.exists(dst_img_local): self._delete_file_at_path(dst_img_local) return cloned
def _is_file_size_equal(self, path, size): """Checks if file size at path is equal to size.""" data = image_utils.qemu_img_info(path, run_as_root=self._execute_as_root) virt_size = int(data.virtual_size / units.Gi) return virt_size == size