Skip to content

Commit bc96af5

Browse files
committed
libvirt: Do not reference VIR_ERR_DEVICE_MISSING when libvirt is < v4.1.0
I7eb86edc130d186a66c04b229d46347ec5c0b625 introduced VIR_ERR_DEVICE_MISSING into the hot unplug libvirt error code list within detach_device_with_retry. While the change correctly referenced that the error code was introduced in v4.1.0 it made no attempt to handle versions prior to this. With MIN_LIBVIRT_VERSION currently pinned to v4.0.0 we need to handle libvirt < v4.1.0 to avoid referencing the non-existent error code within the libvirt module. Closes-Bug: #1891547 Change-Id: I32908b77c18f8ec08211dd67be49bbf903611c34
1 parent cff7382 commit bc96af5

File tree

4 files changed

+141
-21
lines changed

4 files changed

+141
-21
lines changed

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

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9460,6 +9460,51 @@ def test_attach_volume_with_vir_domain_affect_live_flag(self,
94609460
mock_build_metadata.assert_called_with(self.context, instance)
94619461
mock_save.assert_called_with()
94629462

9463+
@mock.patch('nova.virt.libvirt.host.Host.get_guest')
9464+
@mock.patch.object(fakelibvirt.Connection, 'getLibVersion')
9465+
@mock.patch(
9466+
'nova.virt.libvirt.driver.LibvirtDriver._disconnect_volume',
9467+
new=mock.Mock())
9468+
def test_detach_volume_supports_device_missing(
9469+
self, mock_get_version, mock_get_guest):
9470+
"""Assert that VIR_ERR_DEVICE_MISSING is only used if libvirt >= v4.1.0
9471+
"""
9472+
mock_guest = mock.Mock(spec=libvirt_guest.Guest)
9473+
mock_guest.get_power_state.return_value = power_state.RUNNING
9474+
mock_get_guest.return_value = mock_guest
9475+
9476+
v4_0_0 = versionutils.convert_version_to_int((4, 0, 0))
9477+
mock_get_version.return_value = v4_0_0
9478+
9479+
mountpoint = "/dev/foo"
9480+
expected_disk_dev = "foo"
9481+
9482+
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
9483+
drvr.detach_volume(
9484+
self.context, mock.sentinel.connection_info,
9485+
mock.sentinel.instance, mountpoint)
9486+
9487+
# Assert supports_device_missing_error_code=False is used
9488+
mock_guest.detach_device_with_retry.assert_called_once_with(
9489+
mock_guest.get_disk, expected_disk_dev, live=True,
9490+
supports_device_missing_error_code=False)
9491+
9492+
# reset and try again with v4.1.0
9493+
mock_guest.reset_mock()
9494+
mock_get_version.reset_mock()
9495+
9496+
v4_1_0 = versionutils.convert_version_to_int((4, 1, 0))
9497+
mock_get_version.return_value = v4_1_0
9498+
9499+
drvr.detach_volume(
9500+
self.context, mock.sentinel.connection_info,
9501+
mock.sentinel.instance, mountpoint)
9502+
9503+
# Assert supports_device_missing_error_code=True is used
9504+
mock_guest.detach_device_with_retry.assert_called_once_with(
9505+
mock_guest.get_disk, expected_disk_dev, live=True,
9506+
supports_device_missing_error_code=True)
9507+
94639508
@mock.patch('nova.virt.libvirt.host.Host._get_domain')
94649509
def test_detach_volume_with_vir_domain_affect_live_flag(self,
94659510
mock_get_domain):
@@ -18812,6 +18857,51 @@ def test_detach_volume_with_instance_not_found(self):
1881218857
_disconnect_volume.assert_called_once_with(
1881318858
self.context, connection_info, instance, encryption=None)
1881418859

18860+
@mock.patch('nova.virt.libvirt.host.Host.get_guest')
18861+
@mock.patch.object(fakelibvirt.Connection, 'getLibVersion')
18862+
def test_detach_interface_supports_device_missing(
18863+
self, mock_get_version, mock_get_guest):
18864+
"""Assert that VIR_ERR_DEVICE_MISSING is only used if libvirt >= v4.1.0
18865+
"""
18866+
mock_guest = mock.Mock(spec=libvirt_guest.Guest)
18867+
mock_guest.get_power_state.return_value = power_state.RUNNING
18868+
mock_get_guest.return_value = mock_guest
18869+
18870+
v4_0_0 = versionutils.convert_version_to_int((4, 0, 0))
18871+
mock_get_version.return_value = v4_0_0
18872+
18873+
instance = objects.Instance(**self.test_instance)
18874+
18875+
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
18876+
with mock.patch.object(drvr, 'vif_driver') as mock_vif_driver:
18877+
mock_vif_driver.get_config.return_value = mock.sentinel.cfg
18878+
mock_vif_driver.get_vif_devname.return_value = mock.sentinel.dev
18879+
18880+
drvr.detach_interface(
18881+
self.context, instance, mock.sentinel.vif)
18882+
18883+
# Assert supports_device_missing_error_code=False is used
18884+
mock_guest.detach_device_with_retry.assert_called_once_with(
18885+
mock_guest.get_interface_by_cfg, mock.sentinel.cfg, live=True,
18886+
alternative_device_name=mock.sentinel.dev,
18887+
supports_device_missing_error_code=False)
18888+
18889+
# reset and try again with v4.1.0
18890+
mock_guest.reset_mock()
18891+
mock_get_version.reset_mock()
18892+
18893+
v4_1_0 = versionutils.convert_version_to_int((4, 1, 0))
18894+
mock_get_version.return_value = v4_1_0
18895+
18896+
drvr.detach_interface(
18897+
self.context, instance, mock.sentinel.vif)
18898+
18899+
# Assert supports_device_missing_error_code=True is used
18900+
mock_guest.detach_device_with_retry.assert_called_once_with(
18901+
mock_guest.get_interface_by_cfg, mock.sentinel.cfg, live=True,
18902+
alternative_device_name=mock.sentinel.dev,
18903+
supports_device_missing_error_code=True)
18904+
1881518905
def _test_attach_detach_interface_get_config(self, method_name):
1881618906
"""Tests that the get_config() method is properly called in
1881718907
attach_interface() and detach_interface().

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

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -294,8 +294,8 @@ def test_detach_device_with_retry_device_not_found_alt_name(self):
294294

295295
@mock.patch.object(libvirt_guest.Guest, "detach_device")
296296
def _test_detach_device_with_retry_second_detach_failure(
297-
self, mock_detach, error_code=fakelibvirt.VIR_ERR_DEVICE_MISSING,
298-
error_message="device not found: disk vdb not found"):
297+
self, mock_detach, error_code=None, error_message=None,
298+
supports_device_missing=False):
299299
# This simulates a retry of the transient/live domain detach
300300
# failing because the device is not found
301301
conf = mock.Mock(spec=vconfig.LibvirtConfigGuestDevice)
@@ -312,7 +312,8 @@ def _test_detach_device_with_retry_second_detach_failure(
312312
mock_detach.side_effect = [None, fake_exc]
313313
retry_detach = self.guest.detach_device_with_retry(
314314
get_config, fake_device, live=True,
315-
inc_sleep_time=.01, max_retry_count=3)
315+
inc_sleep_time=.01, max_retry_count=3,
316+
supports_device_missing_error_code=supports_device_missing)
316317
# Some time later, we can do the wait/retry to ensure detach
317318
self.assertRaises(exception.DeviceNotFound, retry_detach)
318319
# Check that the save_and_reraise_exception context manager didn't log
@@ -325,20 +326,25 @@ def _test_detach_device_with_retry_second_detach_failure(
325326
def test_detach_device_with_retry_second_detach_operation_failed(self):
326327
self._test_detach_device_with_retry_second_detach_failure(
327328
error_code=fakelibvirt.VIR_ERR_OPERATION_FAILED,
328-
error_message="operation failed: disk vdb not found")
329+
error_message="operation failed: disk vdb not found",
330+
supports_device_missing=False)
329331

330332
# TODO(lyarwood): Remove this test once MIN_LIBVIRT_VERSION is >= 4.1.0
331333
def test_detach_device_with_retry_second_detach_internal_error(self):
332334
self._test_detach_device_with_retry_second_detach_failure(
333335
error_code=fakelibvirt.VIR_ERR_INTERNAL_ERROR,
334-
error_message="operation failed: disk vdb not found")
336+
error_message="operation failed: disk vdb not found",
337+
supports_device_missing=False)
335338

336339
def test_detach_device_with_retry_second_detach_device_missing(self):
337-
self._test_detach_device_with_retry_second_detach_failure()
340+
self._test_detach_device_with_retry_second_detach_failure(
341+
error_code=fakelibvirt.VIR_ERR_DEVICE_MISSING,
342+
error_message="device not found: disk vdb not found",
343+
supports_device_missing=True)
338344

339345
def _test_detach_device_with_retry_first_detach_failure(
340-
self, error_code=fakelibvirt.VIR_ERR_DEVICE_MISSING,
341-
error_message="device not found: disk vdb not found"):
346+
self, error_code=None, error_message=None,
347+
supports_device_missing=False):
342348
# This simulates a persistent or live domain detach failing because the
343349
# device is not found during the first attempt to detach the device.
344350
# We should still attempt to detach the device from the live config if
@@ -369,7 +375,8 @@ def _test_detach_device_with_retry_first_detach_failure(
369375
# succeeds afterward
370376
self.domain.detachDeviceFlags.side_effect = [fake_exc, None]
371377
retry_detach = self.guest.detach_device_with_retry(get_config,
372-
fake_device, live=True, inc_sleep_time=.01, max_retry_count=3)
378+
fake_device, live=True, inc_sleep_time=.01, max_retry_count=3,
379+
supports_device_missing_error_code=supports_device_missing)
373380
# We should have tried to detach from the persistent domain
374381
self.domain.detachDeviceFlags.assert_called_once_with(
375382
"</xml>", flags=(fakelibvirt.VIR_DOMAIN_AFFECT_CONFIG |
@@ -385,22 +392,28 @@ def _test_detach_device_with_retry_first_detach_failure(
385392
def test_detach_device_with_retry_first_detach_operation_failed(self):
386393
self._test_detach_device_with_retry_first_detach_failure(
387394
error_code=fakelibvirt.VIR_ERR_OPERATION_FAILED,
388-
error_message="operation failed: disk vdb not found")
395+
error_message="operation failed: disk vdb not found",
396+
supports_device_missing=False)
389397

390398
# TODO(lyarwood): Remove this test once MIN_LIBVIRT_VERSION is >= 4.1.0
391399
def test_detach_device_with_retry_first_detach_internal_error(self):
392400
self._test_detach_device_with_retry_first_detach_failure(
393401
error_code=fakelibvirt.VIR_ERR_INTERNAL_ERROR,
394-
error_message="operation failed: disk vdb not found")
402+
error_message="operation failed: disk vdb not found",
403+
supports_device_missing=False)
395404

396405
# TODO(lyarwood): Remove this test once MIN_LIBVIRT_VERSION is >= 4.1.0
397406
def test_detach_device_with_retry_first_detach_invalid_arg(self):
398407
self._test_detach_device_with_retry_first_detach_failure(
399408
error_code=fakelibvirt.VIR_ERR_INVALID_ARG,
400-
error_message="invalid argument: no target device vdb")
409+
error_message="invalid argument: no target device vdb",
410+
supports_device_missing=False)
401411

402412
def test_detach_device_with_retry_first_detach_device_missing(self):
403-
self._test_detach_device_with_retry_first_detach_failure()
413+
self._test_detach_device_with_retry_first_detach_failure(
414+
error_code=fakelibvirt.VIR_ERR_DEVICE_MISSING,
415+
error_message="device not found: disk vdb not found",
416+
supports_device_missing=True)
404417

405418
def test_get_xml_desc(self):
406419
self.guest.get_xml_desc()

nova/virt/libvirt/driver.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,8 @@ def repr_method(self):
275275
MIN_LIBVIRT_BLOCKDEV = (6, 0, 0)
276276
MIN_QEMU_BLOCKDEV = (4, 2, 0)
277277

278+
MIN_LIBVIRT_VIR_ERR_DEVICE_MISSING = (4, 1, 0)
279+
278280

279281
class LibvirtDriver(driver.ComputeDriver):
280282
def __init__(self, virtapi, read_only=False):
@@ -2015,9 +2017,11 @@ def detach_volume(self, context, connection_info, instance, mountpoint,
20152017
# detaching any attached encryptors or disconnecting the underlying
20162018
# volume in _disconnect_volume. Otherwise, the encryptor or volume
20172019
# driver may report that the volume is still in use.
2018-
wait_for_detach = guest.detach_device_with_retry(guest.get_disk,
2019-
disk_dev,
2020-
live=live)
2020+
supports_device_missing = self._host.has_min_version(
2021+
MIN_LIBVIRT_VIR_ERR_DEVICE_MISSING)
2022+
wait_for_detach = guest.detach_device_with_retry(
2023+
guest.get_disk, disk_dev, live=live,
2024+
supports_device_missing_error_code=supports_device_missing)
20212025
wait_for_detach()
20222026

20232027
except exception.InstanceNotFound:
@@ -2220,9 +2224,12 @@ def detach_interface(self, context, instance, vif):
22202224
live = state in (power_state.RUNNING, power_state.PAUSED)
22212225
# Now we are going to loop until the interface is detached or we
22222226
# timeout.
2227+
supports_device_missing = self._host.has_min_version(
2228+
MIN_LIBVIRT_VIR_ERR_DEVICE_MISSING)
22232229
wait_for_detach = guest.detach_device_with_retry(
22242230
guest.get_interface_by_cfg, cfg, live=live,
2225-
alternative_device_name=self.vif_driver.get_vif_devname(vif))
2231+
alternative_device_name=self.vif_driver.get_vif_devname(vif),
2232+
supports_device_missing_error_code=supports_device_missing)
22262233
wait_for_detach()
22272234
except exception.DeviceDetachFailed:
22282235
# We failed to detach the device even with the retry loop, so let's

nova/virt/libvirt/guest.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,8 @@ def get_all_devices(self, devtype=None):
375375
def detach_device_with_retry(self, get_device_conf_func, device, live,
376376
max_retry_count=7, inc_sleep_time=2,
377377
max_sleep_time=30,
378-
alternative_device_name=None):
378+
alternative_device_name=None,
379+
supports_device_missing_error_code=False):
379380
"""Detaches a device from the guest. After the initial detach request,
380381
a function is returned which can be used to ensure the device is
381382
successfully removed from the guest domain (retrying the removal as
@@ -396,8 +397,16 @@ def detach_device_with_retry(self, get_device_conf_func, device, live,
396397
max_sleep_time will be used as the sleep time.
397398
:param alternative_device_name: This is an alternative identifier for
398399
the device if device is not an ID, used solely for error messages.
400+
:param supports_device_missing_error_code: does the installed version
401+
of libvirt provide the
402+
VIR_ERR_DEVICE_MISSING error
403+
code.
399404
"""
400405
alternative_device_name = alternative_device_name or device
406+
unplug_libvirt_error_codes = set([
407+
libvirt.VIR_ERR_OPERATION_FAILED,
408+
libvirt.VIR_ERR_INTERNAL_ERROR
409+
])
401410

402411
def _try_detach_device(conf, persistent=False, live=False):
403412
# Raise DeviceNotFound if the device isn't found during detach
@@ -414,9 +423,10 @@ def _try_detach_device(conf, persistent=False, live=False):
414423
# TODO(lyarwood): Remove libvirt.VIR_ERR_OPERATION_FAILED
415424
# and libvirt.VIR_ERR_INTERNAL_ERROR once
416425
# MIN_LIBVIRT_VERSION is >= 4.1.0
417-
if errcode in (libvirt.VIR_ERR_OPERATION_FAILED,
418-
libvirt.VIR_ERR_INTERNAL_ERROR,
419-
libvirt.VIR_ERR_DEVICE_MISSING):
426+
if supports_device_missing_error_code:
427+
unplug_libvirt_error_codes.add(
428+
libvirt.VIR_ERR_DEVICE_MISSING)
429+
if errcode in unplug_libvirt_error_codes:
420430
# TODO(lyarwood): Remove the following error message
421431
# check once we only care about VIR_ERR_DEVICE_MISSING
422432
errmsg = ex.get_error_message()

0 commit comments

Comments
 (0)