diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py index 86b15a9e6edc..ad315f84dfb3 100644 --- a/nova/tests/unit/virt/libvirt/test_driver.py +++ b/nova/tests/unit/virt/libvirt/test_driver.py @@ -21798,6 +21798,74 @@ class LibvirtDriverTestCase(test.NoDBTestCase, TraitsComparisonMixin): mock_rbd_driver.flatten.assert_called_once_with( mock.sentinel.rbd_name, pool=mock.sentinel.rbd_pool) + @mock.patch.object(libvirt_driver.LibvirtDriver, '_try_fetch_image_cache') + @mock.patch.object(libvirt_driver.LibvirtDriver, '_rebase_with_qemu_img') + def _test_unshelve_qcow2_rebase_image_during_create(self, + mock_rebase, mock_fetch, original_image_in_glance=True): + self.flags(images_type='qcow2', group='libvirt') + + # Original image ref from where instance was created, before SHELVE + # occurs, base_root_fname is related backing file name. + base_image_ref = 'base_image_ref' + base_root_fname = imagecache.get_cache_fname(base_image_ref) + # Snapshot image ref created during SHELVE. + shelved_image_ref = 'shelved_image_ref' + shelved_root_fname = imagecache.get_cache_fname(shelved_image_ref) + + # Instance state during unshelve spawn(). + inst_params = { + 'image_ref': shelved_image_ref, + 'vm_state': vm_states.SHELVED_OFFLOADED, + 'system_metadata': {'image_base_image_ref': base_image_ref} + } + + instance = self._create_instance(params=inst_params) + disk_images = {'image_id': instance.image_ref} + instance_dir = libvirt_utils.get_instance_path(instance) + disk_path = os.path.join(instance_dir, 'disk') + drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) + + if original_image_in_glance: + # We expect final backing file is original image, not shelved one. + expected_backing_file = os.path.join( + imagecache.ImageCacheManager().cache_dir, + base_root_fname) + else: + # None means rebase will merge backing file into disk(flatten). + expected_backing_file = None + mock_fetch.side_effect = [ + None, + exception.ImageNotFound(image_id=base_image_ref) + ] + + drvr._create_and_inject_local_root( + self.context, instance, False, '', disk_images, None, None) + + mock_fetch.assert_has_calls([ + mock.call(test.MatchType(nova.virt.libvirt.imagebackend.Qcow2), + libvirt_utils.fetch_image, + self.context, shelved_root_fname, shelved_image_ref, + instance, instance.root_gb * units.Gi, None), + mock.call(test.MatchType(nova.virt.libvirt.imagebackend.Qcow2), + libvirt_utils.fetch_image, + self.context, base_root_fname, base_image_ref, + instance, None)]) + mock_rebase.assert_called_once_with(disk_path, expected_backing_file) + + def test_unshelve_qcow2_rebase_image_during_create(self): + # Original image is present in Glance. In that case the 2nd + # fetch succeeds and we rebase instance disk to original image backing + # file, instance is back to nominal state: after unshelve, + # instance.image_ref will match current backing file. + self._test_unshelve_qcow2_rebase_image_during_create() + + def test_unshelve_qcow2_rebase_image_during_create_notfound(self): + # Original image is no longer available in Glance, so 2nd fetch + # will failed (HTTP 404). In that case qemu-img rebase will merge + # backing file into disk, removing backing file dependency. + self._test_unshelve_qcow2_rebase_image_during_create( + original_image_in_glance=False) + @mock.patch('nova.virt.libvirt.driver.imagebackend') @mock.patch('nova.virt.libvirt.driver.LibvirtDriver._inject_data') @mock.patch('nova.virt.libvirt.driver.imagecache') diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index d90ef79bfbfa..c9e48b3971bb 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -4026,6 +4026,14 @@ class LibvirtDriver(driver.ComputeDriver): root_fname, disk_images['image_id'], instance, size, fallback_from_host) + # During unshelve on Qcow2 backend, we spawn() using snapshot image + # created during shelve. Extra work is needed in order to rebase + # disk image to its original image_ref. Disk backing file will + # then represent back image_ref instead of shelved image. + if (instance.vm_state == vm_states.SHELVED_OFFLOADED and + isinstance(backend, imagebackend.Qcow2)): + self._finalize_unshelve_qcow2_image(context, instance, backend) + if need_inject: self._inject_data(backend, instance, injection_info) @@ -4035,6 +4043,36 @@ class LibvirtDriver(driver.ComputeDriver): return created_disks + def _finalize_unshelve_qcow2_image(self, context, instance, backend): + # NOTE(aarents): During qcow2 instance unshelve, backing file + # represents shelved image, not original instance.image_ref. + # We rebase here instance disk to original image. + # This second fetch call does nothing except downloading original + # backing file if missing, as image disk have already been + # created/resized by first fetch call. + base_dir = self.image_cache_manager.cache_dir + base_image_ref = instance.system_metadata.get('image_base_image_ref') + root_fname = imagecache.get_cache_fname(base_image_ref) + base_backing_fname = os.path.join(base_dir, root_fname) + + try: + self._try_fetch_image_cache(backend, libvirt_utils.fetch_image, + context, root_fname, base_image_ref, + instance, None) + except exception.ImageNotFound: + # We must flatten here in order to remove dependency with an orphan + # backing file (as shelved image will be dropped once unshelve + # is successfull). + LOG.warning('Current disk image is created on top of shelved ' + 'image and cannot be rebased to original image ' + 'because it is no longer available in the image ' + 'service, disk will be consequently flattened.', + instance=instance) + base_backing_fname = None + + LOG.info('Rebasing disk image.', instance=instance) + self._rebase_with_qemu_img(backend.path, base_backing_fname) + def _create_configdrive(self, context, instance, injection_info, rescue=False): # As this method being called right after the definition of a