diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py
index 84b0b88daa58..f6fc85171d09 100644
--- a/nova/tests/unit/virt/libvirt/test_driver.py
+++ b/nova/tests/unit/virt/libvirt/test_driver.py
@@ -16424,7 +16424,19 @@ class LibvirtDriverTestCase(test.NoDBTestCase):
expected_flags):
instance = self._create_instance()
network_info = _fake_network_info(self, 1)
- domain = FakeVirtDomain()
+ domain = FakeVirtDomain(fake_xml="""
+
+
+
+
+
+
+
+
+
+
+ """)
self.mox.StubOutWithMock(host.Host, 'get_domain')
self.mox.StubOutWithMock(self.drvr.firewall_driver,
'setup_basic_filtering')
@@ -16432,30 +16444,51 @@ class LibvirtDriverTestCase(test.NoDBTestCase):
self.mox.StubOutWithMock(domain, 'info')
host.Host.get_domain(instance).AndReturn(domain)
+ domain.info().AndReturn([power_state, 1, 2, 3, 4])
+
if method == 'attach_interface':
self.drvr.firewall_driver.setup_basic_filtering(
instance, [network_info[0]])
-
- fake_image_meta = objects.ImageMeta.from_dict(
- {'id': instance.image_ref})
-
- expected = self.drvr.vif_driver.get_config(
- instance, network_info[0], fake_image_meta, instance.flavor,
- CONF.libvirt.virt_type, self.drvr._host)
-
- self.mox.StubOutWithMock(self.drvr.vif_driver,
- 'get_config')
- self.drvr.vif_driver.get_config(
- instance, network_info[0],
- mox.IsA(objects.ImageMeta),
- mox.IsA(objects.Flavor),
- CONF.libvirt.virt_type,
- self.drvr._host).AndReturn(expected)
- domain.info().AndReturn([power_state, 1, 2, 3, 4])
- if method == 'attach_interface':
+ fake_image_meta = objects.ImageMeta.from_dict(
+ {'id': instance.image_ref})
+ expected = self.drvr.vif_driver.get_config(
+ instance, network_info[0], fake_image_meta, instance.flavor,
+ CONF.libvirt.virt_type, self.drvr._host)
+ self.mox.StubOutWithMock(self.drvr.vif_driver,
+ 'get_config')
+ self.drvr.vif_driver.get_config(
+ instance, network_info[0],
+ mox.IsA(objects.ImageMeta),
+ mox.IsA(objects.Flavor),
+ CONF.libvirt.virt_type,
+ self.drvr._host).AndReturn(expected)
domain.attachDeviceFlags(expected.to_xml(), flags=expected_flags)
elif method == 'detach_interface':
- domain.detachDeviceFlags(expected.to_xml(), expected_flags)
+ expected = vconfig.LibvirtConfigGuestInterface()
+ expected.parse_str("""
+
+
+
+
+
+ """)
+ self.mox.StubOutWithMock(self.drvr.vif_driver,
+ 'get_config')
+ self.drvr.vif_driver.get_config(
+ instance, network_info[0],
+ mox.IsA(objects.ImageMeta),
+ mox.IsA(objects.Flavor),
+ CONF.libvirt.virt_type,
+ self.drvr._host).AndReturn(expected)
+ domain.detachDeviceFlags("""
+
+
+
+
+
+
+ """, expected_flags)
self.mox.ReplayAll()
if method == 'attach_interface':
@@ -16509,21 +16542,81 @@ class LibvirtDriverTestCase(test.NoDBTestCase):
guest = mock.Mock(spec='nova.virt.libvirt.guest.Guest')
guest.get_power_state = mock.Mock()
self.drvr._host.get_guest = mock.Mock(return_value=guest)
- self.drvr.vif_driver = mock.Mock()
error = fakelibvirt.libvirtError(
'no matching network device was found')
error.err = (fakelibvirt.VIR_ERR_OPERATION_FAILED,)
guest.detach_device = mock.Mock(side_effect=error)
- # mock out that get_interface_by_mac doesn't find the interface
- guest.get_interface_by_mac = mock.Mock(return_value=None)
+ # mock out that get_interface_by_cfg doesn't find the interface
+ guest.get_interface_by_cfg = mock.Mock(return_value=None)
self.drvr.detach_interface(self.context, instance, vif)
- guest.get_interface_by_mac.assert_called_once_with(vif['address'])
# an error shouldn't be logged, but a warning should be logged
self.assertFalse(mock_log.error.called)
self.assertEqual(1, mock_log.warning.call_count)
self.assertIn('the device is no longer found on the guest',
six.text_type(mock_log.warning.call_args[0]))
+ def test_detach_interface_device_with_same_mac_address(self):
+ instance = self._create_instance()
+ network_info = _fake_network_info(self, 1)
+ domain = FakeVirtDomain(fake_xml="""
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """)
+ self.mox.StubOutWithMock(host.Host, 'get_domain')
+ self.mox.StubOutWithMock(self.drvr.firewall_driver,
+ 'setup_basic_filtering')
+ self.mox.StubOutWithMock(domain, 'attachDeviceFlags')
+ self.mox.StubOutWithMock(domain, 'info')
+
+ host.Host.get_domain(instance).AndReturn(domain)
+ domain.info().AndReturn([power_state.RUNNING, 1, 2, 3, 4])
+ expected = vconfig.LibvirtConfigGuestInterface()
+ expected.parse_str("""
+
+
+
+
+
+ """)
+ self.mox.StubOutWithMock(self.drvr.vif_driver, 'get_config')
+ self.drvr.vif_driver.get_config(
+ instance, network_info[0],
+ mox.IsA(objects.ImageMeta),
+ mox.IsA(objects.Flavor),
+ CONF.libvirt.virt_type,
+ self.drvr._host).AndReturn(expected)
+ expected_flags = (fakelibvirt.VIR_DOMAIN_AFFECT_CONFIG |
+ fakelibvirt.VIR_DOMAIN_AFFECT_LIVE)
+ domain.detachDeviceFlags("""
+
+
+
+
+
+
+ """, expected_flags)
+ self.mox.ReplayAll()
+ self.drvr.detach_interface(self.context, instance, network_info[0])
+ self.mox.VerifyAll()
+
@mock.patch('nova.virt.libvirt.utils.write_to_file')
# NOTE(mdbooth): The following 4 mocks are required to execute
# get_guest_xml().
diff --git a/nova/tests/unit/virt/libvirt/test_guest.py b/nova/tests/unit/virt/libvirt/test_guest.py
index 4bd694348eea..5e737ea50248 100644
--- a/nova/tests/unit/virt/libvirt/test_guest.py
+++ b/nova/tests/unit/virt/libvirt/test_guest.py
@@ -433,9 +433,18 @@ class GuestTestCase(test.NoDBTestCase):
self.assertEqual(1, len(devs))
self.assertIsInstance(devs[0], vconfig.LibvirtConfigGuestInterface)
+ cfg = vconfig.LibvirtConfigGuestInterface()
+ cfg.parse_str("""
+
+
+
+
+
+
+ """)
self.assertIsNotNone(
- self.guest.get_interface_by_mac('fa:16:3e:f9:af:ae'))
- self.assertIsNone(self.guest.get_interface_by_mac(None))
+ self.guest.get_interface_by_cfg(cfg))
+ self.assertIsNone(self.guest.get_interface_by_cfg(None))
def test_get_info(self):
self.domain.info.return_value = (1, 2, 3, 4, 5)
diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py
index 85f2daf0cdf2..ff5a5469fd31 100644
--- a/nova/virt/libvirt/driver.py
+++ b/nova/virt/libvirt/driver.py
@@ -1370,11 +1370,25 @@ class LibvirtDriver(driver.ComputeDriver):
instance.image_meta,
instance.flavor,
CONF.libvirt.virt_type, self._host)
+ interface = guest.get_interface_by_cfg(cfg)
try:
self.vif_driver.unplug(instance, vif)
+ # NOTE(mriedem): When deleting an instance and using Neutron,
+ # we can be racing against Neutron deleting the port and
+ # sending the vif-deleted event which then triggers a call to
+ # detach the interface, so if the interface is not found then
+ # we can just log it as a warning.
+ if not interface:
+ mac = vif.get('address')
+ # The interface is gone so just log it as a warning.
+ LOG.warning(_LW('Detaching interface %(mac)s failed because '
+ 'the device is no longer found on the guest.'),
+ {'mac': mac}, instance=instance)
+ return
+
state = guest.get_power_state(self._host)
live = state in (power_state.RUNNING, power_state.PAUSED)
- guest.detach_device(cfg, persistent=True, live=live)
+ guest.detach_device(interface, persistent=True, live=live)
except libvirt.libvirtError as ex:
error_code = ex.get_error_code()
if error_code == libvirt.VIR_ERR_NO_DOMAIN:
@@ -1389,11 +1403,11 @@ class LibvirtDriver(driver.ComputeDriver):
# network device no longer exists. Libvirt will fail with
# "operation failed: no matching network device was found"
# which unfortunately does not have a unique error code so we
- # need to look up the interface by MAC and if it's not found
+ # need to look up the interface by config and if it's not found
# then we can just log it as a warning rather than tracing an
# error.
mac = vif.get('address')
- interface = guest.get_interface_by_mac(mac)
+ interface = guest.get_interface_by_cfg(cfg)
if interface:
LOG.error(_LE('detaching network adapter failed.'),
instance=instance, exc_info=True)
diff --git a/nova/virt/libvirt/guest.py b/nova/virt/libvirt/guest.py
index 4b4e0433568e..6c2b9e195444 100644
--- a/nova/virt/libvirt/guest.py
+++ b/nova/virt/libvirt/guest.py
@@ -226,20 +226,29 @@ class Guest(object):
return interfaces
- def get_interface_by_mac(self, mac):
- """Lookup a LibvirtConfigGuestInterface by the MAC address.
+ def get_interface_by_cfg(self, cfg):
+ """Lookup a full LibvirtConfigGuestInterface with
+ LibvirtConfigGuestInterface generated
+ by nova.virt.libvirt.vif.get_config.
- :param mac: MAC address of the guest interface.
- :type mac: str
+ :param cfg: config object that represents the guest interface.
+ :type cfg: LibvirtConfigGuestInterface object
:returns: nova.virt.libvirt.config.LibvirtConfigGuestInterface instance
if found, else None
"""
- if mac:
+ if cfg:
interfaces = self.get_all_devices(
vconfig.LibvirtConfigGuestInterface)
for interface in interfaces:
- if interface.mac_addr == mac:
+ # NOTE(leehom) LibvirtConfigGuestInterface get from domain and
+ # LibvirtConfigGuestInterface generated by
+ # nova.virt.libvirt.vif.get_config must be identical.
+ if (interface.mac_addr == cfg.mac_addr and
+ interface.net_type == cfg.net_type and
+ interface.source_dev == cfg.source_dev and
+ interface.target_dev == cfg.target_dev and
+ interface.vhostuser_path == cfg.vhostuser_path):
return interface
def get_vcpus_info(self):