Skip to content

Commit 9cac2a8

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 (cherry picked from commit 26d65fc)
1 parent f2ba671 commit 9cac2a8

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
@@ -9046,6 +9046,19 @@ def test_disconnect_volume_luks(self, mock_get_volume_encryptor):
90469046
uuids.volume_id)
90479047
mock_encryptor.detach_volume.assert_not_called()
90489048

9049+
# assert that no attempt to remove the secret is made when
9050+
# destroy_secrets=False
9051+
drvr._host.find_secret.reset_mock()
9052+
drvr._host.delete_secret.reset_mock()
9053+
drvr._disconnect_volume(
9054+
self.context,
9055+
connection_info,
9056+
instance,
9057+
encryption=encryption,
9058+
destroy_secrets=False
9059+
)
9060+
drvr._host.delete_secret.assert_not_called()
9061+
90499062
# assert that the encryptor is used if no secret is found
90509063
drvr._host.find_secret.reset_mock()
90519064
drvr._host.delete_secret.reset_mock()
@@ -10109,6 +10122,36 @@ def test_detach_encryptor_native_luks_device_path_secret_missing(
1010910122
mock_find_secret.assert_called_once_with('volume', uuids.volume_id)
1011010123
mock_get_encryptor.assert_not_called()
1011110124

10125+
@mock.patch('nova.virt.libvirt.host.Host.delete_secret')
10126+
@mock.patch('nova.virt.libvirt.host.Host.find_secret', new=mock.Mock())
10127+
def test_detach_encryptor_skip_secret_removal(self, mock_delete_secret):
10128+
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
10129+
drvr._detach_encryptor(
10130+
self.context,
10131+
{
10132+
'data': {
10133+
'volume_id': uuids.volume_id
10134+
}
10135+
},
10136+
None,
10137+
destroy_secrets=False
10138+
)
10139+
# Assert that no attempt is made to delete the volume secert
10140+
mock_delete_secret.assert_not_called()
10141+
10142+
drvr._detach_encryptor(
10143+
self.context,
10144+
{
10145+
'data': {
10146+
'volume_id': uuids.volume_id
10147+
}
10148+
},
10149+
None,
10150+
destroy_secrets=True
10151+
)
10152+
# Assert that volume secert is deleted
10153+
mock_delete_secret.assert_called_once_with('volume', uuids.volume_id)
10154+
1011210155
def test_allow_native_luksv1(self):
1011310156
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
1011410157
self.assertFalse(drvr._allow_native_luksv1({}))
@@ -15710,7 +15753,8 @@ def destroy_side_effect(*args, **kwargs):
1571015753
mock_domain_destroy.assert_called_once_with()
1571115754
mock_teardown_container.assert_called_once_with(instance)
1571215755
mock_cleanup.assert_called_once_with(self.context, instance,
15713-
network_info, None, False)
15756+
network_info, None, False,
15757+
destroy_secrets=True)
1571415758

1571515759
@mock.patch.object(libvirt_driver.LibvirtDriver, 'cleanup')
1571615760
@mock.patch.object(libvirt_driver.LibvirtDriver, '_teardown_container')
@@ -15730,7 +15774,8 @@ def test_destroy_lxc_calls_teardown_container_when_no_domain(self,
1573015774
mock.call(instance)])
1573115775
mock_teardown_container.assert_called_once_with(instance)
1573215776
mock_cleanup.assert_called_once_with(self.context, instance,
15733-
network_info, None, False)
15777+
network_info, None, False,
15778+
destroy_secrets=True)
1573415779

1573515780
@mock.patch.object(host.Host, 'get_guest')
1573615781
def test_reboot_different_ids(self, mock_get):
@@ -15951,7 +15996,8 @@ def test_hard_reboot(self, mock_get_mdev, mock_destroy, mock_get_disk_info,
1595115996
mock_get_mdev.assert_called_once_with(instance)
1595215997
mock_destroy.assert_called_once_with(self.context, instance,
1595315998
network_info, destroy_disks=False,
15954-
block_device_info=block_device_info)
15999+
block_device_info=block_device_info,
16000+
destroy_secrets=False)
1595516001

