Skip to content

Commit 8b50f48

Browse files
author
Balazs Gibizer
committed
Consolidate device detach error handling
After I7f2b6330decb92e2838aa7cee47fb228f00f47da the error handling of the callers of _detach_with_retry() can be consolidated. Libvirt related errors now handled in _detach_sync() and various DeviceNotFound cases now don't need special handling on the caller side. Change-Id: Ic6eb66bc2396949aceecda50bda334a1bc7dab31 (cherry picked from commit 7317cfb)
1 parent ebf1ceb commit 8b50f48

File tree

2 files changed

+63
-122
lines changed

2 files changed

+63
-122
lines changed

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

Lines changed: 56 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -18589,6 +18589,7 @@ def _test_attach_detach_interface_get_config(self, method_name):
1858918589
lambda self, instance: FakeVirtDomain())
1859018590

1859118591
instance = objects.Instance(**self.test_instance)
18592+
instance.info_cache = None
1859218593
network_info = _fake_network_info(self)
1859318594
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
1859418595

@@ -22957,23 +22958,20 @@ def _test_detach_interface(self, state, device_not_found=False):
2295722958
# This will trigger _detach_with_retry to raise
2295822959
# DeviceNotFound
2295922960
get_interface_calls = [
22960-
expected_cfg, # detach_interface() itself gets the config
2296122961
expected_cfg, # _detach_with_retry: persistent config
2296222962
None, # _detach_with_retry: no device in live config
2296322963
None, # _detach_with_retry: persistent config gone as detached
2296422964
]
2296522965
else:
2296622966
if state in (power_state.RUNNING, power_state.PAUSED):
2296722967
get_interface_calls = [
22968-
expected_cfg, # detach_interface() itself gets the config
2296922968
expected_cfg, # _detach_with_retry: persistent config
2297022969
expected_cfg, # _detach_with_retry: live config
2297122970
None, # _detach_with_retry: persistent config gone
2297222971
None # _detach_with_retry: live config gone
2297322972
]
2297422973
else:
2297522974
get_interface_calls = [
22976-
expected_cfg, # detach_interface() itself gets the config
2297722975
expected_cfg, # _detach_with_retry: persistent config
2297822976
None, # _detach_with_retry: persistent config gone
2297922977
]
@@ -23008,7 +23006,6 @@ def _test_detach_interface(self, state, device_not_found=False):
2300823006
flags=fakelibvirt.VIR_DOMAIN_AFFECT_CONFIG)
2300923007
mock_get_interface.assert_has_calls(
2301023008
[
23011-
mock.call(expected_cfg),
2301223009
mock.call(expected_cfg, from_persistent_config=True),
2301323010
mock.call(expected_cfg),
2301423011
mock.call(expected_cfg, from_persistent_config=True),
@@ -23026,7 +23023,6 @@ def _test_detach_interface(self, state, device_not_found=False):
2302623023
])
2302723024
mock_get_interface.assert_has_calls(
2302823025
[
23029-
mock.call(expected_cfg),
2303023026
mock.call(expected_cfg, from_persistent_config=True),
2303123027
mock.call(expected_cfg),
2303223028
mock.call(expected_cfg, from_persistent_config=True),
@@ -23038,7 +23034,6 @@ def _test_detach_interface(self, state, device_not_found=False):
2303823034
flags=fakelibvirt.VIR_DOMAIN_AFFECT_CONFIG)
2303923035
mock_get_interface.assert_has_calls(
2304023036
[
23041-
mock.call(expected_cfg),
2304223037
mock.call(expected_cfg, from_persistent_config=True),
2304323038
mock.call(expected_cfg, from_persistent_config=True),
2304423039
])
@@ -23064,6 +23059,7 @@ def test_detach_interface_device_not_found(self, mock_log):
2306423059
# Asserts that we don't log an error when the interface device is not
2306523060
# found on the guest after a libvirt error during detach.
2306623061
instance = self._create_instance()
23062+
instance.info_cache = None
2306723063
vif = _fake_network_info(self)[0]
2306823064
guest = mock.Mock(spec=libvirt_guest.Guest)
2306923065
guest.get_power_state = mock.Mock()
@@ -23081,36 +23077,6 @@ def test_detach_interface_device_not_found(self, mock_log):
2308123077
self.assertIn('the device is no longer found on the guest',
2308223078
str(mock_log.warning.call_args[0]))
2308323079

23084-
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver.'
23085-
'_detach_with_retry')
23086-
@mock.patch('nova.virt.libvirt.driver.LOG')
23087-
def test_detach_interface_guest_not_found_after_detach(
23088-
self, mock_log, mock_detach_with_retry
23089-
):
23090-
# Asserts that we don't raise an exception when the guest is gone
23091-
# after a libvirt error during detach.
23092-
instance = self._create_instance()
23093-
vif = _fake_network_info(self, 1)[0]
23094-
guest = mock.MagicMock()
23095-
guest.get_power_state.return_value = power_state.RUNNING
23096-
guest.get_interface_by_cfg.return_value = (
23097-
vconfig.LibvirtConfigGuestInterface())
23098-
get_guest_mock = mock.Mock()
23099-
# Host.get_guest should be called twice: the first time it is found,
23100-
# the second time it is gone.
23101-
get_guest_mock.side_effect = (
23102-
guest, exception.InstanceNotFound(instance_id=instance.uuid))
23103-
self.drvr._host.get_guest = get_guest_mock
23104-
error = fakelibvirt.libvirtError(
23105-
'internal error: End of file from qemu monitor')
23106-
error.err = (fakelibvirt.VIR_ERR_OPERATION_FAILED,)
23107-
mock_detach_with_retry.side_effect = error
23108-
self.drvr.detach_interface(self.context, instance, vif)
23109-
self.assertEqual(1, mock_log.info.call_count)
23110-
self.assertIn('Instance disappeared while detaching interface',
23111-
mock_log.info.call_args[0][0])
23112-
get_guest_mock.assert_has_calls([mock.call(instance)] * 2)
23113-
2311423080
@mock.patch('threading.Event.wait', new=mock.Mock())
2311523081
@mock.patch.object(FakeVirtDomain, 'info')
2311623082
@mock.patch.object(FakeVirtDomain, 'detachDeviceFlags')
@@ -23154,7 +23120,6 @@ def test_detach_interface_device_with_same_mac_address(
2315423120
mock.patch.object(
2315523121
libvirt_guest.Guest, 'get_interface_by_cfg',
2315623122
side_effect=[
23157-
expected, # detach_interface gets the config
2315823123
expected, # _detach_with_retry: persistent config
2315923124
expected, # _detach_with_retry: live config
2316023125
None, # _detach_with_retry: persistent gone
@@ -23168,14 +23133,12 @@ def test_detach_interface_device_with_same_mac_address(
2316823133

2316923134
mock_get_interface.assert_has_calls(
2317023135
[
23171-
mock.call(expected, ),
2317223136
mock.call(expected, from_persistent_config=True),
2317323137
mock.call(expected),
2317423138
mock.call(expected, from_persistent_config=True),
2317523139
mock.call(expected),
2317623140
]
2317723141
)
23178-
self.assertEqual(5, mock_get_interface.call_count)
2317923142
mock_get_config.assert_called_once_with(
2318023143
instance, network_info[0], test.MatchType(objects.ImageMeta),
2318123144
test.MatchType(objects.Flavor), CONF.libvirt.virt_type)
@@ -23197,7 +23160,6 @@ def test_detach_interface_guest_set_metadata(self):
2319723160
instance = self._create_instance()
2319823161
network_info = _fake_network_info(self, num_networks=3)
2319923162
vif = network_info[0]
23200-
interface = vconfig.LibvirtConfigGuestInterface()
2320123163
image_meta = objects.ImageMeta.from_dict({})
2320223164
disk_info = blockinfo.get_disk_info(
2320323165
CONF.libvirt.virt_type, instance, image_meta)
@@ -23210,8 +23172,6 @@ def test_detach_interface_guest_set_metadata(self):
2321023172
self.drvr._host, 'get_guest', return_value=guest),
2321123173
mock.patch.object(
2321223174
self.drvr.vif_driver, 'get_config', return_value=cfg),
23213-
mock.patch.object(
23214-
guest, 'get_interface_by_cfg', return_value=interface),
2321523175
mock.patch.object(
2321623176
instance, 'get_network_info', return_value=network_info),
2321723177
mock.patch.object(
@@ -23220,16 +23180,15 @@ def test_detach_interface_guest_set_metadata(self):
2322023180
self.drvr, '_get_guest_config_meta', return_value=config_meta),
2322123181
mock.patch.object(guest, 'set_metadata')
2322223182
) as (
23223-
mock_get_guest, mock_get_config, mock_get_interface_by_cfg,
23224-
mock_get_network_info, mock_detach_with_retry,
23225-
mock_get_guest_config_meta, mock_set_metadata
23183+
mock_get_guest, mock_get_config, mock_get_network_info,
23184+
mock_detach_with_retry, mock_get_guest_config_meta,
23185+
mock_set_metadata
2322623186
):
2322723187
self.drvr.detach_interface(self.context, instance, vif)
2322823188
mock_get_guest.assert_called_once_with(instance)
2322923189
mock_get_config.assert_called_once_with(
2323023190
instance, vif, test.MatchType(objects.ImageMeta),
2323123191
test.MatchType(objects.Flavor), CONF.libvirt.virt_type)
23232-
mock_get_interface_by_cfg.assert_called_once_with(cfg)
2323323192
mock_detach_with_retry.assert_called_once_with(
2323423193
guest, instance.uuid, mock.ANY, device_name=None)
2323523194
mock_get_network_info.assert_called_once_with()
@@ -23791,6 +23750,56 @@ def test__detach_with_retry_libvirt_reports_not_found_give_up(self):
2379123750
mock_guest.detach_device.assert_called_once_with(
2379223751
mock_dev, persistent=True, live=False)
2379323752

23753+
@mock.patch.object(libvirt_driver.LOG, 'warning')
23754+
def test__detach_with_retry_libvirt_reports_domain_not_found(
23755+
self, mock_warning
23756+
):
23757+
"""Test that libvirt reports that the domain is not found and assert
23758+
that it is only logged.
23759+
"""
23760+
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
23761+
mock_guest = mock.Mock(spec=libvirt_guest.Guest)
23762+
mock_guest.get_power_state.return_value = power_state.SHUTDOWN
23763+
23764+
mock_dev = mock.Mock(spec=vconfig.LibvirtConfigGuestDisk)
23765+
mock_dev.alias = 'virtio-disk1'
23766+
23767+
mock_get_device_conf_func = mock.Mock(
23768+
# The first call is to get the device from the persistent domain
23769+
# before the detach. The second calls to double check that the
23770+
# device is gone after libvirt returned. This second call is
23771+
# pointless in the current case as libvirt returned that the domain
23772+
# does not exists. So the code could know that there is no device.
23773+
# Still for simplicity the call is made and expected to return None
23774+
side_effect=[
23775+
mock_dev,
23776+
None,
23777+
]
23778+
)
23779+
23780+
mock_guest.detach_device.side_effect = fakelibvirt.make_libvirtError(
23781+
fakelibvirt.libvirtError,
23782+
msg='error',
23783+
error_code=fakelibvirt.VIR_ERR_NO_DOMAIN)
23784+
23785+
drvr._detach_with_retry(
23786+
mock_guest,
23787+
uuids.instance_uuid,
23788+
mock_get_device_conf_func,
23789+
device_name='vdb',
23790+
)
23791+
23792+
mock_guest.has_persistent_configuration.assert_called_once_with()
23793+
mock_get_device_conf_func.assert_has_calls([
23794+
mock.call(from_persistent_config=True),
23795+
mock.call(from_persistent_config=True),
23796+
])
23797+
mock_guest.detach_device.assert_called_once_with(
23798+
mock_dev, persistent=True, live=False)
23799+
mock_warning.assert_called_once_with(
23800+
'During device detach, instance disappeared.',
23801+
instance_uuid=uuids.instance_uuid)
23802+
2379423803
@ddt.data(power_state.RUNNING, power_state.PAUSED)
2379523804
def test__detach_with_retry_other_sync_libvirt_error(self, state):
2379623805
"""Test that libvirt reports non device related error during detach
@@ -23809,7 +23818,7 @@ def test__detach_with_retry_other_sync_libvirt_error(self, state):
2380923818
mock_guest.detach_device.side_effect = fakelibvirt.make_libvirtError(
2381023819
fakelibvirt.libvirtError,
2381123820
msg='error',
23812-
error_code=fakelibvirt.VIR_ERR_NO_DOMAIN)
23821+
error_code=fakelibvirt.VIR_ERR_INTERNAL_ERROR)
2381323822

2381423823
self.assertRaises(
2381523824
fakelibvirt.libvirtError,

nova/virt/libvirt/driver.py

Lines changed: 7 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -2526,6 +2526,13 @@ def _detach_sync(
25262526
'still being in progress.', device_name, instance_uuid)
25272527
return
25282528

2529+
if code == libvirt.VIR_ERR_NO_DOMAIN:
2530+
LOG.warning(
2531+
"During device detach, instance disappeared.",
2532+
instance_uuid=instance_uuid)
2533+
# if the domain has disappeared then we have nothing to detach
2534+
return
2535+
25292536
LOG.warning(
25302537
'Unexpected libvirt error while detaching device %s from '
25312538
'instance %s: %s', device_name, instance_uuid, str(ex))
@@ -2560,17 +2567,6 @@ def detach_volume(self, context, connection_info, instance, mountpoint,
25602567
# call.
25612568
LOG.info("Device %s not found in instance.",
25622569
disk_dev, instance=instance)
2563-
except libvirt.libvirtError as ex:
2564-
# NOTE(vish): This is called to cleanup volumes after live
2565-
# migration, so we should still disconnect even if
2566-
# the instance doesn't exist here anymore.
2567-
error_code = ex.get_error_code()
2568-
if error_code == libvirt.VIR_ERR_NO_DOMAIN:
2569-
# NOTE(vish):
2570-
LOG.warning("During detach_volume, instance disappeared.",
2571-
instance=instance)
2572-
else:
2573-
raise
25742570

25752571
self._disconnect_volume(context, connection_info, instance,
25762572
encryption=encryption)
@@ -2737,83 +2733,19 @@ def detach_interface(self, context, instance, vif):
27372733
instance.image_meta,
27382734
instance.flavor,
27392735
CONF.libvirt.virt_type)
2740-
interface = guest.get_interface_by_cfg(cfg)
27412736
try:
2742-
# NOTE(mriedem): When deleting an instance and using Neutron,
2743-
# we can be racing against Neutron deleting the port and
2744-
# sending the vif-deleted event which then triggers a call to
2745-
# detach the interface, so if the interface is not found then
2746-
# we can just log it as a warning.
2747-
if not interface:
2748-
mac = vif.get('address')
2749-
# The interface is gone so just log it as a warning.
2750-
LOG.warning('Detaching interface %(mac)s failed because '
2751-
'the device is no longer found on the guest.',
2752-
{'mac': mac}, instance=instance)
2753-
return
2754-
27552737
get_dev = functools.partial(guest.get_interface_by_cfg, cfg)
27562738
self._detach_with_retry(
27572739
guest,
27582740
instance.uuid,
27592741
get_dev,
27602742
device_name=self.vif_driver.get_vif_devname(vif),
27612743
)
2762-
except exception.DeviceDetachFailed:
2763-
# We failed to detach the device even with the retry loop, so let's
2764-
# dump some debug information to the logs before raising back up.
2765-
with excutils.save_and_reraise_exception():
2766-
devname = self.vif_driver.get_vif_devname(vif)
2767-
interface = guest.get_interface_by_cfg(cfg)
2768-
if interface:
2769-
LOG.warning(
2770-
'Failed to detach interface %(devname)s after '
2771-
'repeated attempts. Final interface xml:\n'
2772-
'%(interface_xml)s\nFinal guest xml:\n%(guest_xml)s',
2773-
{'devname': devname,
2774-
'interface_xml': interface.to_xml(),
2775-
'guest_xml': guest.get_xml_desc()},
2776-
instance=instance)
27772744
except exception.DeviceNotFound:
27782745
# The interface is gone so just log it as a warning.
27792746
LOG.warning('Detaching interface %(mac)s failed because '
27802747
'the device is no longer found on the guest.',
27812748
{'mac': vif.get('address')}, instance=instance)
2782-
except libvirt.libvirtError as ex:
2783-
error_code = ex.get_error_code()
2784-
if error_code == libvirt.VIR_ERR_NO_DOMAIN:
2785-
LOG.warning("During detach_interface, instance disappeared.",
2786-
instance=instance)
2787-
else:
2788-
# NOTE(mriedem): When deleting an instance and using Neutron,
2789-
# we can be racing against Neutron deleting the port and
2790-
# sending the vif-deleted event which then triggers a call to
2791-
# detach the interface, so we might have failed because the
2792-
# network device no longer exists. Libvirt will fail with
2793-
# "operation failed: no matching network device was found"
2794-
# which unfortunately does not have a unique error code so we
2795-
# need to look up the interface by config and if it's not found
2796-
# then we can just log it as a warning rather than tracing an
2797-
# error.
2798-
mac = vif.get('address')
2799-
# Get a fresh instance of the guest in case it is gone.
2800-
try:
2801-
guest = self._host.get_guest(instance)
2802-
except exception.InstanceNotFound:
2803-
LOG.info("Instance disappeared while detaching interface "
2804-
"%s", vif['id'], instance=instance)
2805-
return
2806-
interface = guest.get_interface_by_cfg(cfg)
2807-
if interface:
2808-
LOG.error('detaching network adapter failed.',
2809-
instance=instance, exc_info=True)
2810-
raise exception.InterfaceDetachFailed(
2811-
instance_uuid=instance.uuid)
2812-
2813-
# The interface is gone so just log it as a warning.
2814-
LOG.warning('Detaching interface %(mac)s failed because '
2815-
'the device is no longer found on the guest.',
2816-
{'mac': mac}, instance=instance)
28172749
finally:
28182750
# NOTE(gibi): we need to unplug the vif _after_ the detach is done
28192751
# on the libvirt side as otherwise libvirt will still manage the

0 commit comments

Comments
 (0)