Skip to content

Commit 26d65fc

Browse files
committed
libvirt: Do not destroy volume secrets during _hard_reboot
Ia2007bc63ef09931ea0197cef29d6a5614ed821a unfortunately missed that resume_state_on_host_boot calls down into _hard_reboot always removing volume secrets rendering that change useless. This change seeks to address this by using the destroy_secrets kwarg introduced by I856268b371f7ba712b02189db3c927cd762a4dc3 within the _hard_reboot method of the libvirt driver to ensure secrets are not removed during a hard reboot. This resolves the original issue in bug #1905701 *and* allows admins to hard reboot a users instance when that instance has encrypted volumes attached with secrets stored in Barbican. This latter use case being something we can easily test within tempest unlike the compute reboot in bug #1905701. This change is kept small as it should ideally be backported alongside Ia2007bc63ef09931ea0197cef29d6a5614ed821a to stable/queens. Follow up changes on master will improve formatting, doc text and introduce functional tests to further validate this new behaviour of hard reboot within the libvirt driver. Closes-Bug: #1905701 Change-Id: I3d1b21ba6eb3f5eb728693197c24b4b315eef821
1 parent 1cc52fd commit 26d65fc

File tree

2 files changed

+124
-11
lines changed

2 files changed

+124
-11
lines changed

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

Lines changed: 102 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9049,6 +9049,19 @@ def test_disconnect_volume_luks(self, mock_get_volume_encryptor):
90499049
uuids.volume_id)
90509050
mock_encryptor.detach_volume.assert_not_called()
90519051

9052+
# assert that no attempt to remove the secret is made when
9053+
# destroy_secrets=False
9054+
drvr._host.find_secret.reset_mock()
9055+
drvr._host.delete_secret.reset_mock()
9056+
drvr._disconnect_volume(
9057+
self.context,
9058+
connection_info,
9059+
instance,
9060+
encryption=encryption,
9061+
destroy_secrets=False
9062+
)
9063+
drvr._host.delete_secret.assert_not_called()
9064+
90529065
# assert that the encryptor is used if no secret is found
90539066
drvr._host.find_secret.reset_mock()
90549067
drvr._host.delete_secret.reset_mock()
@@ -10112,6 +10125,36 @@ def test_detach_encryptor_native_luks_device_path_secret_missing(
1011210125
mock_find_secret.assert_called_once_with('volume', uuids.volume_id)
1011310126
mock_get_encryptor.assert_not_called()
1011410127

10128+
@mock.patch('nova.virt.libvirt.host.Host.delete_secret')
10129+
@mock.patch('nova.virt.libvirt.host.Host.find_secret', new=mock.Mock())
10130+
def test_detach_encryptor_skip_secret_removal(self, mock_delete_secret):
10131+
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
10132+
drvr._detach_encryptor(
10133+
self.context,
10134+
{
10135+
'data': {
10136+
'volume_id': uuids.volume_id
10137+
}
10138+
},
10139+
None,
10140+
destroy_secrets=False
10141+
)
10142+
# Assert that no attempt is made to delete the volume secert
10143+
mock_delete_secret.assert_not_called()
10144+
10145+
drvr._detach_encryptor(
10146+
self.context,
10147+
{
10148+
'data': {
10149+
'volume_id': uuids.volume_id
10150+
}
10151+
},
10152+
None,
10153+
destroy_secrets=True
10154+
)
10155+
# Assert that volume secert is deleted
10156+
mock_delete_secret.assert_called_once_with('volume', uuids.volume_id)
10157+
1011510158
def test_allow_native_luksv1(self):
1011610159
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
1011710160
self.assertFalse(drvr._allow_native_luksv1({}))
@@ -15752,7 +15795,8 @@ def destroy_side_effect(*args, **kwargs):
1575215795
mock_domain_destroy.assert_called_once_with()
1575315796
mock_teardown_container.assert_called_once_with(instance)
1575415797
mock_cleanup.assert_called_once_with(self.context, instance,
15755-
network_info, None, False)
15798+
network_info, None, False,
15799+
destroy_secrets=True)
1575615800

