Skip to content

Commit 200c743

Browse files
committed
compute: Reject requests to commit intermediary snapshot of an inactive instance
Introduced by I76eb2e4da027a13525314bd58264f482374d270d the os-assisted-volume-snapshots API is only implemented by the libvirt virt driver and should only be called by c-vol as part of an orchestrated remotefs based volume snapshot creation or deletion workflow. While not documented clearly in the current api-ref there are code comments within the compute API suggesting that this API can be called against a volume attached to an instance that is in *any* vm_state. This however is not correct when deleting and in turn committing an intermediary volume snapshot of an instance that is not running given the current implementation within the libvirt driver. With a request to virDomainBlockCommit being made that fails if the instance and underlying domain is not running. Adding support for an offline commit isn't trivial and would be considered a new feature and not something we could backport on the stable branches. As such this change seeks to ensure requests to commit an intermediary volume snapshot from an inactive instance are rejected quickly and clearly by the compute API to the caller before we cast to the compute. Closes-Bug: #1919487 Change-Id: I212a2db8d71702d330b146dc6f871b402a309e74 (cherry picked from commit 9940937)
1 parent 68af588 commit 200c743

File tree

2 files changed

+66
-9
lines changed

2 files changed

+66
-9
lines changed

nova/compute/api.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5483,11 +5483,25 @@ def volume_snapshot_delete(self, context, volume_id, snapshot_id,
54835483
bdm = self._get_bdm_by_volume_id(
54845484
context, volume_id, expected_attrs=['instance'])
54855485

5486-
# We allow deleting the snapshot in any vm_state as long as there is
5487-
# no task being performed on the instance and it has a host.
54885486
@check_instance_host()
54895487
@check_instance_state(vm_state=None)
54905488
def do_volume_snapshot_delete(self, context, instance):
5489+
# FIXME(lyarwood): Avoid bug #1919487 by rejecting the request
5490+
# to delete an intermediary volume snapshot offline as this isn't
5491+
# currently implemented within the libvirt driver and will fail.
5492+
# This should be fixed in a future release but as it is essentially
5493+
# a new feature wouldn't be something we could backport. As such
5494+
# reject the request here so n-api can respond correctly to c-vol.
5495+
if (delete_info.get('merge_target_file') is not None and
5496+
instance.vm_state != vm_states.ACTIVE
5497+
):
5498+
raise exception.InstanceInvalidState(
5499+
attr='vm_state',
5500+
instance_uuid=instance.uuid,
5501+
state=instance.vm_state,
5502+
method='volume_snapshot_delete'
5503+
)
5504+
54915505
self.compute_rpcapi.volume_snapshot_delete(context, instance,
54925506
volume_id, snapshot_id, delete_info)
54935507

nova/tests/unit/compute/test_api.py

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3601,23 +3601,29 @@ def test_volume_snapshot_delete(self, mock_get_bdm):
36013601
'boot_index': -1})
36023602
fake_bdm['instance'] = fake_instance.fake_db_instance(
36033603
launched_at=timeutils.utcnow(),
3604-
vm_state=vm_states.STOPPED)
3604+
vm_state=vm_states.ACTIVE)
36053605
fake_bdm['instance_uuid'] = fake_bdm['instance']['uuid']
36063606
fake_bdm = objects.BlockDeviceMapping._from_db_object(
36073607
self.context, objects.BlockDeviceMapping(),
36083608
fake_bdm, expected_attrs=['instance'])
36093609

36103610
mock_get_bdm.return_value = fake_bdm
3611+
delete_info = {
3612+
'merge_target_file': 'foo.qcow2',
3613+
}
36113614

3612-
with mock.patch.object(self.compute_api.compute_rpcapi,
3613-
'volume_snapshot_delete') as mock_snapshot:
3614-
self.compute_api.volume_snapshot_delete(self.context, volume_id,
3615-
snapshot_id, {})
3616-
3615+
with mock.patch.object(
3616+
self.compute_api.compute_rpcapi, 'volume_snapshot_delete'
3617+
) as mock_snapshot:
3618+
self.compute_api.volume_snapshot_delete(
3619+
self.context, volume_id, snapshot_id, delete_info
3620+
)
36173621
mock_get_bdm.assert_called_once_with(self.context, volume_id,
36183622
expected_attrs=['instance'])
36193623
mock_snapshot.assert_called_once_with(
3620-
self.context, fake_bdm['instance'], volume_id, snapshot_id, {})
3624+
self.context, fake_bdm['instance'], volume_id, snapshot_id,
3625+
delete_info
3626+
)
36213627

36223628
@mock.patch.object(
36233629
objects.BlockDeviceMapping, 'get_by_volume',
@@ -3651,6 +3657,43 @@ def test_volume_snapshot_delete_shelved_offloaded(self, bdm_get_by_volume):
36513657
self.context, mock.sentinel.volume_id,
36523658
mock.sentinel.snapshot_id, mock.sentinel.delete_info)
36533659

3660+
@mock.patch.object(compute_api.API, '_get_bdm_by_volume_id')
3661+
def test_volume_snapshot_delete_intermediary_commit(self, mock_get_bdm):
3662+
fake_bdm = fake_block_device.FakeDbBlockDeviceDict({
3663+
'id': 123,
3664+
'device_name': '/dev/sda2',
3665+
'source_type': 'volume',
3666+
'destination_type': 'volume',
3667+
'connection_info': "{'fake': 'connection_info'}",
3668+
'volume_id': uuids.volume_id,
3669+
'boot_index': -1
3670+
})
3671+
fake_bdm['instance'] = fake_instance.fake_db_instance(
3672+
launched_at=timeutils.utcnow(),
3673+
vm_state=vm_states.STOPPED)
3674+
fake_bdm['instance_uuid'] = fake_bdm['instance']['uuid']
3675+
fake_bdm = objects.BlockDeviceMapping._from_db_object(
3676+
self.context, objects.BlockDeviceMapping(),
3677+
fake_bdm, expected_attrs=['instance'])
3678+
mock_get_bdm.return_value = fake_bdm
3679+
3680+
# c-vol can provide delete_info with merge_target_file pointing to
3681+
# an intermediary snapshot to commit into it's base. This is only
3682+
# supported while the instance is running at present.
3683+
delete_info = {
3684+
'merge_target_file': 'snap.img'
3685+
}
3686+
3687+
# Assert that the request is rejected as offline commit isn't supported
3688+
self.assertRaises(
3689+
exception.InstanceInvalidState,
3690+
self.compute_api.volume_snapshot_delete,
3691+
self.context,
3692+
uuids.volume_id,
3693+
uuids.snapshot_id,
3694+
delete_info
3695+
)
3696+
36543697
def _create_instance_with_disabled_disk_config(self, object=False):
36553698
sys_meta = {"image_auto_disk_config": "Disabled"}
36563699
params = {"system_metadata": sys_meta}

0 commit comments

Comments
 (0)