def qemu_img_info(path, run_as_root=True): """Return an object containing the parsed output from qemu-img info.""" cmd = ['env', 'LC_ALL=C', 'qemu-img', 'info', path] if os.name == 'nt': cmd = cmd[2:] out, _err = utils.execute(*cmd, run_as_root=run_as_root, prlimit=QEMU_IMG_LIMITS) info = imageutils.QemuImgInfo(out) # From Cinder's point of view, any 'luks' formatted images # should be treated as 'raw'. if info.file_format == 'luks': info.file_format = 'raw' return info
def qemu_img_info(path, format=None): """Return an object containing the parsed output from qemu-img info.""" # TODO(mikal): this code should not be referring to a libvirt specific # flag. if not os.path.exists(path) and CONF.libvirt.images_type != 'rbd': raise exception.DiskNotFound(location=path) try: # The following check is about ploop images that reside within # directories and always have DiskDescriptor.xml file beside them if (os.path.isdir(path) and os.path.exists(os.path.join(path, "DiskDescriptor.xml"))): path = os.path.join(path, "root.hds") cmd = ('env', 'LC_ALL=C', 'LANG=C', 'qemu-img', 'info', path) if format is not None: cmd = cmd + ('-f', format) # Check to see if the qemu version is >= 2.10 because if so, we need # to add the --force-share flag. if QEMU_VERSION and operator.ge(QEMU_VERSION, QEMU_VERSION_REQ_SHARED): cmd = cmd + ('--force-share', ) out, err = utils.execute(*cmd, prlimit=QEMU_IMG_LIMITS) except processutils.ProcessExecutionError as exp: # this means we hit prlimits, make the exception more specific if exp.exit_code == -9: msg = (_("qemu-img aborted by prlimits when inspecting " "%(path)s : %(exp)s") % { 'path': path, 'exp': exp }) else: msg = (_("qemu-img failed to execute on %(path)s : %(exp)s") % { 'path': path, 'exp': exp }) raise exception.InvalidDiskInfo(reason=msg) if not out: msg = (_("Failed to run qemu-img info on %(path)s : %(error)s") % { 'path': path, 'error': err }) raise exception.InvalidDiskInfo(reason=msg) return imageutils.QemuImgInfo(out)
def test_copy_volume_to_image_raw_image(self): drv = self._driver volume = self._simple_volume() volume_path = '%s/%s' % (self.TEST_MNT_POINT, volume['name']) image_meta = {'id': '10958016-e196-42e3-9e7f-5d8927ae3099'} with mock.patch.object(drv, 'get_active_image_from_info') as \ mock_get_active_image_from_info, \ mock.patch.object(drv, '_local_volume_dir') as \ mock_local_volume_dir, \ mock.patch.object(image_utils, 'qemu_img_info') as \ mock_qemu_img_info, \ mock.patch.object(image_utils, 'upload_volume') as \ mock_upload_volume, \ mock.patch.object(image_utils, 'create_temporary_file') as \ mock_create_temporary_file: mock_get_active_image_from_info.return_value = volume['name'] mock_local_volume_dir.return_value = self.TEST_MNT_POINT mock_create_temporary_file.return_value = self.TEST_TMP_FILE 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) mock_qemu_img_info.return_value = img_info upload_path = volume_path drv.copy_volume_to_image(mock.ANY, volume, mock.ANY, image_meta) mock_get_active_image_from_info.assert_called_once_with(volume) mock_local_volume_dir.assert_called_once_with(volume) mock_qemu_img_info.assert_called_once_with(volume_path, run_as_root=False) mock_upload_volume.assert_called_once_with(mock.ANY, mock.ANY, mock.ANY, upload_path, run_as_root=False) self.assertTrue(mock_create_temporary_file.called)
def test_create_snapshot_from_bootable_volume_fail(self, mock_qemu_info): """Test create snapshot from bootable volume. But it fails to volume_glance_metadata_copy_to_snapshot. As a result, status of snapshot is changed to ERROR. """ # create bootable volume from image volume = self._create_volume_from_image() volume_id = volume['id'] self.assertEqual('available', volume['status']) self.assertTrue(volume['bootable']) image_info = imageutils.QemuImgInfo() image_info.virtual_size = '1073741824' mock_qemu_info.return_value = image_info # get volume's volume_glance_metadata ctxt = context.get_admin_context() vol_glance_meta = db.volume_glance_metadata_get(ctxt, volume_id) self.assertTrue(vol_glance_meta) snap = create_snapshot(volume_id) snap_stat = snap.status self.assertTrue(snap.id) self.assertTrue(snap_stat) # set to return DB exception with mock.patch.object(db, 'volume_glance_metadata_copy_to_snapshot')\ as mock_db: mock_db.side_effect = exception.MetadataCopyFailure( reason="Because of DB service down.") # create snapshot from bootable volume self.assertRaises(exception.MetadataCopyFailure, self.volume.create_snapshot, ctxt, snap) # get snapshot's volume_glance_metadata self.assertRaises(exception.GlanceMetadataNotFound, db.volume_snapshot_glance_metadata_get, ctxt, snap.id) # ensure that status of snapshot is 'error' self.assertEqual(fields.SnapshotStatus.ERROR, snap.status) # cleanup resource snap.destroy() db.volume_destroy(ctxt, volume_id)
def test_create_volume_from_image_with_img_too_big(self, mock_qemu_info): """Test create volume with ImageCopyFailure This exception should not trigger rescheduling and allocated_capacity should be incremented so we're having assert for that here. """ image_info = imageutils.QemuImgInfo() image_info.virtual_size = '1073741824' mock_qemu_info.return_value = image_info def fake_copy_image_to_volume(context, volume, image_service, image_id): raise exception.ImageTooBig(image_id=image_id, reason='') self.mock_object(self.volume.driver, 'copy_image_to_volume', fake_copy_image_to_volume) self.assertRaises(exception.ImageTooBig, self._create_volume_from_image)
def test_create_from_image_cache_miss_error_size_invalid( self, mock_qemu_info, mock_get_internal_context, mock_create_from_img_dl, mock_create_from_src, mock_handle_bootable, mock_fetch_img): mock_fetch_img.return_value = mock.MagicMock() image_info = imageutils.QemuImgInfo() image_info.virtual_size = '2147483648' mock_qemu_info.return_value = image_info self.mock_driver.clone_image.return_value = (None, False) self.mock_cache.get_entry.return_value = None volume = fake_volume.fake_volume_obj(self.ctxt, size=1, host='foo@bar#pool') image_volume = fake_volume.fake_db_volume(size=2) self.mock_db.volume_create.return_value = image_volume image_location = 'someImageLocationStr' image_id = 'c7a8b8d4-e519-46c7-a0df-ddf1b9b9fff2' image_meta = mock.MagicMock() manager = create_volume_manager.CreateVolumeFromSpecTask( self.mock_volume_manager, self.mock_db, self.mock_driver, image_volume_cache=self.mock_cache ) self.assertRaises( exception.ImageUnacceptable, manager._create_from_image, self.ctxt, volume, image_location, image_id, image_meta, self.mock_image_service ) # The volume size should NOT be changed when in this case self.assertFalse(self.mock_db.volume_update.called) # Make sure we didn't try and create a cache entry self.assertFalse(self.mock_cache.ensure_space.called) self.assertFalse(self.mock_cache.create_cache_entry.called)
def test_backup_volume(self, mock_open, mock_temporary_chown, mock_qemu_img_info): """Backup a volume with no snapshots.""" info = imageutils.QemuImgInfo() info.file_format = 'raw' mock_qemu_img_info.return_value = info backup = {'volume_id': _FAKE_VOLUME['id']} mock_backup_service = mock.MagicMock() self.drv.db.volume_get.return_value = _FAKE_VOLUME self.drv.backup_volume(context, backup, mock_backup_service) mock_qemu_img_info.assert_called_once_with(_FAKE_VOL_PATH) mock_temporary_chown.assert_called_once_with(_FAKE_VOL_PATH) mock_open.assert_called_once_with(_FAKE_VOL_PATH) mock_backup_service.backup.assert_called_once_with( backup, mock_open().__enter__())
def test_create_volume_from_image_unavailable_no_attach_info( self, mock_qemu_info, mock_detach, mock_connect, *args): """Test create volume with ImageCopyFailure We'll raise an exception on _connect_device call to confirm that it detaches the volume even if the exception doesn't have attach_info. """ mock_connect.side_effect = NameError image_info = imageutils.QemuImgInfo() image_info.virtual_size = '1073741824' mock_qemu_info.return_value = image_info unbound_copy_method = cinder.volume.driver.BaseVD.copy_image_to_volume bound_copy_method = unbound_copy_method.__get__(self.volume.driver) with mock.patch.object(self.volume.driver, 'copy_image_to_volume', side_effect=bound_copy_method): self.assertRaises(exception.ImageCopyFailure, self._create_volume_from_image, fakeout_copy_image_to_volume=False) # We must have called detach method. self.assertEqual(1, mock_detach.call_count)
def test_qemu_img_info(self): img_info = self._initialize_img_info() img_info = img_info + ('cluster_size: %s' % self.cluster_size, ) if self.backing_file is not None: img_info = img_info + ('backing file: %s' % self.backing_file, ) if self.encrypted is not None: img_info = img_info + ('encrypted: %s' % self.encrypted, ) if self.garbage_before_snapshot is True: img_info = img_info + ('blah BLAH: bb', ) if self.snapshot_count is not None: img_info = self._insert_snapshots(img_info) if self.garbage_before_snapshot is False: img_info = img_info + ('junk stuff: bbb', ) example_output = '\n'.join(img_info) image_info = imageutils.QemuImgInfo(example_output) self._base_validation(image_info) self.assertEqual(image_info.cluster_size, self.exp_cluster_size) if self.backing_file is not None: self.assertEqual(image_info.backing_file, self.exp_backing_file) if self.encrypted is not None: self.assertEqual(image_info.encrypted, self.encrypted)
def test_create_snapshot_from_bootable_volume(self, mock_qemu_info): """Test create snapshot from bootable volume.""" # create bootable volume from image volume = self._create_volume_from_image() volume_id = volume['id'] self.assertEqual('available', volume['status']) self.assertTrue(volume['bootable']) image_info = imageutils.QemuImgInfo() image_info.virtual_size = '1073741824' mock_qemu_info.return_value = image_info # get volume's volume_glance_metadata ctxt = context.get_admin_context() vol_glance_meta = db.volume_glance_metadata_get(ctxt, volume_id) self.assertTrue(bool(vol_glance_meta)) # create snapshot from bootable volume snap = create_snapshot(volume_id) self.volume.create_snapshot(ctxt, snap) # get snapshot's volume_glance_metadata snap_glance_meta = db.volume_snapshot_glance_metadata_get( ctxt, snap.id) self.assertTrue(bool(snap_glance_meta)) # ensure that volume's glance metadata is copied # to snapshot's glance metadata self.assertEqual(len(vol_glance_meta), len(snap_glance_meta)) vol_glance_dict = {x.key: x.value for x in vol_glance_meta} snap_glance_dict = {x.key: x.value for x in snap_glance_meta} self.assertDictEqual(vol_glance_dict, snap_glance_dict) # ensure that snapshot's status is changed to 'available' self.assertEqual(fields.SnapshotStatus.AVAILABLE, snap.status) # cleanup resource snap.destroy() db.volume_destroy(ctxt, volume_id)
def qemu_img_info(path, format=None): """Return an object containing the parsed output from qemu-img info.""" # TODO(mikal): this code should not be referring to a libvirt specific # flag. if not os.path.exists(path) and CONF.libvirt.images_type != 'rbd': raise exception.DiskNotFound(location=path) try: cmd = ('env', 'LC_ALL=C', 'LANG=C', 'qemu-img', 'info', path) if format is not None: cmd = cmd + ('-f', format) out, err = utils.execute(*cmd, prlimit=QEMU_IMG_LIMITS) except processutils.ProcessExecutionError as exp: msg = (_("qemu-img failed to execute on %(path)s : %(exp)s") % {'path': path, 'exp': exp}) raise exception.InvalidDiskInfo(reason=msg) if not out: msg = (_("Failed to run qemu-img info on %(path)s : %(error)s") % {'path': path, 'error': err}) raise exception.InvalidDiskInfo(reason=msg) return imageutils.QemuImgInfo(out)
def qemu_img_info(path, run_as_root=True, force_share=False): """Return an object containing the parsed output from qemu-img info.""" cmd = ['env', 'LC_ALL=C', 'qemu-img', 'info'] if force_share: if qemu_img_supports_force_share(): cmd.append('--force-share') else: msg = _("qemu-img --force-share requested, but " "qemu-img does not support this parameter") LOG.warning(msg) cmd.append(path) if os.name == 'nt': cmd = cmd[2:] out, _err = utils.execute(*cmd, run_as_root=run_as_root, prlimit=QEMU_IMG_LIMITS) info = imageutils.QemuImgInfo(out) # From Cinder's point of view, any 'luks' formatted images # should be treated as 'raw'. if info.file_format == 'luks': info.file_format = 'raw' return info
def test_create_from_image_cache_miss_error_downloading( self, mock_qemu_info, mock_volume_get, mock_volume_update, mock_get_internal_context, mock_create_from_img_dl, mock_create_from_src, mock_handle_bootable, mock_fetch_img): mock_fetch_img.return_value = mock.MagicMock() image_info = imageutils.QemuImgInfo() image_info.virtual_size = '2147483648' mock_qemu_info.return_value = image_info self.mock_driver.clone_image.return_value = (None, False) self.mock_cache.get_entry.return_value = None volume = fake_volume.fake_volume_obj(self.ctxt, size=10, host='foo@bar#pool') mock_volume_get.return_value = volume mock_create_from_img_dl.side_effect = exception.CinderException() image_location = 'someImageLocationStr' image_id = 'c7a8b8d4-e519-46c7-a0df-ddf1b9b9fff2' image_meta = mock.MagicMock() manager = create_volume_manager.CreateVolumeFromSpecTask( self.mock_volume_manager, self.mock_db, self.mock_driver, image_volume_cache=self.mock_cache ) self.assertRaises( exception.CinderException, manager._create_from_image, self.ctxt, volume, image_location, image_id, image_meta, self.mock_image_service ) # Make sure clone_image is always called self.assertTrue(self.mock_driver.clone_image.called) # The image download should happen if clone fails and # we get a cache miss mock_create_from_img_dl.assert_called_once_with( self.ctxt, mock.ANY, image_location, image_id, self.mock_image_service ) # The volume size should be reduced to virtual_size and then put back, # especially if there is an exception while creating the volume. self.assertEqual(2, mock_volume_update.call_count) mock_volume_update.assert_any_call(self.ctxt, volume.id, {'size': 2}) mock_volume_update.assert_any_call(self.ctxt, volume.id, {'size': 10}) # Make sure we didn't try and create a cache entry self.assertFalse(self.mock_cache.ensure_space.called) self.assertFalse(self.mock_cache.create_cache_entry.called)
def test_create_from_image_cache_miss( self, mock_qemu_info, mock_volume_get, mock_volume_update, mock_get_internal_context, mock_create_from_img_dl, mock_create_from_src, mock_handle_bootable, mock_fetch_img): mock_get_internal_context.return_value = self.ctxt mock_fetch_img.return_value = mock.MagicMock( spec=utils.get_file_spec()) image_info = imageutils.QemuImgInfo() image_info.virtual_size = '2147483648' mock_qemu_info.return_value = image_info self.mock_driver.clone_image.return_value = (None, False) self.mock_cache.get_entry.return_value = None volume = fake_volume.fake_volume_obj(self.ctxt, size=10, host='foo@bar#pool') mock_volume_get.return_value = volume image_location = 'someImageLocationStr' image_id = 'c7a8b8d4-e519-46c7-a0df-ddf1b9b9fff2' image_meta = mock.MagicMock() manager = create_volume_manager.CreateVolumeFromSpecTask( self.mock_volume_manager, self.mock_db, self.mock_driver, image_volume_cache=self.mock_cache ) manager._create_from_image(self.ctxt, volume, image_location, image_id, image_meta, self.mock_image_service) # Make sure clone_image is always called self.assertTrue(self.mock_driver.clone_image.called) # The image download should happen if clone fails and # we get a cache miss mock_create_from_img_dl.assert_called_once_with( self.ctxt, mock.ANY, image_location, image_id, self.mock_image_service ) # The volume size should be reduced to virtual_size and then put back mock_volume_update.assert_any_call(self.ctxt, volume.id, {'size': 2}) mock_volume_update.assert_any_call(self.ctxt, volume.id, {'size': 10}) # Make sure created a new cache entry (self.mock_volume_manager. _create_image_cache_volume_entry.assert_called_once_with( self.ctxt, volume, image_id, image_meta)) mock_handle_bootable.assert_called_once_with( self.ctxt, volume['id'], image_id=image_id, image_meta=image_meta )