From f40a7421288b32ca1ba3af12c8882b0c324f6f1e Mon Sep 17 00:00:00 2001 From: Bartosz Bezak Date: Wed, 19 Aug 2026 13:02:55 +0200 Subject: [PATCH 1/2] Set MTU on nmstate bridge ports Propagate the bridge MTU to its physical ports when using the nmstate network engine. Closes-Bug: #2164513 Change-Id: I05d3f386157b887d5f550104fef7c5e7ffeed923 Signed-off-by: Bartosz Bezak (cherry picked from commit 4ac2b42dda806375091404fcd1df8e8cc7e7aa26) --- kayobe/plugins/filter/nmstate.py | 2 ++ kayobe/tests/unit/plugins/filter/test_nmstate.py | 3 +++ 2 files changed, 5 insertions(+) diff --git a/kayobe/plugins/filter/nmstate.py b/kayobe/plugins/filter/nmstate.py index 7fae395f1..4559207e5 100644 --- a/kayobe/plugins/filter/nmstate.py +++ b/kayobe/plugins/filter/nmstate.py @@ -519,6 +519,8 @@ def get_iface(name): # _port_type_. for port in br_ports or []: port_iface = get_iface(port) + if mtu: + port_iface.setdefault("mtu", mtu) if "type" not in port_iface: # Check for explicit type configuration port_type = networks.net_attr( diff --git a/kayobe/tests/unit/plugins/filter/test_nmstate.py b/kayobe/tests/unit/plugins/filter/test_nmstate.py index da1fef140..d1869d866 100644 --- a/kayobe/tests/unit/plugins/filter/test_nmstate.py +++ b/kayobe/tests/unit/plugins/filter/test_nmstate.py @@ -44,6 +44,7 @@ class TestNMStateFilter(unittest.TestCase): "net3_interface": "br0", "net3_bridge_ports": ['eth1'], "net3_bridge_stp": True, + "net3_mtu": 9000, # net4: bond on bond0 with slaves eth2 and eth3. "net4_interface": "bond0", "net4_bond_slaves": ['eth2', 'eth3'], @@ -123,6 +124,7 @@ def test_nmstate_config_bridge(self): result = nmstate.nmstate_config(self.context, ["net3"]) br_iface = next(i for i in result["interfaces"] if i["name"] == "br0") self.assertEqual(br_iface["type"], "linux-bridge") + self.assertEqual(br_iface["mtu"], 9000) self.assertEqual(br_iface["bridge"]["port"], [{"name": "eth1"}]) self.assertTrue(br_iface["bridge"]["options"]["stp"]["enabled"]) @@ -130,6 +132,7 @@ def test_nmstate_config_bridge(self): eth1_iface = next(i for i in result["interfaces"] if i["name"] == "eth1") self.assertEqual(eth1_iface["type"], "ethernet") + self.assertEqual(eth1_iface["mtu"], 9000) def test_nmstate_config_bond(self): result = nmstate.nmstate_config(self.context, ["net4"]) From 6d461b8287525ea030e8e4e1f1334c15a3d59bec Mon Sep 17 00:00:00 2001 From: Bartosz Bezak Date: Wed, 19 Aug 2026 13:58:25 +0200 Subject: [PATCH 2/2] Fix nmstate bridge VLAN MTU Do not pass an MTU for VLAN interfaces on bridges when it is the same as the parent bridge MTU. NetworkManager may otherwise set an incorrect MTU after reboot. This applies the workaround from commit 6e2a551f8d342c2603da294cfa12ecb001a56240 to the nmstate network engine. Closes-Bug: #2164520 Related-Bug: #2039947 Change-Id: I9fb4ca153297c20672573e59815313a38d36c297 Signed-off-by: Bartosz Bezak (cherry picked from commit 3c149c0784a2a445aeb4436a881b9ba3a401dd5b) --- kayobe/plugins/filter/nmstate.py | 16 ++++++++ .../tests/unit/plugins/filter/test_nmstate.py | 38 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/kayobe/plugins/filter/nmstate.py b/kayobe/plugins/filter/nmstate.py index 7fae395f1..6feb5a9f3 100644 --- a/kayobe/plugins/filter/nmstate.py +++ b/kayobe/plugins/filter/nmstate.py @@ -586,6 +586,22 @@ def get_iface(name): parent = re.sub( r'\.{}$'.format(vlan_id), '', iface_name) + # NOTE(bbezak): Do not pass MTU for VLAN interfaces on bridges when + # it is identical to the parent bridge, to work around a + # NetworkManager bug. + bridge_mtus = {} + for bridge in networks.net_select_bridges( + context, names, inventory_hostname): + bridge_interface = networks.net_interface( + context, bridge, inventory_hostname) + bridge_mtus[bridge_interface] = networks.net_mtu( + context, bridge, inventory_hostname) + + if parent in bridge_mtus: + parent_mtu = bridge_mtus[parent] + if mtu and mtu == parent_mtu: + del iface["mtu"] + iface["vlan"] = { "base-iface": parent, "id": int(vlan_id) diff --git a/kayobe/tests/unit/plugins/filter/test_nmstate.py b/kayobe/tests/unit/plugins/filter/test_nmstate.py index da1fef140..a5d4614f9 100644 --- a/kayobe/tests/unit/plugins/filter/test_nmstate.py +++ b/kayobe/tests/unit/plugins/filter/test_nmstate.py @@ -434,6 +434,44 @@ def test_vlan_interface_explicit_vlan_and_parent(self): self.assertEqual(vlan_iface["vlan"]["base-iface"], "eth0") self.assertEqual(vlan_iface["vlan"]["id"], 100) + def test_vlan_on_bridge_inherits_matching_mtu(self): + variables = { + "inventory_hostname": "test-host", + "ansible_facts": {"os_family": "RedHat"}, + "vlan_interface": "br0.6", + "vlan_vlan": 6, + "vlan_mtu": 9150, + "bridge_interface": "br0", + "bridge_bridge_ports": ["eth0"], + "bridge_mtu": 9150, + } + context = self._make_context(variables) + result = nmstate.nmstate_config(context, ["vlan", "bridge"]) + + vlan_iface = next( + i for i in result["interfaces"] + if i["name"] == "br0.6") + self.assertNotIn("mtu", vlan_iface) + + def test_vlan_on_bridge_keeps_different_mtu(self): + variables = { + "inventory_hostname": "test-host", + "ansible_facts": {"os_family": "RedHat"}, + "vlan_interface": "br0.6", + "vlan_vlan": 6, + "vlan_mtu": 9000, + "bridge_interface": "br0", + "bridge_bridge_ports": ["eth0"], + "bridge_mtu": 9150, + } + context = self._make_context(variables) + result = nmstate.nmstate_config(context, ["vlan", "bridge"]) + + vlan_iface = next( + i for i in result["interfaces"] + if i["name"] == "br0.6") + self.assertEqual(vlan_iface["mtu"], 9000) + def test_vlan_interface_invalid_name(self): """Test VLAN with invalid interface name is skipped gracefully.""" variables = {