Skip to content

Commit e93bc57

Browse files
rsriteshlyarwood
authored andcommitted
libvirt: flatten rbd images when unshelving an instance
Previously attempts to remove the shelved snapshot of an unshelved instance when using the rbd backends for both Nova and Glance would fail. This was due to the instance disk being cloned from and still referencing the shelved snapshot image in Glance, blocking any attempt to remove this image later in the unshelve process. After much debate this change attempts to fix this issue by flattening the instance disk while the instance is being spawned as part of an unshelve. For the rbd imagebackend this removes any reference to the shelved snapshot in Glance allowing this image to be removed. For all other imagebackends the call to flatten the image is currently a no-op. Co-Authored-By: Lee Yarwood <[email protected]> Co-Authored-By: Vladyslav Drok <[email protected]> Closes-Bug: #1653953 Change-Id: If3c9d1de3ce0fe394405bd1e1f0fa08ce2baeda8 (cherry picked from commit d89e7d7) (cherry picked from commit e802ede)
1 parent d8f1bef commit e93bc57

File tree

4 files changed

+89
-2
lines changed

4 files changed

+89
-2
lines changed

nova/tests/unit/virt/libvirt/test_driver.py

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -856,6 +856,7 @@ def _create_test_instance():
856856
'vcpu_model': None,
857857
'host': 'fake-host',
858858
'task_state': None,
859+
'vm_state': None,
859860
'trusted_certs': None
860861
}
861862

@@ -16911,8 +16912,8 @@ def fake_prepare(instance, name, tag):
1691116912
prepare.side_effect = fake_prepare
1691216913
drvr = libvirt_driver.LibvirtDriver(virtapi, False)
1691316914

16914-
instance = objects.Instance(vm_state=vm_states.BUILDING,
16915-
**self.test_instance)
16915+
instance = objects.Instance(**self.test_instance)
16916+
instance.vm_state = vm_states.BUILDING
1691616917
vifs = [{'id': uuids.vif_1, 'active': False},
1691716918
{'id': uuids.vif_2, 'active': False}]
1691816919

@@ -18307,6 +18308,7 @@ def _create_instance(self, params=None):
1830718308
inst['system_metadata'] = {}
1830818309
inst['metadata'] = {}
1830918310
inst['task_state'] = None
18311+
inst['vm_state'] = None
1831018312

1831118313
inst.update(params)
1831218314

@@ -19290,6 +19292,54 @@ def test_is_booted_from_volume(self):
1929019292
bdm.append({'boot_index': 0})
1929119293
self.assertTrue(func(bdi))
1929219294

19295+
def test_unshelve_noop_flatten_fetch_image_cache(self):
19296+
instance = self._create_instance(
19297+
params={'vm_state': vm_states.SHELVED_OFFLOADED})
19298+
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
19299+
mock_imagebackend = mock.Mock(spec=imagebackend.Lvm)
19300+
mock_imagebackend.flatten.side_effect = NotImplementedError()
19301+
19302+
# Assert that this doesn't raise NotImplementedError
19303+
drvr._try_fetch_image_cache(mock_imagebackend, mock.sentinel.fetch,
19304+
self.context, mock.sentinel.filename, uuids.image_id,
19305+
instance, mock.sentinel.size)
19306+
19307+
# Assert that we cache and then flatten the image when an instance is
19308+
# still SHELVED_OFFLOADED during _try_fetch_image_cache.
19309+
mock_imagebackend.cache.assert_called_once_with(
19310+
fetch_func=mock.sentinel.fetch, context=self.context,
19311+
filename=mock.sentinel.filename, image_id=uuids.image_id,
19312+
size=mock.sentinel.size, trusted_certs=instance.trusted_certs)
19313+
mock_imagebackend.flatten.assert_called_once()
19314+
19315+
def test_unshelve_rbd_image_flatten_during_fetch_image_cache(self):
19316+
instance = self._create_instance(
19317+
params={'vm_state': vm_states.SHELVED_OFFLOADED})
19318+
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
19319+
mock_rbd_driver = mock.Mock(spec=rbd_utils.RBDDriver)
19320+
mock_rbd_imagebackend = mock.Mock(spec=imagebackend.Rbd)
19321+
mock_rbd_imagebackend.rbd_name = mock.sentinel.rbd_name
19322+
mock_rbd_imagebackend.pool = mock.sentinel.rbd_pool
19323+
# This is logged so we can't use a sentinel
19324+
mock_rbd_imagebackend.path = 'rbd:pool/vol_disk'
19325+
mock_rbd_imagebackend.driver = mock_rbd_driver
19326+
mock_rbd_imagebackend.flatten.side_effect = \
19327+
imagebackend.Rbd.flatten(mock_rbd_imagebackend)
19328+
19329+
drvr._try_fetch_image_cache(mock_rbd_imagebackend, mock.sentinel.fetch,
19330+
self.context, mock.sentinel.filename, uuids.image_id,
19331+
instance, mock.sentinel.size)
19332+
19333+
# Assert that we cache and then flatten the image when an instance is
19334+
# still SHELVED_OFFLOADED during _try_fetch_image_cache.
19335+
mock_rbd_imagebackend.cache.assert_called_once_with(
19336+
fetch_func=mock.sentinel.fetch, context=self.context,
19337+
filename=mock.sentinel.filename, image_id=uuids.image_id,
19338+
size=mock.sentinel.size, trusted_certs=instance.trusted_certs)
19339+
mock_rbd_imagebackend.flatten.assert_called_once()
19340+
mock_rbd_driver.flatten.assert_called_once_with(
19341+
mock.sentinel.rbd_name, pool=mock.sentinel.rbd_pool)
19342+
1929319343
@mock.patch('nova.virt.libvirt.driver.imagebackend')
1929419344
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._inject_data')
1929519345
@mock.patch('nova.virt.libvirt.driver.imagecache')

