libvirt: Detach volumes from a domain before detaching any encryptors

I983f80822a5 introduced retry logic around the detaching of volumes from
a libvirt domain. However it also reordered the surrounding code so that
we attempt to detach any encryptors from a volume first while the volume
is still attached to the domain. This can lead to `Device or resource
busy` errors if the volume is still being used by the instance when we
attempt to detach the encryptors.

Closes-bug: #1642628
Change-Id: Ia0f8e725ec8a0fbc44bd4592b021dea978cf4e4f
(cherry picked from commit d55c5136d0)
This commit is contained in:
Lee Yarwood 2016-11-16 15:51:02 +00:00
parent c6743ca709
commit 7a7bbb6da2
2 changed files with 38 additions and 4 deletions

View File

@ -6233,6 +6233,40 @@ class LibvirtConnTestCase(test.NoDBTestCase):
mock_get_domain.assert_called_once_with(instance)
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._disconnect_volume')
@mock.patch('nova.volume.encryptors.get_volume_encryptor')
@mock.patch('nova.virt.libvirt.host.Host.get_guest')
def test_detach_volume_order_with_encryptors(self, mock_get_guest,
mock_get_encryptor, mock_disconnect_volume):
mock_guest = mock.MagicMock(spec=libvirt_guest.Guest)
mock_guest.get_power_state.return_value = power_state.RUNNING
mock_get_guest.return_value = mock_guest
mock_encryptor = mock.MagicMock(
spec=nova.volume.encryptors.nop.NoOpEncryptor)
mock_get_encryptor.return_value = mock_encryptor
mock_order = mock.Mock()
mock_order.attach_mock(mock_disconnect_volume, 'disconnect_volume')
mock_order.attach_mock(mock_guest.detach_device_with_retry(),
'detach_volume')
mock_order.attach_mock(mock_encryptor.detach_volume,
'detach_encryptor')
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
instance = objects.Instance(**self.test_instance)
connection_info = {"driver_volume_type": "fake",
"data": {"device_path": "/fake",
"access_mode": "rw"}}
encryption = {"provider": "NoOpEncryptor"}
drvr.detach_volume(connection_info, instance, '/dev/vdc',
encryption=encryption)
mock_order.assert_has_calls([
mock.call.detach_volume(),
mock.call.detach_encryptor(**encryption),
mock.call.disconnect_volume(connection_info, 'vdc')])
def test_multi_nic(self):
network_info = _fake_network_info(self, 2)
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)

View File

@ -1279,20 +1279,20 @@ class LibvirtDriver(driver.ComputeDriver):
state = guest.get_power_state(self._host)
live = state in (power_state.RUNNING, power_state.PAUSED)
# The volume must be detached from the VM before disconnecting it
# from its encryptor. Otherwise, the encryptor may report that the
# volume is still in use.
wait_for_detach = guest.detach_device_with_retry(guest.get_disk,
disk_dev,
persistent=True,
live=live)
wait_for_detach()
if encryption:
# The volume must be detached from the VM before
# disconnecting it from its encryptor. Otherwise, the
# encryptor may report that the volume is still in use.
encryptor = self._get_volume_encryptor(connection_info,
encryption)
encryptor.detach_volume(**encryption)
wait_for_detach()
except exception.InstanceNotFound:
# NOTE(zhaoqin): If the instance does not exist, _lookup_by_name()
# will throw InstanceNotFound exception. Need to