1575715801
@mock.patch.object(libvirt_driver.LibvirtDriver, 'cleanup')
1575815802
@mock.patch.object(libvirt_driver.LibvirtDriver, '_teardown_container')
@@ -15772,7 +15816,8 @@ def test_destroy_lxc_calls_teardown_container_when_no_domain(self,
1577215816
mock.call(instance)])
1577315817
mock_teardown_container.assert_called_once_with(instance)
1577415818
mock_cleanup.assert_called_once_with(self.context, instance,
15775-
network_info, None, False)
15819+
network_info, None, False,
15820+
destroy_secrets=True)
1577615821

1577715822
@mock.patch.object(host.Host, 'get_guest')
1577815823
def test_reboot_different_ids(self, mock_get):
@@ -15993,7 +16038,8 @@ def test_hard_reboot(self, mock_get_mdev, mock_destroy, mock_get_disk_info,
1599316038
mock_get_mdev.assert_called_once_with(instance)
1599416039
mock_destroy.assert_called_once_with(self.context, instance,
1599516040
network_info, destroy_disks=False,
15996-
block_device_info=block_device_info)
16041+
block_device_info=block_device_info,
16042+
destroy_secrets=False)
1599716043

1599816044
mock_get_guest_xml.assert_called_once_with(self.context, instance,
1599916045
network_info, mock.ANY, mock.ANY,
@@ -19278,6 +19324,59 @@ def test_cleanup_instance_dir_with_rbd_workaround(self,
1927819324
self.assertTrue(instance.cleaned)
1927919325
save.assert_called_once_with()
1928019326

19327+
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._disconnect_volume')
19328+
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._undefine_domain',
19329+
new=mock.Mock())
19330+
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._get_vpmems',
19331+
new=mock.Mock(return_value=None))
19332+
def test_cleanup_destroy_secrets(self, mock_disconnect_volume):
19333+
block_device_info = {
19334+
'block_device_mapping': [
19335+
{
19336+
'connection_info': mock.sentinel.connection_info
19337+
}
19338+
]
19339+
}
19340+
instance = objects.Instance(self.context, **self.test_instance)
19341+
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI())
19342+
19343+
# Pass destroy_vifs=False and destroy_disks=False as we only care about
19344+
# asserting the behaviour of destroy_secrets in this test.
19345+
drvr.cleanup(
19346+
self.context,
19347+
instance,
19348+
network_info={},
19349+
block_device_info=block_device_info,
19350+
destroy_vifs=False,
19351+
destroy_disks=False,
19352+
destroy_secrets=False
19353+
)
19354+
drvr.cleanup(
19355+
self.context,
19356+
instance,
19357+
network_info={},
19358+
block_device_info=block_device_info,
19359+
destroy_vifs=False,
19360+
destroy_disks=False,
19361+
)
19362+
19363+
# Assert that disconnect_volume is called with destroy_secrets=False
19364+
# and destroy_secrets=True by default
19365+
mock_disconnect_volume.assert_has_calls([
19366+
mock.call(
19367+
self.context,
19368+
mock.sentinel.connection_info,
19369+
instance,
19370+
destroy_secrets=False
19371+
),
19372+
mock.call(
19373+
self.context,
19374+
mock.sentinel.connection_info,
19375+
instance,
19376+
destroy_secrets=True
19377+
)
19378+
])
19379+
1928119380
@mock.patch.object(libvirt_driver.LibvirtDriver, '_get_volume_encryption')
1928219381
@mock.patch.object(libvirt_driver.LibvirtDriver, '_allow_native_luksv1')
1928319382
def test_swap_volume_native_luks_blocked(self, mock_allow_native_luksv1,

nova/virt/libvirt/driver.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1411,7 +1411,7 @@ def destroy(self, context, instance, network_info, block_device_info=None,
14111411
# unblock the waiting threads and clean up.
14121412
self._device_event_handler.cleanup_waiters(instance.uuid)
14131413
self.cleanup(context, instance, network_info, block_device_info,
1414-
destroy_disks)
1414+
destroy_disks, destroy_secrets=destroy_secrets)
14151415

14161416
def _undefine_domain(self, instance):
14171417
try:
@@ -1485,11 +1485,12 @@ def cleanup(self, context, instance, network_info, block_device_info=None,
14851485
block_device_info=block_device_info,
14861486
destroy_vifs=destroy_vifs,
14871487
cleanup_instance_dir=cleanup_instance_dir,
1488-
cleanup_instance_disks=cleanup_instance_disks)
1488+
cleanup_instance_disks=cleanup_instance_disks,
1489+
destroy_secrets=destroy_secrets)
14891490

