Add managed='no' flag to libvirt XML definition for VIF type TAP

libvirt 9.5.0 and later by default doesn't allow using a pre-created
TAP device; instead it expects to create and manage the TAP device
itself, which is incompatible with how Nova works.  To restore
compatibility with Nova we need to add the managed="no" flag to the
target device section in the XML domain file.

The libvirt change is here[1].  In particular it breaks Calico for
OpenStack, because the Calico plugin (out of tree[2]) uses VIF type TAP.

1. https://github.com/libvirt/libvirt/commit/a2ae3d299cf
2. https://github.com/projectcalico/calico/blob/master/networking-calico/networking_calico/plugins/ml2/drivers/calico/mech_calico.py#L217

Many thanks to Masahito Muroi <masahito.muroi@linecorp.com> for
proposing an earlier version of this fix.

Closes-Bug: #2033681
Change-Id: I4a7b4ecf69cfe04c5291e5ca2a76db8829d6e592
Signed-off-by: Nell Jerram <nell@tigera.io>
This commit is contained in:
Nell Jerram
2025-09-08 11:22:53 +01:00
parent 2952c10948
commit 6aba55a23f
5 changed files with 36 additions and 7 deletions
+4 -2
View File
@@ -2103,6 +2103,7 @@ class LibvirtConfigGuestInterfaceTest(LibvirtConfigBaseTest):
def test_config_ethernet(self):
obj = config.LibvirtConfigGuestInterface()
obj.net_type = "ethernet"
obj.managed = "no"
obj.mac_addr = "DE:AD:BE:EF:CA:FE"
obj.model = "virtio"
obj.target_dev = "vnet0"
@@ -2120,7 +2121,7 @@ class LibvirtConfigGuestInterfaceTest(LibvirtConfigBaseTest):
<mac address="DE:AD:BE:EF:CA:FE"/>
<model type="virtio"/>
<driver name="vhost"/>
<target dev="vnet0"/>
<target dev="vnet0" managed="no"/>
<bandwidth>
<inbound average="16384" peak="32768" burst="3276"/>
<outbound average="32768" peak="65536" burst="6553"/>
@@ -2136,6 +2137,7 @@ class LibvirtConfigGuestInterfaceTest(LibvirtConfigBaseTest):
def test_config_ethernet_with_mtu(self):
obj = config.LibvirtConfigGuestInterface()
obj.net_type = "ethernet"
obj.managed = "no"
obj.mac_addr = "DE:AD:BE:EF:CA:FE"
obj.model = "virtio"
obj.target_dev = "vnet0"
@@ -2155,7 +2157,7 @@ class LibvirtConfigGuestInterfaceTest(LibvirtConfigBaseTest):
<model type="virtio"/>
<driver name="vhost"/>
<mtu size="9000"/>
<target dev="vnet0"/>
<target dev="vnet0" managed="no"/>
<bandwidth>
<inbound average="16384" peak="32768" burst="3276"/>
<outbound average="32768" peak="65536" burst="6553"/>
+8 -4
View File
@@ -540,14 +540,18 @@ class LibvirtVifTestCase(test.NoDBTestCase):
mac = node.find("mac").get("address")
self.assertEqual(mac, vif['address'])
def _assertTypeEquals(self, node, type, attr, source, br_want):
def _assertTypeEquals(self, node, type, attr, source, br_want,
managed_want=None):
self.assertEqual(node.get("type"), type)
br_name = node.find(attr).get(source)
self.assertEqual(br_name, br_want)
if managed_want is not None:
managed = node.find(attr).get("managed")
self.assertEqual(managed, managed_want)
def _assertTypeAndMacEquals(self, node, type, attr, source, vif,
br_want=None):
self._assertTypeEquals(node, type, attr, source, br_want)
br_want=None, managed_want=None):
self._assertTypeEquals(node, type, attr, source, br_want, managed_want)
self._assertMacEquals(node, vif)
def _assertModel(self, xml, model_want=None, driver_want=None):
@@ -1113,7 +1117,7 @@ class LibvirtVifTestCase(test.NoDBTestCase):
xml = self._get_instance_xml(d, self.vif_tap)
node = self._get_node(xml)
self._assertTypeAndMacEquals(node, "ethernet", "target", "dev",
self.vif_tap, br_want)
self.vif_tap, br_want, "no")
@mock.patch('nova.privsep.linux_net.device_exists', return_value=True)
@mock.patch('nova.privsep.linux_net.set_device_mtu')
+7 -1
View File
@@ -1913,6 +1913,7 @@ class LibvirtConfigGuestInterface(LibvirtConfigGuestDevice):
self.device_addr = None
self.mtu = None
self.alias = None
self.managed = None
def __eq__(self, other):
if not isinstance(other, LibvirtConfigGuestInterface):
@@ -2019,7 +2020,11 @@ class LibvirtConfigGuestInterface(LibvirtConfigGuestDevice):
dev.append(vlan_elem)
if self.target_dev is not None:
dev.append(etree.Element("target", dev=self.target_dev))
if self.managed is not None:
dev.append(etree.Element("target", dev=self.target_dev,
managed=self.managed))
else:
dev.append(etree.Element("target", dev=self.target_dev))
if self.vporttype is not None:
vport = etree.Element("virtualport", type=self.vporttype)
@@ -2105,6 +2110,7 @@ class LibvirtConfigGuestInterface(LibvirtConfigGuestDevice):
self.source_dev = c.get('bridge')
elif c.tag == 'target':
self.target_dev = c.get('dev')
self.managed = c.get('managed')
elif c.tag == 'script':
self.script = c.get('path')
elif c.tag == 'vlan':
+1
View File
@@ -435,6 +435,7 @@ class LibvirtGenericVIFDriver(object):
dev = self.get_vif_devname(vif)
designer.set_vif_host_backend_ethernet_config(conf, dev)
conf.managed = "no"
network = vif.get('network')
if network and network.get_meta('mtu'):
@@ -0,0 +1,16 @@
---
fixes:
- |
Nova operation with VIF type TAP was broken by a libvirt change (in 9.5.0)
to treat TAP interface specifications as "managed" by default. This means
that libvirt expects to create the TAP interface itself, which is
incompatible with how Nova works, because Nova creates the TAP interface
before telling libvirt about it. In particular this broke OpenStack with
Calico as the network plugin, because Calico uses VIF type TAP.
This is now fixed, by Nova adding the `managed="no"` attribute to TAP
interface XML specifications.
See `bug 2033681`_ for more details.
.. _bug 2033681: https://bugs.launchpad.net/nova/+bug/2033681