nova/tests/unit/virt/libvirt/test_imagebackend.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1550,6 +1550,12 @@ def get_mon_addrs():
15501550
["server1:1899", "server2:1920"]),
15511551
model)
15521552

1553+
@mock.patch.object(rbd_utils.RBDDriver, 'flatten')
1554+
def test_flatten(self, mock_flatten):
1555+
image = self.image_class(self.INSTANCE, self.NAME)
1556+
image.flatten()
1557+
mock_flatten.assert_called_once_with(image.rbd_name, pool=self.POOL)
1558+
15531559
def test_import_file(self):
15541560
image = self.image_class(self.INSTANCE, self.NAME)
15551561

nova/virt/libvirt/driver.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7821,6 +7821,26 @@ def copy_from_host(target):
78217821
image.cache(fetch_func=copy_from_host, size=size,
78227822
filename=filename)
78237823

7824+
# NOTE(lyarwood): If the instance vm_state is shelved offloaded then we
7825+
# must be unshelving for _try_fetch_image_cache to be called.
7826+
if instance.vm_state == vm_states.SHELVED_OFFLOADED:
7827+
# NOTE(lyarwood): When using the rbd imagebackend the call to cache
7828+
# above will attempt to clone from the shelved snapshot in Glance
7829+
# if available from this compute. We then need to flatten the
7830+
# resulting image to avoid it still referencing and ultimately
7831+
# blocking the removal of the shelved snapshot at the end of the
7832+
# unshelve. This is a no-op for all but the rbd imagebackend.
7833+
try:
7834+
image.flatten()
7835+
LOG.debug('Image %s flattened successfully while unshelving '
7836+
'instance.', image.path, instance=instance)
7837+
except NotImplementedError:
7838+
# NOTE(lyarwood): There's an argument to be made for logging
7839+
# our inability to call flatten here, however given this isn't
7840+
# implemented for most of the backends it may do more harm than
7841+
# good, concerning operators etc so for now just pass.
7842+
pass
7843+
78247844
def _create_images_and_backing(self, context, instance, instance_dir,
78257845
disk_info, fallback_from_host=None):
78267846
""":param context: security context

nova/virt/libvirt/imagebackend.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,14 @@ def clone(self, context, image_id_or_uri):
431431
raise exception.ImageUnacceptable(image_id=image_id_or_uri,
432432
reason=reason)
433433

434+
def flatten(self):
435+
"""Flatten an image.
436+
437+
The implementation of this method is optional and therefore is
438+
not an abstractmethod.
439+
"""
440+
raise NotImplementedError('flatten() is not implemented')
441+
434442
def direct_snapshot(self, context, snapshot_name, image_format, image_id,
435443
base_image_id):
436444
"""Prepare a snapshot for direct reference from glance.
@@ -961,6 +969,9 @@ def clone(self, context, image_id_or_uri):
961969
raise exception.ImageUnacceptable(image_id=image_id_or_uri,
962970
reason=reason)
963971

972+
def flatten(self):
973+
self.driver.flatten(self.rbd_name, pool=self.pool)
974+
964975
def get_model(self, connection):
965976
secret = None
966977
if CONF.libvirt.rbd_secret_uuid:

0 commit comments

Comments
 (0)