From cd29a84f4c8a8e9e00fc79e1305cec94bc2b99fa Mon Sep 17 00:00:00 2001 From: Nell Jerram Date: Mon, 8 Sep 2025 11:22:53 +0100 Subject: [PATCH] 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 for proposing an earlier version of this fix. Closes-Bug: #2033681 Change-Id: I4a7b4ecf69cfe04c5291e5ca2a76db8829d6e592 Signed-off-by: Nell Jerram (cherry picked from commit 6aba55a23faf47e6650e4cc0ae3007d77a19e3ac) (cherry picked from commit d44afc965685deb1bb3581692375da59446dbe49) (cherry picked from commit b7d13d4dd46bb75fee609763f64d53bf7ac057b5) (cherry picked from commit 6a995e49fea2f4ed1bc24c0a6049588e1eb48463) --- nova/tests/unit/virt/libvirt/test_config.py | 6 ++++-- nova/tests/unit/virt/libvirt/test_vif.py | 12 ++++++++---- nova/virt/libvirt/config.py | 8 +++++++- nova/virt/libvirt/vif.py | 1 + ...pe-tap-with-new-libvirt-791e8b5f170e9c40.yaml | 16 ++++++++++++++++ 5 files changed, 36 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/bug-2033681-fix-vif-type-tap-with-new-libvirt-791e8b5f170e9c40.yaml diff --git a/nova/tests/unit/virt/libvirt/test_config.py b/nova/tests/unit/virt/libvirt/test_config.py index b91c68557123..ad766629bd0c 100644 --- a/nova/tests/unit/virt/libvirt/test_config.py +++ b/nova/tests/unit/virt/libvirt/test_config.py @@ -1964,6 +1964,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" @@ -1981,7 +1982,7 @@ class LibvirtConfigGuestInterfaceTest(LibvirtConfigBaseTest): - + @@ -1997,6 +1998,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" @@ -2016,7 +2018,7 @@ class LibvirtConfigGuestInterfaceTest(LibvirtConfigBaseTest): - + diff --git a/nova/tests/unit/virt/libvirt/test_vif.py b/nova/tests/unit/virt/libvirt/test_vif.py index efd1058ca520..8ea2f3e093e0 100644 --- a/nova/tests/unit/virt/libvirt/test_vif.py +++ b/nova/tests/unit/virt/libvirt/test_vif.py @@ -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') diff --git a/nova/virt/libvirt/config.py b/nova/virt/libvirt/config.py index ff1eb925b2b3..0e2e31c2d14c 100644 --- a/nova/virt/libvirt/config.py +++ b/nova/virt/libvirt/config.py @@ -1867,6 +1867,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): @@ -1973,7 +1974,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) @@ -2059,6 +2064,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': diff --git a/nova/virt/libvirt/vif.py b/nova/virt/libvirt/vif.py index 6e9069fa50ff..1f124c330796 100644 --- a/nova/virt/libvirt/vif.py +++ b/nova/virt/libvirt/vif.py @@ -433,6 +433,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'): diff --git a/releasenotes/notes/bug-2033681-fix-vif-type-tap-with-new-libvirt-791e8b5f170e9c40.yaml b/releasenotes/notes/bug-2033681-fix-vif-type-tap-with-new-libvirt-791e8b5f170e9c40.yaml new file mode 100644 index 000000000000..c254a2ce99ae --- /dev/null +++ b/releasenotes/notes/bug-2033681-fix-vif-type-tap-with-new-libvirt-791e8b5f170e9c40.yaml @@ -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