14901491
def _cleanup(self, context, instance, network_info, block_device_info=None,
14911492
destroy_vifs=True, cleanup_instance_dir=False,
1492-
cleanup_instance_disks=False):
1493+
cleanup_instance_disks=False, destroy_secrets=True):
14931494
"""Cleanup the domain and any attached resources from the host.
14941495

14951496
This method cleans up any pmem devices, unplugs VIFs, disconnects
@@ -1530,7 +1531,9 @@ def _cleanup(self, context, instance, network_info, block_device_info=None,
15301531
continue
15311532

15321533
try:
1533-
self._disconnect_volume(context, connection_info, instance)
1534+
self._disconnect_volume(
1535+
context, connection_info, instance,
1536+
destroy_secrets=destroy_secrets)
15341537
except Exception as exc:
15351538
with excutils.save_and_reraise_exception() as ctxt:
15361539
if cleanup_instance_disks:
@@ -1847,8 +1850,13 @@ def _should_disconnect_target(self, context, instance, multiattach,
18471850
return (False if connection_count > 1 else True)
18481851

18491852
def _disconnect_volume(self, context, connection_info, instance,
1850-
encryption=None):
1851-
self._detach_encryptor(context, connection_info, encryption=encryption)
1853+
encryption=None, destroy_secrets=True):
1854+
self._detach_encryptor(
1855+
context,
1856+
connection_info,
1857+
encryption=encryption,
1858+
destroy_secrets=destroy_secrets
1859+
)
18521860
vol_driver = self._get_volume_driver(connection_info)
18531861
volume_id = driver_block_device.get_volume_id(connection_info)
18541862
multiattach = connection_info.get('multiattach', False)
@@ -1961,7 +1969,8 @@ def _attach_encryptor(self, context, connection_info, encryption):
19611969
encryption)
19621970
encryptor.attach_volume(context, **encryption)
19631971

1964-
def _detach_encryptor(self, context, connection_info, encryption):
1972+
def _detach_encryptor(self, context, connection_info, encryption,
1973+
destroy_secrets=True):
19651974
"""Detach the frontend encryptor if one is required by the volume.
19661975

19671976
The request context is only used when an encryption metadata dict is
@@ -1973,7 +1982,11 @@ def _detach_encryptor(self, context, connection_info, encryption):
19731982
"""
19741983
volume_id = driver_block_device.get_volume_id(connection_info)
19751984
if volume_id and self._host.find_secret('volume', volume_id):
1985+
if not destroy_secrets:
1986+
LOG.debug("Skipping volume secret destruction")
1987+
return
19761988
return self._host.delete_secret('volume', volume_id)
1989+
19771990
if encryption is None:
19781991
encryption = self._get_volume_encryption(context, connection_info)
19791992

@@ -3729,7 +3742,8 @@ def _hard_reboot(self, context, instance, network_info,
37293742
# we can here without losing data. This allows us to re-initialise from
37303743
# scratch, and hopefully fix, most aspects of a non-functioning guest.
37313744
self.destroy(context, instance, network_info, destroy_disks=False,
3732-
block_device_info=block_device_info)
3745+
block_device_info=block_device_info,
3746+
destroy_secrets=False)
37333747

37343748
# Convert the system metadata to image metadata
37353749
# NOTE(mdbooth): This is a workaround for stateless Nova compute

0 commit comments

Comments
 (0)