Skip to content

Commit

Permalink
Merge "libvirt: Do not reference VIR_ERR_DEVICE_MISSING when libvirt …
Browse files Browse the repository at this point in the history
…is < v4.1.0" into stable/ussuri
  • Loading branch information
Zuul authored and openstack-gerrit committed Aug 26, 2020
2 parents 7ed7eb7 + 3f3b889 commit 1afed30
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 21 deletions.
90 changes: 90 additions & 0 deletions nova/tests/unit/virt/libvirt/test_driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -9102,6 +9102,51 @@ def test_attach_volume_with_vir_domain_affect_live_flag(self,
mock_build_metadata.assert_called_with(self.context, instance)
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')
def test_detach_volume_with_vir_domain_affect_live_flag(self,
mock_get_domain):
Expand Down Expand Up @@ -18463,6 +18508,51 @@ def test_detach_volume_with_instance_not_found(self):
_disconnect_volume.assert_called_once_with(
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):
"""Tests that the get_config() method is properly called in
attach_interface() and detach_interface().
Expand Down
39 changes: 26 additions & 13 deletions nova/tests/unit/virt/libvirt/test_guest.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,8 @@ def test_detach_device_with_retry_device_not_found_alt_name(self):

@mock.patch.object(libvirt_guest.Guest, "detach_device")
def _test_detach_device_with_retry_second_detach_failure(
self, mock_detach, error_code=fakelibvirt.VIR_ERR_DEVICE_MISSING,
error_message="device not found: disk vdb not found"):
self, mock_detach, error_code=None, error_message=None,
supports_device_missing=False):
# This simulates a retry of the transient/live domain detach
# failing because the device is not found
conf = mock.Mock(spec=vconfig.LibvirtConfigGuestDevice)
Expand All @@ -305,7 +305,8 @@ def _test_detach_device_with_retry_second_detach_failure(
mock_detach.side_effect = [None, fake_exc]
retry_detach = self.guest.detach_device_with_retry(
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
self.assertRaises(exception.DeviceNotFound, retry_detach)
# Check that the save_and_reraise_exception context manager didn't log
Expand All @@ -318,20 +319,25 @@ def _test_detach_device_with_retry_second_detach_failure(
def test_detach_device_with_retry_second_detach_operation_failed(self):
self._test_detach_device_with_retry_second_detach_failure(
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
def test_detach_device_with_retry_second_detach_internal_error(self):
self._test_detach_device_with_retry_second_detach_failure(
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):
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(
self, error_code=fakelibvirt.VIR_ERR_DEVICE_MISSING,
error_message="device not found: disk vdb not found"):
self, error_code=None, error_message=None,
supports_device_missing=False):
# This simulates a persistent or live domain detach failing because the
# 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
Expand Down Expand Up @@ -362,7 +368,8 @@ def _test_detach_device_with_retry_first_detach_failure(
# succeeds afterward
self.domain.detachDeviceFlags.side_effect = [fake_exc, None]
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
self.domain.detachDeviceFlags.assert_called_once_with(
"</xml>", flags=(fakelibvirt.VIR_DOMAIN_AFFECT_CONFIG |
Expand All @@ -378,22 +385,28 @@ def _test_detach_device_with_retry_first_detach_failure(
def test_detach_device_with_retry_first_detach_operation_failed(self):
self._test_detach_device_with_retry_first_detach_failure(
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
def test_detach_device_with_retry_first_detach_internal_error(self):
self._test_detach_device_with_retry_first_detach_failure(
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
def test_detach_device_with_retry_first_detach_invalid_arg(self):
self._test_detach_device_with_retry_first_detach_failure(
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):
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):
self.guest.get_xml_desc()
Expand Down
15 changes: 11 additions & 4 deletions nova/virt/libvirt/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,8 @@ def repr_method(self):
MIN_LIBVIRT_BLOCKDEV = (6, 0, 0)
MIN_QEMU_BLOCKDEV = (4, 2, 0)

MIN_LIBVIRT_VIR_ERR_DEVICE_MISSING = (4, 1, 0)


class LibvirtDriver(driver.ComputeDriver):
def __init__(self, virtapi, read_only=False):
Expand Down Expand Up @@ -2030,9 +2032,11 @@ def detach_volume(self, context, connection_info, instance, mountpoint,
# detaching any attached encryptors or disconnecting the underlying
# volume in _disconnect_volume. Otherwise, the encryptor or volume
# driver may report that the volume is still in use.
wait_for_detach = guest.detach_device_with_retry(guest.get_disk,
disk_dev,
live=live)
supports_device_missing = self._host.has_min_version(
MIN_LIBVIRT_VIR_ERR_DEVICE_MISSING)
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()

except exception.InstanceNotFound:
Expand Down Expand Up @@ -2235,9 +2239,12 @@ def detach_interface(self, context, instance, vif):
live = state in (power_state.RUNNING, power_state.PAUSED)
# Now we are going to loop until the interface is detached or we
# timeout.
supports_device_missing = self._host.has_min_version(
MIN_LIBVIRT_VIR_ERR_DEVICE_MISSING)
wait_for_detach = guest.detach_device_with_retry(
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()
except exception.DeviceDetachFailed:
# We failed to detach the device even with the retry loop, so let's
Expand Down
18 changes: 14 additions & 4 deletions nova/virt/libvirt/guest.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,8 @@ def get_all_devices(self, devtype=None):
def detach_device_with_retry(self, get_device_conf_func, device, live,
max_retry_count=7, inc_sleep_time=2,
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,
a function is returned which can be used to ensure the device is
successfully removed from the guest domain (retrying the removal as
Expand All @@ -378,8 +379,16 @@ def detach_device_with_retry(self, get_device_conf_func, device, live,
max_sleep_time will be used as the sleep time.
:param alternative_device_name: This is an alternative identifier for
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
unplug_libvirt_error_codes = set([
libvirt.VIR_ERR_OPERATION_FAILED,
libvirt.VIR_ERR_INTERNAL_ERROR
])

def _try_detach_device(conf, persistent=False, live=False):
# Raise DeviceNotFound if the device isn't found during detach
Expand All @@ -396,9 +405,10 @@ def _try_detach_device(conf, persistent=False, live=False):
# TODO(lyarwood): Remove libvirt.VIR_ERR_OPERATION_FAILED
# and libvirt.VIR_ERR_INTERNAL_ERROR once
# MIN_LIBVIRT_VERSION is >= 4.1.0
if errcode in (libvirt.VIR_ERR_OPERATION_FAILED,
libvirt.VIR_ERR_INTERNAL_ERROR,
libvirt.VIR_ERR_DEVICE_MISSING):
if supports_device_missing_error_code:
unplug_libvirt_error_codes.add(
libvirt.VIR_ERR_DEVICE_MISSING)
if errcode in unplug_libvirt_error_codes:
# TODO(lyarwood): Remove the following error message
# check once we only care about VIR_ERR_DEVICE_MISSING
errmsg = ex.get_error_message()
Expand Down

0 comments on commit 1afed30

Please sign in to comment.