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):