Merge "libvirt: make cross cell resize spawn from snapshot image"

This commit is contained in:
Zuul 2020-12-15 20:43:45 +00:00 committed by Gerrit Code Review
commit 6c96602864
5 changed files with 116 additions and 39 deletions

View File

@ -3958,6 +3958,14 @@ class API(base.Base):
current_instance_type = instance.get_flavor()
# NOTE(aarents): Ensure image_base_image_ref is present as it will be
# needed during finish_resize/cross_cell_resize. Instances upgraded
# from an older nova release may not have this property because of
# a rebuild bug Bug/1893618.
instance.system_metadata.update(
{'image_base_image_ref': instance.image_ref}
)
# If flavor_id is not provided, only migrate the instance.
volume_backed = None
if not flavor_id:

View File

@ -5902,7 +5902,9 @@ class ComputeManager(manager.Manager):
other generic error handling.
"""
# Figure out the image metadata to use when spawning the guest.
origin_image_ref = instance.image_ref
if snapshot_id:
instance.image_ref = snapshot_id
image_meta = objects.ImageMeta.from_image_ref(
ctxt, self.image_api, snapshot_id)
else:
@ -5940,6 +5942,7 @@ class ComputeManager(manager.Manager):
# If we spawned from a temporary snapshot image we can delete that now,
# similar to how unshelve works.
if snapshot_id:
instance.image_ref = origin_image_ref
compute_utils.delete_image(
ctxt, instance, self.image_api, snapshot_id)

View File

@ -1240,6 +1240,7 @@ class ServersPolicyTest(base.BasePolicyTest):
self.project_member_context,
id=1, uuid=uuids.fake_id, project_id=self.project_id,
user_id='fake-user', vm_state=vm_states.ACTIVE,
expected_attrs=['system_metadata'],
launched_at=timeutils.utcnow())
mock_get.side_effect = fake_get

View File

@ -23164,22 +23164,25 @@ class LibvirtDriverTestCase(test.NoDBTestCase, TraitsComparisonMixin):
@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):
def _test_qcow2_rebase_image_during_create(self,
mock_rebase, mock_fetch, image_ref, base_image_ref, vm_state=None,
task_state=None, original_image_in_glance=True,
rebase_expected=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)
# base_image_ref: original image ref from where instance was created,
# stored in system_metadata at instance creation.
# image_ref: current instance.image_ref used in unshelve/resize)
# vm_state: current vm_state
# Instance state during unshelve spawn().
base_image_root_fname = imagecache.get_cache_fname(base_image_ref)
image_root_fname = imagecache.get_cache_fname(image_ref)
# Instance state during _create_and_inject_local_root call.
inst_params = {
'image_ref': shelved_image_ref,
'vm_state': vm_states.SHELVED_OFFLOADED,
'image_ref': image_ref,
'vm_state': vm_state,
'task_state': task_state,
'system_metadata': {'image_base_image_ref': base_image_ref}
}
@ -23193,7 +23196,7 @@ class LibvirtDriverTestCase(test.NoDBTestCase, TraitsComparisonMixin):
# We expect final backing file is original image, not shelved one.
expected_backing_file = os.path.join(
imagecache.ImageCacheManager().cache_dir,
base_root_fname)
base_image_root_fname)
else:
# None means rebase will merge backing file into disk(flatten).
expected_backing_file = None
@ -23205,36 +23208,81 @@ class LibvirtDriverTestCase(test.NoDBTestCase, TraitsComparisonMixin):
drvr._create_and_inject_local_root(
self.context, instance, False, '', disk_images, None, None)
mock_fetch.assert_has_calls([
mock_fetch_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),
self.context, image_root_fname, image_ref,
instance, instance.root_gb * units.Gi, None)
]
if rebase_expected:
# if we rebase we must expect a 2nd fetch call, to cache the
# original backing file.
mock_fetch_calls.append(
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)
self.context, base_image_root_fname, base_image_ref,
instance, None))
mock_rebase.assert_called_once_with(disk_path,
expected_backing_file)
else:
mock_rebase.assert_not_called()
mock_fetch.assert_has_calls(mock_fetch_calls)
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()
self._test_qcow2_rebase_image_during_create(
image_ref='snapshot_id_of_shelved_instance',
base_image_ref='original_image_id',
vm_state=vm_states.SHELVED_OFFLOADED,
rebase_expected=True)
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)
self._test_qcow2_rebase_image_during_create(
image_ref='snapshot_id_of_shelved_instance',
base_image_ref='original_image_id',
vm_state=vm_states.SHELVED_OFFLOADED,
original_image_in_glance=False,
rebase_expected=True)
@mock.patch('nova.virt.libvirt.driver.imagebackend')
def test_cross_cell_resize_qcow2_rebase_image_during_create(self):
self._test_qcow2_rebase_image_during_create(
image_ref='snapshot_id_of_resized_instance',
base_image_ref='original_image_id',
task_state=task_states.RESIZE_FINISH,
rebase_expected=True)
def test_cross_cell_resize_qcow2_rebase_image_during_create_notfound(self):
self._test_qcow2_rebase_image_during_create(
image_ref='snapshot_id_of_resized_instance',
base_image_ref='original_image_id',
task_state=task_states.RESIZE_FINISH,
original_image_in_glance=False,
rebase_expected=True)
def test_local_cell_resize_qcow2_rebase_image_during_create(self):
# local cell resize does not go into a spawn from a snapshot,
# consequently, instance.image_ref remain the same and we must ensure
# that no rebase is done.
self._test_qcow2_rebase_image_during_create(
image_ref='original_image_id',
base_image_ref='original_image_id',
task_state=task_states.RESIZE_FINISH,
rebase_expected=False)
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver.'
'_try_fetch_image_cache', new=mock.Mock())
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._inject_data')
@mock.patch('nova.virt.libvirt.driver.imagecache')
def test_data_not_injects_with_configdrive(self, mock_image, mock_inject,
mock_backend):
@mock.patch('nova.virt.libvirt.driver.imagecache', new=mock.Mock())
def test_data_not_injects_with_configdrive(self, mock_inject):
self.flags(inject_partition=-1, group='libvirt')
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)

View File

@ -4184,13 +4184,11 @@ 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
# During unshelve or cross cell resize on Qcow2 backend, we spawn()
# using a snapshot image. 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)
# then represent back image_ref instead of snapshot image.
self._rebase_original_qcow2_image(context, instance, backend)
if need_inject:
self._inject_data(backend, instance, injection_info)
@ -4201,13 +4199,32 @@ 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.
def _needs_rebase_original_qcow2_image(self, instance, backend):
if not isinstance(backend, imagebackend.Qcow2):
return False
if instance.vm_state == vm_states.SHELVED_OFFLOADED:
return True
if instance.task_state == task_states.RESIZE_FINISH:
# We need to distinguish between local versus cross cell resize.
# Rebase is only needed in cross cell case because instance
# is spawn from a snapshot.
base_image_ref = instance.system_metadata.get(
'image_base_image_ref')
if base_image_ref != instance.image_ref:
return True
return False
def _rebase_original_qcow2_image(self, context, instance, backend):
# NOTE(aarents): During qcow2 instance unshelve/cross_cell_resize,
# backing file represents a snapshot 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.
if not self._needs_rebase_original_qcow2_image(instance, backend):
return
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)
@ -4219,9 +4236,9 @@ class LibvirtDriver(driver.ComputeDriver):
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 '
# backing file (as snapshot image will be dropped once
# unshelve/cross_cell_resize is successfull).
LOG.warning('Current disk image is created on top of a snapshot '
'image and cannot be rebased to original image '
'because it is no longer available in the image '
'service, disk will be consequently flattened.',