diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py index a3f4b9f7e955..d987dd5b4c2b 100644 --- a/nova/tests/unit/virt/libvirt/test_driver.py +++ b/nova/tests/unit/virt/libvirt/test_driver.py @@ -856,6 +856,7 @@ def _create_test_instance(): 'vcpu_model': None, 'host': 'fake-host', 'task_state': None, + 'vm_state': None, 'trusted_certs': None } @@ -16911,8 +16912,8 @@ class LibvirtConnTestCase(test.NoDBTestCase, prepare.side_effect = fake_prepare drvr = libvirt_driver.LibvirtDriver(virtapi, False) - instance = objects.Instance(vm_state=vm_states.BUILDING, - **self.test_instance) + instance = objects.Instance(**self.test_instance) + instance.vm_state = vm_states.BUILDING vifs = [{'id': uuids.vif_1, 'active': False}, {'id': uuids.vif_2, 'active': False}] @@ -18307,6 +18308,7 @@ class LibvirtDriverTestCase(test.NoDBTestCase, TraitsComparisonMixin): inst['system_metadata'] = {} inst['metadata'] = {} inst['task_state'] = None + inst['vm_state'] = None inst.update(params) @@ -19290,6 +19292,54 @@ class LibvirtDriverTestCase(test.NoDBTestCase, TraitsComparisonMixin): bdm.append({'boot_index': 0}) self.assertTrue(func(bdi)) + def test_unshelve_noop_flatten_fetch_image_cache(self): + instance = self._create_instance( + params={'vm_state': vm_states.SHELVED_OFFLOADED}) + drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) + mock_imagebackend = mock.Mock(spec=imagebackend.Lvm) + mock_imagebackend.flatten.side_effect = NotImplementedError() + + # Assert that this doesn't raise NotImplementedError + drvr._try_fetch_image_cache(mock_imagebackend, mock.sentinel.fetch, + self.context, mock.sentinel.filename, uuids.image_id, + instance, mock.sentinel.size) + + # Assert that we cache and then flatten the image when an instance is + # still SHELVED_OFFLOADED during _try_fetch_image_cache. + mock_imagebackend.cache.assert_called_once_with( + fetch_func=mock.sentinel.fetch, context=self.context, + filename=mock.sentinel.filename, image_id=uuids.image_id, + size=mock.sentinel.size, trusted_certs=instance.trusted_certs) + mock_imagebackend.flatten.assert_called_once() + + def test_unshelve_rbd_image_flatten_during_fetch_image_cache(self): + instance = self._create_instance( + params={'vm_state': vm_states.SHELVED_OFFLOADED}) + drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) + mock_rbd_driver = mock.Mock(spec=rbd_utils.RBDDriver) + mock_rbd_imagebackend = mock.Mock(spec=imagebackend.Rbd) + mock_rbd_imagebackend.rbd_name = mock.sentinel.rbd_name + mock_rbd_imagebackend.pool = mock.sentinel.rbd_pool + # This is logged so we can't use a sentinel + mock_rbd_imagebackend.path = 'rbd:pool/vol_disk' + mock_rbd_imagebackend.driver = mock_rbd_driver + mock_rbd_imagebackend.flatten.side_effect = \ + imagebackend.Rbd.flatten(mock_rbd_imagebackend) + + drvr._try_fetch_image_cache(mock_rbd_imagebackend, mock.sentinel.fetch, + self.context, mock.sentinel.filename, uuids.image_id, + instance, mock.sentinel.size) + + # Assert that we cache and then flatten the image when an instance is + # still SHELVED_OFFLOADED during _try_fetch_image_cache. + mock_rbd_imagebackend.cache.assert_called_once_with( + fetch_func=mock.sentinel.fetch, context=self.context, + filename=mock.sentinel.filename, image_id=uuids.image_id, + size=mock.sentinel.size, trusted_certs=instance.trusted_certs) + mock_rbd_imagebackend.flatten.assert_called_once() + mock_rbd_driver.flatten.assert_called_once_with( + mock.sentinel.rbd_name, pool=mock.sentinel.rbd_pool) + @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/tests/unit/virt/libvirt/test_imagebackend.py b/nova/tests/unit/virt/libvirt/test_imagebackend.py index e7a86b7078d9..439f0c47992e 100644 --- a/nova/tests/unit/virt/libvirt/test_imagebackend.py +++ b/nova/tests/unit/virt/libvirt/test_imagebackend.py @@ -1550,6 +1550,12 @@ class RbdTestCase(_ImageTestCase, test.NoDBTestCase): ["server1:1899", "server2:1920"]), model) + @mock.patch.object(rbd_utils.RBDDriver, 'flatten') + def test_flatten(self, mock_flatten): + image = self.image_class(self.INSTANCE, self.NAME) + image.flatten() + mock_flatten.assert_called_once_with(image.rbd_name, pool=self.POOL) + def test_import_file(self): image = self.image_class(self.INSTANCE, self.NAME) diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index e85fffe3c4fd..6fc98a6c2977 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -7821,6 +7821,26 @@ class LibvirtDriver(driver.ComputeDriver): image.cache(fetch_func=copy_from_host, size=size, filename=filename) + # NOTE(lyarwood): If the instance vm_state is shelved offloaded then we + # must be unshelving for _try_fetch_image_cache to be called. + if instance.vm_state == vm_states.SHELVED_OFFLOADED: + # NOTE(lyarwood): When using the rbd imagebackend the call to cache + # above will attempt to clone from the shelved snapshot in Glance + # if available from this compute. We then need to flatten the + # resulting image to avoid it still referencing and ultimately + # blocking the removal of the shelved snapshot at the end of the + # unshelve. This is a no-op for all but the rbd imagebackend. + try: + image.flatten() + LOG.debug('Image %s flattened successfully while unshelving ' + 'instance.', image.path, instance=instance) + except NotImplementedError: + # NOTE(lyarwood): There's an argument to be made for logging + # our inability to call flatten here, however given this isn't + # implemented for most of the backends it may do more harm than + # good, concerning operators etc so for now just pass. + pass + def _create_images_and_backing(self, context, instance, instance_dir, disk_info, fallback_from_host=None): """:param context: security context diff --git a/nova/virt/libvirt/imagebackend.py b/nova/virt/libvirt/imagebackend.py index 4ae97dda25cf..784aa2c25ba7 100644 --- a/nova/virt/libvirt/imagebackend.py +++ b/nova/virt/libvirt/imagebackend.py @@ -431,6 +431,14 @@ class Image(object): raise exception.ImageUnacceptable(image_id=image_id_or_uri, reason=reason) + def flatten(self): + """Flatten an image. + + The implementation of this method is optional and therefore is + not an abstractmethod. + """ + raise NotImplementedError('flatten() is not implemented') + def direct_snapshot(self, context, snapshot_name, image_format, image_id, base_image_id): """Prepare a snapshot for direct reference from glance. @@ -961,6 +969,9 @@ class Rbd(Image): raise exception.ImageUnacceptable(image_id=image_id_or_uri, reason=reason) + def flatten(self): + self.driver.flatten(self.rbd_name, pool=self.pool) + def get_model(self, connection): secret = None if CONF.libvirt.rbd_secret_uuid: