Merge "libvirt: Do not reference VIR_ERR_DEVICE_MISSING when libvirt is < v4.1.0" into stable/rocky

This commit is contained in:
Zuul 2020-09-24 20:01:05 +00:00 committed by Gerrit Code Review
commit f10d5bdbfa
4 changed files with 141 additions and 21 deletions

View File

@ -7907,6 +7907,51 @@ class LibvirtConnTestCase(test.NoDBTestCase,
mock_build_metadata.assert_called_with(self.context, instance) mock_build_metadata.assert_called_with(self.context, instance)
mock_save.assert_called_with() mock_save.assert_called_with()
@mock.patch('nova.virt.libvirt.host.Host.get_guest')
@mock.patch.object(fakelibvirt.Connection, 'getLibVersion')
@mock.patch(
'nova.virt.libvirt.driver.LibvirtDriver._disconnect_volume',
new=mock.Mock())
def test_detach_volume_supports_device_missing(
self, mock_get_version, mock_get_guest):
"""Assert that VIR_ERR_DEVICE_MISSING is only used if libvirt >= v4.1.0
"""
mock_guest = mock.Mock(spec=libvirt_guest.Guest)
mock_guest.get_power_state.return_value = power_state.RUNNING
mock_get_guest.return_value = mock_guest
v4_0_0 = versionutils.convert_version_to_int((4, 0, 0))
mock_get_version.return_value = v4_0_0
mountpoint = "/dev/foo"
expected_disk_dev = "foo"
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
drvr.detach_volume(
self.context, mock.sentinel.connection_info,
mock.sentinel.instance, mountpoint)
# Assert supports_device_missing_error_code=False is used
mock_guest.detach_device_with_retry.assert_called_once_with(
mock_guest.get_disk, expected_disk_dev, live=True,
supports_device_missing_error_code=False)
# reset and try again with v4.1.0
mock_guest.reset_mock()
mock_get_version.reset_mock()
v4_1_0 = versionutils.convert_version_to_int((4, 1, 0))
mock_get_version.return_value = v4_1_0
drvr.detach_volume(
self.context, mock.sentinel.connection_info,
mock.sentinel.instance, mountpoint)
# Assert supports_device_missing_error_code=True is used
mock_guest.detach_device_with_retry.assert_called_once_with(
mock_guest.get_disk, expected_disk_dev, live=True,
supports_device_missing_error_code=True)
@mock.patch('nova.virt.libvirt.host.Host._get_domain') @mock.patch('nova.virt.libvirt.host.Host._get_domain')
def test_detach_volume_with_vir_domain_affect_live_flag(self, def test_detach_volume_with_vir_domain_affect_live_flag(self,
mock_get_domain): mock_get_domain):
@ -16762,6 +16807,51 @@ class LibvirtConnTestCase(test.NoDBTestCase,
_disconnect_volume.assert_called_once_with( _disconnect_volume.assert_called_once_with(
self.context, connection_info, instance, encryption=None) self.context, connection_info, instance, encryption=None)
@mock.patch('nova.virt.libvirt.host.Host.get_guest')
@mock.patch.object(fakelibvirt.Connection, 'getLibVersion')
def test_detach_interface_supports_device_missing(
self, mock_get_version, mock_get_guest):
"""Assert that VIR_ERR_DEVICE_MISSING is only used if libvirt >= v4.1.0
"""
mock_guest = mock.Mock(spec=libvirt_guest.Guest)
mock_guest.get_power_state.return_value = power_state.RUNNING
mock_get_guest.return_value = mock_guest
v4_0_0 = versionutils.convert_version_to_int((4, 0, 0))
mock_get_version.return_value = v4_0_0
instance = objects.Instance(**self.test_instance)
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
with mock.patch.object(drvr, 'vif_driver') as mock_vif_driver:
mock_vif_driver.get_config.return_value = mock.sentinel.cfg
mock_vif_driver.get_vif_devname.return_value = mock.sentinel.dev
drvr.detach_interface(
self.context, instance, mock.sentinel.vif)
# Assert supports_device_missing_error_code=False is used
mock_guest.detach_device_with_retry.assert_called_once_with(
mock_guest.get_interface_by_cfg, mock.sentinel.cfg, live=True,
alternative_device_name=mock.sentinel.dev,
supports_device_missing_error_code=False)
# reset and try again with v4.1.0
mock_guest.reset_mock()
mock_get_version.reset_mock()
v4_1_0 = versionutils.convert_version_to_int((4, 1, 0))
mock_get_version.return_value = v4_1_0
drvr.detach_interface(
self.context, instance, mock.sentinel.vif)
# Assert supports_device_missing_error_code=True is used
mock_guest.detach_device_with_retry.assert_called_once_with(
mock_guest.get_interface_by_cfg, mock.sentinel.cfg, live=True,
alternative_device_name=mock.sentinel.dev,
supports_device_missing_error_code=True)
def _test_attach_detach_interface_get_config(self, method_name): def _test_attach_detach_interface_get_config(self, method_name):
"""Tests that the get_config() method is properly called in """Tests that the get_config() method is properly called in
attach_interface() and detach_interface(). attach_interface() and detach_interface().

View File

@ -306,8 +306,8 @@ class GuestTestCase(test.NoDBTestCase):
@mock.patch.object(libvirt_guest.Guest, "detach_device") @mock.patch.object(libvirt_guest.Guest, "detach_device")
def _test_detach_device_with_retry_second_detach_failure( def _test_detach_device_with_retry_second_detach_failure(
self, mock_detach, error_code=fakelibvirt.VIR_ERR_DEVICE_MISSING, self, mock_detach, error_code=None, error_message=None,
error_message="device not found: disk vdb not found"): supports_device_missing=False):
# This simulates a retry of the transient/live domain detach # This simulates a retry of the transient/live domain detach
# failing because the device is not found # failing because the device is not found
conf = mock.Mock(spec=vconfig.LibvirtConfigGuestDevice) conf = mock.Mock(spec=vconfig.LibvirtConfigGuestDevice)
@ -324,7 +324,8 @@ class GuestTestCase(test.NoDBTestCase):
mock_detach.side_effect = [None, fake_exc] mock_detach.side_effect = [None, fake_exc]
retry_detach = self.guest.detach_device_with_retry( retry_detach = self.guest.detach_device_with_retry(
get_config, fake_device, live=True, get_config, fake_device, live=True,
inc_sleep_time=.01, max_retry_count=3) inc_sleep_time=.01, max_retry_count=3,
supports_device_missing_error_code=supports_device_missing)
# Some time later, we can do the wait/retry to ensure detach # Some time later, we can do the wait/retry to ensure detach
self.assertRaises(exception.DeviceNotFound, retry_detach) self.assertRaises(exception.DeviceNotFound, retry_detach)
# Check that the save_and_reraise_exception context manager didn't log # Check that the save_and_reraise_exception context manager didn't log
@ -337,20 +338,25 @@ class GuestTestCase(test.NoDBTestCase):
def test_detach_device_with_retry_second_detach_operation_failed(self): def test_detach_device_with_retry_second_detach_operation_failed(self):
self._test_detach_device_with_retry_second_detach_failure( self._test_detach_device_with_retry_second_detach_failure(
error_code=fakelibvirt.VIR_ERR_OPERATION_FAILED, error_code=fakelibvirt.VIR_ERR_OPERATION_FAILED,
error_message="operation failed: disk vdb not found") error_message="operation failed: disk vdb not found",
supports_device_missing=False)
# TODO(lyarwood): Remove this test once MIN_LIBVIRT_VERSION is >= 4.1.0 # TODO(lyarwood): Remove this test once MIN_LIBVIRT_VERSION is >= 4.1.0
def test_detach_device_with_retry_second_detach_internal_error(self): def test_detach_device_with_retry_second_detach_internal_error(self):
self._test_detach_device_with_retry_second_detach_failure( self._test_detach_device_with_retry_second_detach_failure(
error_code=fakelibvirt.VIR_ERR_INTERNAL_ERROR, error_code=fakelibvirt.VIR_ERR_INTERNAL_ERROR,
error_message="operation failed: disk vdb not found") error_message="operation failed: disk vdb not found",
supports_device_missing=False)
def test_detach_device_with_retry_second_detach_device_missing(self): def test_detach_device_with_retry_second_detach_device_missing(self):
self._test_detach_device_with_retry_second_detach_failure() self._test_detach_device_with_retry_second_detach_failure(
error_code=fakelibvirt.VIR_ERR_DEVICE_MISSING,
error_message="device not found: disk vdb not found",
supports_device_missing=True)
def _test_detach_device_with_retry_first_detach_failure( def _test_detach_device_with_retry_first_detach_failure(
self, error_code=fakelibvirt.VIR_ERR_DEVICE_MISSING, self, error_code=None, error_message=None,
error_message="device not found: disk vdb not found"): supports_device_missing=False):
# This simulates a persistent or live domain detach failing because the # This simulates a persistent or live domain detach failing because the
# device is not found during the first attempt to detach the device. # device is not found during the first attempt to detach the device.
# We should still attempt to detach the device from the live config if # We should still attempt to detach the device from the live config if
@ -381,7 +387,8 @@ class GuestTestCase(test.NoDBTestCase):
# succeeds afterward # succeeds afterward
self.domain.detachDeviceFlags.side_effect = [fake_exc, None] self.domain.detachDeviceFlags.side_effect = [fake_exc, None]
retry_detach = self.guest.detach_device_with_retry(get_config, retry_detach = self.guest.detach_device_with_retry(get_config,
fake_device, live=True, inc_sleep_time=.01, max_retry_count=3) fake_device, live=True, inc_sleep_time=.01, max_retry_count=3,
supports_device_missing_error_code=supports_device_missing)
# We should have tried to detach from the persistent domain # We should have tried to detach from the persistent domain
self.domain.detachDeviceFlags.assert_called_once_with( self.domain.detachDeviceFlags.assert_called_once_with(
"</xml>", flags=(fakelibvirt.VIR_DOMAIN_AFFECT_CONFIG | "</xml>", flags=(fakelibvirt.VIR_DOMAIN_AFFECT_CONFIG |
@ -397,22 +404,28 @@ class GuestTestCase(test.NoDBTestCase):
def test_detach_device_with_retry_first_detach_operation_failed(self): def test_detach_device_with_retry_first_detach_operation_failed(self):
self._test_detach_device_with_retry_first_detach_failure( self._test_detach_device_with_retry_first_detach_failure(
error_code=fakelibvirt.VIR_ERR_OPERATION_FAILED, error_code=fakelibvirt.VIR_ERR_OPERATION_FAILED,
error_message="operation failed: disk vdb not found") error_message="operation failed: disk vdb not found",
supports_device_missing=False)
# TODO(lyarwood): Remove this test once MIN_LIBVIRT_VERSION is >= 4.1.0 # TODO(lyarwood): Remove this test once MIN_LIBVIRT_VERSION is >= 4.1.0
def test_detach_device_with_retry_first_detach_internal_error(self): def test_detach_device_with_retry_first_detach_internal_error(self):
self._test_detach_device_with_retry_first_detach_failure( self._test_detach_device_with_retry_first_detach_failure(
error_code=fakelibvirt.VIR_ERR_INTERNAL_ERROR, error_code=fakelibvirt.VIR_ERR_INTERNAL_ERROR,
error_message="operation failed: disk vdb not found") error_message="operation failed: disk vdb not found",
supports_device_missing=False)
# TODO(lyarwood): Remove this test once MIN_LIBVIRT_VERSION is >= 4.1.0 # TODO(lyarwood): Remove this test once MIN_LIBVIRT_VERSION is >= 4.1.0
def test_detach_device_with_retry_first_detach_invalid_arg(self): def test_detach_device_with_retry_first_detach_invalid_arg(self):
self._test_detach_device_with_retry_first_detach_failure( self._test_detach_device_with_retry_first_detach_failure(
error_code=fakelibvirt.VIR_ERR_INVALID_ARG, error_code=fakelibvirt.VIR_ERR_INVALID_ARG,
error_message="invalid argument: no target device vdb") error_message="invalid argument: no target device vdb",
supports_device_missing=False)
def test_detach_device_with_retry_first_detach_device_missing(self): def test_detach_device_with_retry_first_detach_device_missing(self):
self._test_detach_device_with_retry_first_detach_failure() self._test_detach_device_with_retry_first_detach_failure(
error_code=fakelibvirt.VIR_ERR_DEVICE_MISSING,
error_message="device not found: disk vdb not found",
supports_device_missing=True)
def test_get_xml_desc(self): def test_get_xml_desc(self):
self.guest.get_xml_desc() self.guest.get_xml_desc()

View File

@ -306,6 +306,8 @@ VGPU_RESOURCE_SEMAPHORE = "vgpu_resources"
# domain XML is persisted on the destination. # domain XML is persisted on the destination.
MIN_LIBVIRT_MIGRATE_PARAM_PERSIST_XML = (1, 3, 4) MIN_LIBVIRT_MIGRATE_PARAM_PERSIST_XML = (1, 3, 4)
MIN_LIBVIRT_VIR_ERR_DEVICE_MISSING = (4, 1, 0)
class LibvirtDriver(driver.ComputeDriver): class LibvirtDriver(driver.ComputeDriver):
capabilities = { capabilities = {
@ -1712,9 +1714,11 @@ class LibvirtDriver(driver.ComputeDriver):
# detaching any attached encryptors or disconnecting the underlying # detaching any attached encryptors or disconnecting the underlying
# volume in _disconnect_volume. Otherwise, the encryptor or volume # volume in _disconnect_volume. Otherwise, the encryptor or volume
# driver may report that the volume is still in use. # driver may report that the volume is still in use.
wait_for_detach = guest.detach_device_with_retry(guest.get_disk, supports_device_missing = self._host.has_min_version(
disk_dev, MIN_LIBVIRT_VIR_ERR_DEVICE_MISSING)
live=live) wait_for_detach = guest.detach_device_with_retry(
guest.get_disk, disk_dev, live=live,
supports_device_missing_error_code=supports_device_missing)
wait_for_detach() wait_for_detach()
except exception.InstanceNotFound: except exception.InstanceNotFound:
@ -1844,9 +1848,12 @@ class LibvirtDriver(driver.ComputeDriver):
live = state in (power_state.RUNNING, power_state.PAUSED) live = state in (power_state.RUNNING, power_state.PAUSED)
# Now we are going to loop until the interface is detached or we # Now we are going to loop until the interface is detached or we
# timeout. # timeout.
supports_device_missing = self._host.has_min_version(
MIN_LIBVIRT_VIR_ERR_DEVICE_MISSING)
wait_for_detach = guest.detach_device_with_retry( wait_for_detach = guest.detach_device_with_retry(
guest.get_interface_by_cfg, cfg, live=live, guest.get_interface_by_cfg, cfg, live=live,
alternative_device_name=self.vif_driver.get_vif_devname(vif)) alternative_device_name=self.vif_driver.get_vif_devname(vif),
supports_device_missing_error_code=supports_device_missing)
wait_for_detach() wait_for_detach()
except exception.DeviceDetachFailed: except exception.DeviceDetachFailed:
# We failed to detach the device even with the retry loop, so let's # We failed to detach the device even with the retry loop, so let's

View File

@ -370,7 +370,8 @@ class Guest(object):
def detach_device_with_retry(self, get_device_conf_func, device, live, def detach_device_with_retry(self, get_device_conf_func, device, live,
max_retry_count=7, inc_sleep_time=2, max_retry_count=7, inc_sleep_time=2,
max_sleep_time=30, max_sleep_time=30,
alternative_device_name=None): alternative_device_name=None,
supports_device_missing_error_code=False):
"""Detaches a device from the guest. After the initial detach request, """Detaches a device from the guest. After the initial detach request,
a function is returned which can be used to ensure the device is a function is returned which can be used to ensure the device is
successfully removed from the guest domain (retrying the removal as successfully removed from the guest domain (retrying the removal as
@ -391,8 +392,16 @@ class Guest(object):
max_sleep_time will be used as the sleep time. max_sleep_time will be used as the sleep time.
:param alternative_device_name: This is an alternative identifier for :param alternative_device_name: This is an alternative identifier for
the device if device is not an ID, used solely for error messages. the device if device is not an ID, used solely for error messages.
:param supports_device_missing_error_code: does the installed version
of libvirt provide the
VIR_ERR_DEVICE_MISSING error
code.
""" """
alternative_device_name = alternative_device_name or device alternative_device_name = alternative_device_name or device
unplug_libvirt_error_codes = set([
libvirt.VIR_ERR_OPERATION_FAILED,
libvirt.VIR_ERR_INTERNAL_ERROR
])
def _try_detach_device(conf, persistent=False, live=False): def _try_detach_device(conf, persistent=False, live=False):
# Raise DeviceNotFound if the device isn't found during detach # Raise DeviceNotFound if the device isn't found during detach
@ -409,9 +418,10 @@ class Guest(object):
# TODO(lyarwood): Remove libvirt.VIR_ERR_OPERATION_FAILED # TODO(lyarwood): Remove libvirt.VIR_ERR_OPERATION_FAILED
# and libvirt.VIR_ERR_INTERNAL_ERROR once # and libvirt.VIR_ERR_INTERNAL_ERROR once
# MIN_LIBVIRT_VERSION is >= 4.1.0 # MIN_LIBVIRT_VERSION is >= 4.1.0
if errcode in (libvirt.VIR_ERR_OPERATION_FAILED, if supports_device_missing_error_code:
libvirt.VIR_ERR_INTERNAL_ERROR, unplug_libvirt_error_codes.add(
libvirt.VIR_ERR_DEVICE_MISSING): libvirt.VIR_ERR_DEVICE_MISSING)
if errcode in unplug_libvirt_error_codes:
# TODO(lyarwood): Remove the following error message # TODO(lyarwood): Remove the following error message
# check once we only care about VIR_ERR_DEVICE_MISSING # check once we only care about VIR_ERR_DEVICE_MISSING
errmsg = ex.get_error_message() errmsg = ex.get_error_message()