Merge "Support detach interface with same MAC from instance" into stable/newton

This commit is contained in:
Jenkins 2017-08-16 01:21:12 +00:00 committed by Gerrit Code Review
commit 0fde49039c
4 changed files with 160 additions and 35 deletions

View File

@ -16543,7 +16543,19 @@ class LibvirtDriverTestCase(test.NoDBTestCase):
expected_flags):
instance = self._create_instance()
network_info = _fake_network_info(self, 1)
domain = FakeVirtDomain()
domain = FakeVirtDomain(fake_xml="""
<domain type='kvm'>
<devices>
<interface type='bridge'>
<mac address='52:54:00:f6:35:8f'/>
<model type='virtio'/>
<source bridge='br0'/>
<target dev='tap12345678'/>
<address type='pci' domain='0x0000' bus='0x00'
slot='0x03' function='0x0'/>
</interface>
</devices>
</domain>""")
self.mox.StubOutWithMock(host.Host, 'get_domain')
self.mox.StubOutWithMock(self.drvr.firewall_driver,
'setup_basic_filtering')
@ -16551,30 +16563,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("""
<interface type='bridge'>
<mac address='52:54:00:f6:35:8f'/>
<model type='virtio'/>
<source bridge='br0'/>
<target dev='tap12345678'/>
</interface>""")
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("""
<interface type='bridge'>
<mac address='52:54:00:f6:35:8f'/>
<model type='virtio'/>
<source bridge='br0'/>
<target dev='tap12345678'/>
<address type='pci' domain='0x0000' bus='0x00'
slot='0x03' function='0x0'/>
</interface>""", expected_flags)
self.mox.ReplayAll()
if method == 'attach_interface':
@ -16628,21 +16661,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(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="""
<domain type='kvm'>
<devices>
<interface type='bridge'>
<mac address='52:54:00:f6:35:8f'/>
<model type='virtio'/>
<source bridge='br0'/>
<target dev='tap12345678'/>
<address type='pci' domain='0x0000' bus='0x00'
slot='0x03' function='0x0'/>
</interface>
<interface type='bridge'>
<mac address='52:54:00:f6:35:8f'/>
<model type='virtio'/>
<source bridge='br1'/>
<target dev='tap87654321'/>
<address type='pci' domain='0x0000' bus='0x00'
slot='0x03' function='0x1'/>
</interface>
</devices>
</domain>""")
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("""
<interface type='bridge'>
<mac address='52:54:00:f6:35:8f'/>
<model type='virtio'/>
<source bridge='br0'/>
<target dev='tap12345678'/>
</interface>""")
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("""
<interface type='bridge'>
<mac address='52:54:00:f6:35:8f'/>
<model type='virtio'/>
<source bridge='br0'/>
<target dev='tap12345678'/>
<address type='pci' domain='0x0000' bus='0x00'
slot='0x03' function='0x0'/>
</interface>""", expected_flags)
self.mox.ReplayAll()
self.drvr.detach_interface(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().

View File

@ -464,9 +464,18 @@ class GuestTestCase(test.NoDBTestCase):
self.assertEqual(1, len(devs))
self.assertIsInstance(devs[0], vconfig.LibvirtConfigGuestInterface)
cfg = vconfig.LibvirtConfigGuestInterface()
cfg.parse_str("""
<interface type="bridge">
<mac address="fa:16:3e:f9:af:ae"/>
<model type="virtio"/>
<driver name="qemu"/>
<source bridge="qbr84008d03-11"/>
<target dev="tap84008d03-11"/>
</interface>""")
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)

View File

@ -1342,11 +1342,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:
@ -1361,11 +1375,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)

View File

@ -228,20 +228,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):