1595616002
mock_get_guest_xml.assert_called_once_with(self.context, instance,
1595716003
network_info, mock.ANY, mock.ANY,
@@ -19236,6 +19282,59 @@ def test_cleanup_instance_dir_with_rbd_workaround(self,
1923619282
self.assertTrue(instance.cleaned)
1923719283
save.assert_called_once_with()
1923819284

19285+
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._disconnect_volume')
19286+
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._undefine_domain',
19287+
new=mock.Mock())
19288+
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._get_vpmems',
19289+
new=mock.Mock(return_value=None))
19290+
def test_cleanup_destroy_secrets(self, mock_disconnect_volume):
19291+
block_device_info = {
19292+
'block_device_mapping': [
19293+
{
19294+
'connection_info': mock.sentinel.connection_info
19295+
}
19296+
]
19297+
}
19298+
instance = objects.Instance(self.context, **self.test_instance)
19299+
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI())
19300+
19301+
# Pass destroy_vifs=False and destroy_disks=False as we only care about
19302+
# asserting the behaviour of destroy_secrets in this test.
19303+
drvr.cleanup(
19304+
self.context,
19305+
instance,
19306+
network_info={},
19307+
block_device_info=block_device_info,
19308+
destroy_vifs=False,
19309+
destroy_disks=False,
19310+
destroy_secrets=False
19311+
)
19312+
drvr.cleanup(
19313+
self.context,
19314+
instance,
19315+
network_info={},
19316+
block_device_info=block_device_info,
19317+
destroy_vifs=False,
19318+
destroy_disks=False,
19319+
)
19320+
19321+
# Assert that disconnect_volume is called with destroy_secrets=False
19322+
# and destroy_secrets=True by default
19323+
mock_disconnect_volume.assert_has_calls([
19324+
mock.call(
19325+
self.context,
19326+
mock.sentinel.connection_info,
19327+
instance,
19328+
destroy_secrets=False
19329+
),
19330+
mock.call(
19331+
self.context,
19332+
mock.sentinel.connection_info,
19333+
instance,
19334+
destroy_secrets=True
19335+
)
19336+
])
19337+
1923919338
@mock.patch.object(libvirt_driver.LibvirtDriver, '_get_volume_encryption')
1924019339
@mock.patch.object(libvirt_driver.LibvirtDriver, '_allow_native_luksv1')
1924119340
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
@@ -1408,7 +1408,7 @@ def destroy(self, context, instance, network_info, block_device_info=None,
14081408
# unblock the waiting threads and clean up.
14091409
self._device_event_handler.cleanup_waiters(instance.uuid)
14101410
self.cleanup(context, instance, network_info, block_device_info,
1411-
destroy_disks)
1411+
destroy_disks, destroy_secrets=destroy_secrets)
14121412

14131413
def _undefine_domain(self, instance):
14141414
try:
@@ -1482,11 +1482,12 @@ def cleanup(self, context, instance, network_info, block_device_info=None,
14821482
block_device_info=block_device_info,
14831483
destroy_vifs=destroy_vifs,
14841484
cleanup_instance_dir=cleanup_instance_dir,
1485-
cleanup_instance_disks=cleanup_instance_disks)
1485+
cleanup_instance_disks=cleanup_instance_disks,
1486+
destroy_secrets=destroy_secrets)
14861487

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

14921493
This method cleans up any pmem devices, unplugs VIFs, disconnects
@@ -1527,7 +1528,9 @@ def _cleanup(self, context, instance, network_info, block_device_info=None,
15271528
continue
15281529

15291530
try:
1530-
self._disconnect_volume(context, connection_info, instance)
1531+
self._disconnect_volume(
1532+
context, connection_info, instance,
1533+
destroy_secrets=destroy_secrets)
15311534
except Exception as exc:
15321535
with excutils.save_and_reraise_exception() as ctxt:
15331536
if cleanup_instance_disks:
@@ -1844,8 +1847,13 @@ def _should_disconnect_target(self, context, instance, multiattach,
18441847
return (False if connection_count > 1 else True)
18451848

18461849
def _disconnect_volume(self, context, connection_info, instance,
1847-
encryption=None):
1848-
self._detach_encryptor(context, connection_info, encryption=encryption)
1850+
encryption=None, destroy_secrets=True):
1851+
self._detach_encryptor(
1852+
context,
1853+
connection_info,
1854+
encryption=encryption,
1855+
destroy_secrets=destroy_secrets
1856+
)
18491857
vol_driver = self._get_volume_driver(connection_info)
18501858
volume_id = driver_block_device.get_volume_id(connection_info)
18511859
multiattach = connection_info.get('multiattach', False)
@@ -1958,7 +1966,8 @@ def _attach_encryptor(self, context, connection_info, encryption):
19581966
encryption)
19591967
encryptor.attach_volume(context, **encryption)
19601968

1961-
def _detach_encryptor(self, context, connection_info, encryption):
1969+
def _detach_encryptor(self, context, connection_info, encryption,
1970+
destroy_secrets=True):
19621971
"""Detach the frontend encryptor if one is required by the volume.
19631972

19641973
The request context is only used when an encryption metadata dict is
@@ -1970,7 +1979,11 @@ def _detach_encryptor(self, context, connection_info, encryption):
19701979
"""
19711980
volume_id = driver_block_device.get_volume_id(connection_info)
19721981
if volume_id and self._host.find_secret('volume', volume_id):
1982+
if not destroy_secrets:
1983+
LOG.debug("Skipping volume secret destruction")
1984+
return
19731985
return self._host.delete_secret('volume', volume_id)
1986+
19741987
if encryption is None:
19751988
encryption = self._get_volume_encryption(context, connection_info)
19761989

@@ -3726,7 +3739,8 @@ def _hard_reboot(self, context, instance, network_info,
37263739
# we can here without losing data. This allows us to re-initialise from
37273740
# scratch, and hopefully fix, most aspects of a non-functioning guest.
37283741
self.destroy(context, instance, network_info, destroy_disks=False,
3729-
block_device_info=block_device_info)
3742+
block_device_info=block_device_info,
3743+
destroy_secrets=False)
37303744

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

0 commit comments

Comments
 (0)