summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNataliia Solomko <natalirs1985@gmail.com>2026-02-27 21:22:15 +0200
committerNataliia Solomko <natalirs1985@gmail.com>2026-03-03 17:52:40 +0200
commitc90512f68dbabe6f005c259f6e787abe527fe975 (patch)
tree86166dcd8b9ddf17611a0b6f2142d1130604e981
parentd47be4a8e2119e2be0be5fe9d7c29d7d8eaf24d9 (diff)
downloadvyos-1x-c90512f68dbabe6f005c259f6e787abe527fe975.tar.gz
vyos-1x-c90512f68dbabe6f005c259f6e787abe527fe975.zip
vpp: T8327: Migrate bridge interface to 'interfaces vpp bridge'
-rw-r--r--data/config-mode-dependencies/vyos-vpp.json3
-rw-r--r--interface-definitions/vpp.xml.in46
-rw-r--r--interface-definitions/vpp_interface_bridge.xml.in56
-rw-r--r--op-mode-definitions/vpp-bridge.xml.in2
-rw-r--r--python/vyos/ifconfig/vpp/__init__.py2
-rw-r--r--python/vyos/ifconfig/vpp/bridge.py (renamed from python/vyos/vpp/interface/bridge.py)122
-rw-r--r--python/vyos/vpp/config_deps.py4
-rw-r--r--python/vyos/vpp/config_verify.py11
-rw-r--r--python/vyos/vpp/interface/__init__.py2
-rw-r--r--smoketest/configs/assert/vpp2
-rw-r--r--smoketest/configs/vpp6
-rwxr-xr-xsmoketest/scripts/cli/test_vpp.py60
-rwxr-xr-xsrc/conf_mode/vpp.py30
-rw-r--r--src/conf_mode/vpp_interfaces_bonding.py8
-rw-r--r--src/conf_mode/vpp_interfaces_bridge.py129
-rw-r--r--src/conf_mode/vpp_interfaces_gre.py14
-rw-r--r--src/conf_mode/vpp_interfaces_vxlan.py8
-rw-r--r--src/migration-scripts/vpp/5-to-621
-rwxr-xr-xsrc/op_mode/vpp.py12
19 files changed, 258 insertions, 280 deletions
diff --git a/data/config-mode-dependencies/vyos-vpp.json b/data/config-mode-dependencies/vyos-vpp.json
index 490cec717..12dd1efad 100644
--- a/data/config-mode-dependencies/vyos-vpp.json
+++ b/data/config-mode-dependencies/vyos-vpp.json
@@ -20,6 +20,7 @@
},
"vpp_interfaces_bonding": {
"vpp_interfaces_xconnect": ["vpp_interfaces_xconnect"],
+ "vpp_interfaces_bridge": ["vpp_interfaces_bridge"],
"vpp_acl": ["vpp_acl"],
"vpp_nat_nat44": ["vpp_nat_nat44"],
"vpp_nat_cgnat": ["vpp_nat_cgnat"],
@@ -42,6 +43,7 @@
},
"vpp_interfaces_gre": {
"vpp_interfaces_xconnect": ["vpp_interfaces_xconnect"],
+ "vpp_interfaces_bridge": ["vpp_interfaces_bridge"],
"vpp_acl": ["vpp_acl"],
"vpp_nat_nat44": ["vpp_nat_nat44"],
"vpp_nat_cgnat": ["vpp_nat_cgnat"],
@@ -55,6 +57,7 @@
"vpp_kernel_interface": ["vpp_kernel-interfaces"]
},
"vpp_interfaces_loopback": {
+ "vpp_interfaces_bridge": ["vpp_interfaces_bridge"],
"vpp_acl": ["vpp_acl"],
"vpp_nat_nat44": ["vpp_nat_nat44"],
"vpp_nat_cgnat": ["vpp_nat_cgnat"],
diff --git a/interface-definitions/vpp.xml.in b/interface-definitions/vpp.xml.in
index 517129e78..141c40275 100644
--- a/interface-definitions/vpp.xml.in
+++ b/interface-definitions/vpp.xml.in
@@ -12,52 +12,6 @@
<priority>300</priority>
</properties>
<children>
- <tagNode name="bridge" owner="${vyos_conf_scripts_dir}/vpp_interfaces_bridge.py">
- <properties>
- <priority>327</priority>
- <help>Bridge domain</help>
- <constraint>
- <!-- Bridge domain 0 is reserved to vpp default -->
- <regex>br(?!0$)[0-9]+</regex>
- </constraint>
- <constraintErrorMessage>Bridge domain must be named brN and br0 is reserved</constraintErrorMessage>
- <valueHelp>
- <format>brN</format>
- <description>Bridge interface name</description>
- </valueHelp>
- </properties>
- <children>
- #include <include/generic-description.xml.i>
- #include <include/interface/disable.xml.i>
- <node name="member">
- <properties>
- <help>Bridge member interfaces</help>
- </properties>
- <children>
- <tagNode name="interface">
- <properties>
- <help>Member interface name</help>
- <completionHelp>
- <script>${vyos_completion_dir}/list_interfaces --bridgeable</script>
- </completionHelp>
- <valueHelp>
- <format>txt</format>
- <description>Interface name</description>
- </valueHelp>
- </properties>
- <children>
- <leafNode name="bvi">
- <properties>
- <help>Bridge Virtual Interface (BVI)</help>
- <valueless/>
- </properties>
- </leafNode>
- </children>
- </tagNode>
- </children>
- </node>
- </children>
- </tagNode>
<!-- Disable as geneve interface index in the VPP always 0 and not configured -->
<!-- <tagNode name="geneve" owner="${vyos_conf_scripts_dir}/vpp_interfaces_geneve.py">
<properties>
diff --git a/interface-definitions/vpp_interface_bridge.xml.in b/interface-definitions/vpp_interface_bridge.xml.in
new file mode 100644
index 000000000..a918ec99b
--- /dev/null
+++ b/interface-definitions/vpp_interface_bridge.xml.in
@@ -0,0 +1,56 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name='interfaces'>
+ <children>
+ <node name='vpp'>
+ <children>
+ <tagNode name="bridge" owner="${vyos_conf_scripts_dir}/vpp_interfaces_bridge.py">
+ <properties>
+ <priority>327</priority>
+ <help>Bridge domain</help>
+ <constraint>
+ <!-- Bridge domain 0 is reserved to vpp default -->
+ <regex>vppbr(?!0$)[0-9]+</regex>
+ </constraint>
+ <constraintErrorMessage>Bridge domain must be named vppbrN and vppbr0 is reserved</constraintErrorMessage>
+ <valueHelp>
+ <format>vppbrN</format>
+ <description>Bridge interface name</description>
+ </valueHelp>
+ </properties>
+ <children>
+ #include <include/generic-description.xml.i>
+ <node name="member">
+ <properties>
+ <help>Bridge member interfaces</help>
+ </properties>
+ <children>
+ <tagNode name="interface">
+ <properties>
+ <help>Member interface name</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces --bridgeable</script>
+ </completionHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>Interface name</description>
+ </valueHelp>
+ </properties>
+ <children>
+ <leafNode name="bvi">
+ <properties>
+ <help>Bridge Virtual Interface (BVI)</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/vpp-bridge.xml.in b/op-mode-definitions/vpp-bridge.xml.in
index d75fb17b4..70aaa0418 100644
--- a/op-mode-definitions/vpp-bridge.xml.in
+++ b/op-mode-definitions/vpp-bridge.xml.in
@@ -14,7 +14,7 @@
<properties>
<help>Show bridge information for specified interface</help>
<completionHelp>
- <path>vpp interfaces bridge</path>
+ <path>interfaces vpp bridge</path>
</completionHelp>
</properties>
<command>${vyos_op_scripts_dir}/vpp.py show_bridge --ifname=$4</command>
diff --git a/python/vyos/ifconfig/vpp/__init__.py b/python/vyos/ifconfig/vpp/__init__.py
index 7abe402c6..38f72e42f 100644
--- a/python/vyos/ifconfig/vpp/__init__.py
+++ b/python/vyos/ifconfig/vpp/__init__.py
@@ -16,6 +16,7 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from .bond import VPPBondInterface
+from .bridge import VPPBridgeInterface
from .gre import VPPGREInterface
from .interface import VPPInterface
from .ipip import VPPIPIPInterface
@@ -24,6 +25,7 @@ from .vxlan import VPPVXLANInterface
__all__ = [
'VPPBondInterface',
+ 'VPPBridgeInterface',
'VPPGREInterface',
'VPPInterface',
'VPPIPIPInterface',
diff --git a/python/vyos/vpp/interface/bridge.py b/python/vyos/ifconfig/vpp/bridge.py
index 1d1a6e1b4..fce0dbfbb 100644
--- a/python/vyos/vpp/interface/bridge.py
+++ b/python/vyos/ifconfig/vpp/bridge.py
@@ -15,27 +15,16 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-from vyos.vpp import VPPControl
-
-
-class BridgeInterface:
- def __init__(
- self,
- ifname: str,
- flood: bool = True,
- forward: bool = True,
- learn: bool = True,
- uu_flood: bool = True,
- arp_term: bool = False,
- ):
- self.ifname = ifname
- self.interface_suffix = int(self.ifname.replace('br', ''))
- self.flood = flood
- self.forward = forward
- self.learn = learn
- self.uu_flood = uu_flood
- self.arp_term = arp_term
- self.vpp = VPPControl()
+from vyos.ifconfig.vpp.interface import VPPInterface
+from vyos.vpp.utils import iftunnel_transform
+
+
+class VPPBridgeInterface(VPPInterface):
+ def __init__(self, ifname):
+ self.instance = int(ifname.removeprefix('vppbr'))
+ self.vpp_ifname = f'br{self.instance}'
+
+ super().__init__(self.vpp_ifname)
def add(self):
"""Create Bridge interface
@@ -44,18 +33,18 @@ class BridgeInterface:
Bridge-domain 0 is reserved for the default bridge-domain.
Example:
- from vyos.vpp.interface import BridgeInterface
- a = BridgeInterface(ifname='br23')
+ from vyos.ifconfig.vpp import VPPBridgeInterface
+ a = VPPBridgeInterface(ifname='br23')
a.add()
"""
self.vpp.api.bridge_domain_add_del_v2(
is_add=True,
- bd_id=self.interface_suffix,
- flood=self.flood,
- forward=self.forward,
- learn=self.learn,
- uu_flood=self.uu_flood,
- arp_term=self.arp_term,
+ bd_id=self.instance,
+ flood=True,
+ forward=True,
+ learn=True,
+ uu_flood=True,
+ arp_term=False,
)
def delete(self):
@@ -64,11 +53,18 @@ class BridgeInterface:
Bridge-members must be detached before deleting the bridge interface.
Example:
- from vyos.vpp.interface import BridgeInterface
- a = BridgeInterface(ifname='br23')
+ from vyos.ifconfig.vpp import VPPBridgeInterface
+ a = VPPBridgeInterface(ifname='br23')
a.delete()
"""
- self.vpp.api.bridge_domain_add_del_v2(is_add=False, bd_id=self.interface_suffix)
+ self.vpp.api.bridge_domain_add_del_v2(is_add=False, bd_id=self.instance)
+
+ def get_members(self):
+ bridge = self.vpp.api.bridge_domain_dump(bd_id=self.instance)[0]
+ members = []
+ for member in bridge.sw_if_details:
+ members.append(member.sw_if_index)
+ return members
def add_member(self, member: str | int, port_type: int = 0):
"""Add member to Bridge interface
@@ -83,24 +79,16 @@ class BridgeInterface:
port_type: 0 - Normal port, 1 - BVI port
Example:
- from vyos.vpp.interface import BridgeInterface
- a = BridgeInterface(ifname='br23')
+ from vyos.ifconfig.vpp import VPPBridgeInterface
+ a = VPPBridgeInterface(ifname='br23')
a.add_member(member='eth0')
"""
- bridge_index = self.interface_suffix
- # If the 'member' is an Integer or digit, assume it's an interface index
- if isinstance(member, int):
- member_if_index = member
- elif member.isdigit():
- member_if_index = int(member)
- else:
- member_if_index = self.vpp.get_sw_if_index(member)
-
+ member_if_index = self.vpp.get_sw_if_index(member)
return self.vpp.api.sw_interface_set_l2_bridge(
- rx_sw_if_index=member_if_index, bd_id=bridge_index, port_type=port_type
+ rx_sw_if_index=member_if_index, bd_id=self.instance, port_type=port_type
)
- def detach_member(self, member: str | int):
+ def detach_member(self, member: int):
"""Detach member from Bridge interface.
Bridge-domain 0 is reserved for the default bridge-domain.
The `member` parameter can be either the name (str) or the index (int)
@@ -111,19 +99,41 @@ class BridgeInterface:
to be detached from the bridge.
Example:
- from vyos.vpp.interface import BridgeInterface
- a = BridgeInterface(ifname='br23')
+ from vyos.ifconfig.vpp import VPPBridgeInterface
+ a = VPPBridgeInterface(ifname='br23')
a.detach_member(member='eth0')
"""
- # If the 'member' is an Integer or digit, assume it's an interface index
- if isinstance(member, int):
- member_if_index = member
- elif member.isdigit():
- member_if_index = int(member)
- else:
- member_if_index = self.vpp.get_sw_if_index(member)
-
# enable=0, 0 = Enable L3 mode
return self.vpp.api.sw_interface_set_l2_bridge(
- rx_sw_if_index=member_if_index, bd_id=0, port_type=0, enable=0
+ rx_sw_if_index=member, bd_id=0, port_type=0, enable=0
)
+
+ def remove(self):
+ if self.vpp.api.bridge_domain_dump(bd_id=self.instance):
+ # Detach all existing members
+ members = self.get_members()
+ for member in members:
+ self.detach_member(member=member)
+
+ # Delete bridge interface
+ self.delete()
+
+ def update(self, config):
+ # Add bridge interface
+ self.add()
+
+ # Add members to bridge
+ for member, member_config in (
+ config.get('member', {}).get('interface', []).items()
+ ):
+ member = member.removeprefix('vpp')
+ if member.startswith('vxlan'):
+ member = iftunnel_transform(member)
+ elif member.startswith('lo'):
+ # interface name in VPP is loopX
+ member = member.replace('lo', 'loop')
+ elif member.startswith('bond'):
+ # interface name in VPP is BondEthernetX
+ member = member.replace('bond', 'BondEthernet')
+ port = 1 if 'bvi' in member_config else 0
+ self.add_member(member=member, port_type=port)
diff --git a/python/vyos/vpp/config_deps.py b/python/vyos/vpp/config_deps.py
index dffd865d1..4aab90a4a 100644
--- a/python/vyos/vpp/config_deps.py
+++ b/python/vyos/vpp/config_deps.py
@@ -61,7 +61,7 @@ def deps_bridge_dict(conf) -> dict[str, list[str]]:
"""
bridge_members_dict: dict[str, list[str]] = {}
config = conf.get_config_dict(
- ['vpp', 'interfaces', 'bridge'],
+ ['interfaces', 'vpp', 'bridge'],
key_mangling=('-', '_'),
get_first_key=True,
no_tag_node_value_mangle=True,
@@ -69,7 +69,7 @@ def deps_bridge_dict(conf) -> dict[str, list[str]]:
for bridge_name, bridge_config in config.items():
for member_name in bridge_config.get('member', {}).get('interface', []):
- bridge_ifaces_list = bridge_members_dict.get(bridge_name, [])
+ bridge_ifaces_list = bridge_members_dict.get(member_name, [])
bridge_ifaces_list.append(bridge_name)
bridge_members_dict.update({member_name: bridge_ifaces_list})
diff --git a/python/vyos/vpp/config_verify.py b/python/vyos/vpp/config_verify.py
index 84c32f1e6..dab22cf8f 100644
--- a/python/vyos/vpp/config_verify.py
+++ b/python/vyos/vpp/config_verify.py
@@ -108,6 +108,17 @@ def verify_vpp_remove_xconnect_interface(config: dict):
)
+def verify_vpp_remove_bridge_interface(config: dict):
+ if not 'deleted' in config:
+ return
+ for bridge_member, bridge_iface in config.get('bridge_members').items():
+ if bridge_member == config.get('ifname'):
+ raise ConfigError(
+ f'interface "{bridge_member}" is still in use within "interfaces vpp bridge". '
+ f'Please remove it from "interface vpp bridge {bridge_iface}" before proceeding.'
+ )
+
+
def verify_vpp_tunnel_source_address(config: dict):
from vyos.utils.network import is_intf_addr_assigned
diff --git a/python/vyos/vpp/interface/__init__.py b/python/vyos/vpp/interface/__init__.py
index cdfb0c0b8..0a3176199 100644
--- a/python/vyos/vpp/interface/__init__.py
+++ b/python/vyos/vpp/interface/__init__.py
@@ -15,7 +15,6 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-from .bridge import BridgeInterface
from .ethernet import EthernetInterface
from .geneve import GeneveInterface
from .interface import Interface
@@ -23,7 +22,6 @@ from .wireguard import WireguardInterface
from .xconnect import XconnectInterface
__all__ = [
- 'BridgeInterface',
'EthernetInterface',
'GeneveInterface',
'Interface',
diff --git a/smoketest/configs/assert/vpp b/smoketest/configs/assert/vpp
index 1e89dc324..7d8fcb7a7 100644
--- a/smoketest/configs/assert/vpp
+++ b/smoketest/configs/assert/vpp
@@ -22,6 +22,8 @@ set system syslog local facility local7 level 'debug'
set interfaces vpp bonding vppbond0 hash-policy 'layer2+3'
set interfaces vpp bonding vppbond0 member interface 'eth4'
set interfaces vpp bonding vppbond0 mode '802.3ad'
+set interfaces vpp bridge vppbr1 description 'Bridge1'
+set interfaces vpp bridge vppbr1 member interface eth2
set interfaces vpp gre vppgre10 description 'Layer-3'
set interfaces vpp gre vppgre10 remote '192.0.2.2'
set interfaces vpp gre vppgre10 source-address '192.0.2.1'
diff --git a/smoketest/configs/vpp b/smoketest/configs/vpp
index 46a0dd0f6..57ec4e260 100644
--- a/smoketest/configs/vpp
+++ b/smoketest/configs/vpp
@@ -71,6 +71,12 @@ vpp {
}
mode "802.3ad"
}
+ bridge br1 {
+ description "Bridge1"
+ member {
+ interface "eth2"
+ }
+ }
gre gre10 {
description "Layer-3"
mode "point-to-point"
diff --git a/smoketest/scripts/cli/test_vpp.py b/smoketest/scripts/cli/test_vpp.py
index a16e9af11..937a6844e 100755
--- a/smoketest/scripts/cli/test_vpp.py
+++ b/smoketest/scripts/cli/test_vpp.py
@@ -101,6 +101,7 @@ class TestVPP(VyOSUnitTestSHIM.TestCase):
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
cls.cli_delete(cls, base_path)
+ cls.cli_delete(cls, interfaces_path)
def setUp(self):
# always forward to base class
@@ -574,9 +575,10 @@ class TestVPP(VyOSUnitTestSHIM.TestCase):
self.assertNotIn('BondEthernet23', out)
def test_07_vpp_bridge(self):
+ bridge_path = interfaces_path + ['bridge']
fake_member = 'eth2'
members = [interface]
- interface_bridge = 'br10'
+ interface_bridge = 'vppbr10'
vni = '23'
interface_vxlan = f'vppvxlan{vni}'
source_address = '192.0.2.1'
@@ -585,15 +587,7 @@ class TestVPP(VyOSUnitTestSHIM.TestCase):
self.cli_set(['interfaces', 'ethernet', interface, 'address', '192.0.2.1/24'])
for member in members:
self.cli_set(
- base_path
- + [
- 'interfaces',
- 'bridge',
- interface_bridge,
- 'member',
- 'interface',
- member,
- ]
+ bridge_path + [interface_bridge, 'member', 'interface', member]
)
# commit changes
@@ -617,29 +611,13 @@ class TestVPP(VyOSUnitTestSHIM.TestCase):
# Set non exist member
# expect raise ConfigError
self.cli_set(
- base_path
- + [
- 'interfaces',
- 'bridge',
- interface_bridge,
- 'member',
- 'interface',
- fake_member,
- ]
+ bridge_path + [interface_bridge, 'member', 'interface', fake_member]
)
with self.assertRaises(ConfigSessionError):
self.cli_commit()
self.cli_delete(
- base_path
- + [
- 'interfaces',
- 'bridge',
- interface_bridge,
- 'member',
- 'interface',
- fake_member,
- ]
+ bridge_path + [interface_bridge, 'member', 'interface', fake_member]
)
# Add VXLAN to the bridge
@@ -652,15 +630,7 @@ class TestVPP(VyOSUnitTestSHIM.TestCase):
)
self.cli_set(interfaces_path + ['vxlan', interface_vxlan, 'vni', vni])
self.cli_set(
- base_path
- + [
- 'interfaces',
- 'bridge',
- interface_bridge,
- 'member',
- 'interface',
- interface_vxlan,
- ]
+ bridge_path + [interface_bridge, 'member', 'interface', interface_vxlan]
)
# commit changes
@@ -673,7 +643,7 @@ class TestVPP(VyOSUnitTestSHIM.TestCase):
# Perform assertions based on the normalized output
self.assertIn('BD-ID Index BSN Age(min)', normalized_out)
- self.assertIn('10 1 0 off', normalized_out)
+ self.assertRegex(normalized_out, r'10 1 \d+ off')
self.assertIn('Learning U-Forwrd UU-Flood Flooding', normalized_out)
self.assertIn('on on flood on', normalized_out)
self.assertIn('Interface If-idx ISN', normalized_out)
@@ -696,16 +666,8 @@ class TestVPP(VyOSUnitTestSHIM.TestCase):
# Add Loopback BVI to the bridge
self.cli_set(interfaces_path + ['loopback', f'vpplo{vni}'])
self.cli_set(
- base_path
- + [
- 'interfaces',
- 'bridge',
- interface_bridge,
- 'member',
- 'interface',
- f'vpplo{vni}',
- 'bvi',
- ]
+ bridge_path
+ + [interface_bridge, 'member', 'interface', f'vpplo{vni}', 'bvi']
)
# commit changes
self.cli_commit()
@@ -715,7 +677,7 @@ class TestVPP(VyOSUnitTestSHIM.TestCase):
# Normalize the output for consistent whitespace
normalized_out = re.sub(r'\s+', ' ', out)
- self.assertIn('10 1 0 off', normalized_out)
+ self.assertRegex(normalized_out, r'10 1 \d+ off')
self.assertRegex(out, r'\bloop23\s+\d+\s+\d+\s+\d+\s+\*\s+')
def test_08_vpp_ipip(self):
diff --git a/src/conf_mode/vpp.py b/src/conf_mode/vpp.py
index 92777a16a..396cce548 100755
--- a/src/conf_mode/vpp.py
+++ b/src/conf_mode/vpp.py
@@ -45,6 +45,7 @@ from vyos.utils.process import is_systemd_service_active
from vyos.vpp import VPPControl
from vyos.vpp import control_host
from vyos.vpp import VppNotRunningError
+from vyos.vpp.config_deps import deps_bridge_dict
from vyos.vpp.config_deps import deps_xconnect_dict
from vyos.vpp.config_verify import (
verify_dev_driver,
@@ -254,6 +255,7 @@ def get_config(config=None):
)
xconn_members = deps_xconnect_dict(conf)
+ bridge_members = deps_bridge_dict(conf)
removed_ifaces = []
tmp = node_changed(conf, base_settings + ['interface'])
@@ -294,6 +296,7 @@ def get_config(config=None):
return {
'removed_ifaces': removed_ifaces,
'xconn_members': xconn_members,
+ 'bridge_members': bridge_members,
'persist_config': eth_ifaces_persist,
'interfaces_vpp': interfaces_config,
'remove': {},
@@ -433,6 +436,7 @@ def get_config(config=None):
if removed_ifaces:
config['removed_ifaces'] = removed_ifaces
config['xconn_members'] = xconn_members
+ config['bridge_members'] = bridge_members
config['interfaces_vpp'] = interfaces_config
@@ -611,32 +615,16 @@ def verify(config):
f'RX mode {rx_mode} is not supported for interface {iface}'
)
- # check GRE tunnels as part of the bridge, only tunnel-type teb is allowed
- # set vpp interfaces bridge br1 member interface vppgre1
- # set interfaces vpp gre vppgre1 tunnel-type teb
- if 'interfaces' in config:
- if 'bridge' in config['interfaces']:
- for iface, iface_config in config['interfaces']['bridge'].items():
- if 'member' in iface_config:
- for member in iface_config['member'].get('interface', []):
- if member.startswith('vppgre'):
- if (
- 'gre' in config['interfaces_vpp']
- and config['interfaces_vpp']['gre']
- .get(member, {})
- .get('tunnel_type')
- != 'teb'
- ):
- raise ConfigError(
- f'Only tunnel-type teb is allowed for GRE interfaces in bridge {iface}'
- )
-
- # Check if deleted interfaces are not xconnect memebrs
+ # Check if deleted interfaces are not xconnect members or not bridge members
for iface_config in config.get('removed_ifaces', []):
if iface_config['iface_name'] in config.get('xconn_members', {}):
raise ConfigError(
f'Interface {iface_config["iface_name"]} is an xconnect member and cannot be removed'
)
+ if iface_config['iface_name'] in config.get('bridge_members', {}):
+ raise ConfigError(
+ f'Interface {iface_config["iface_name"]} is a bridge member and cannot be removed'
+ )
verify_routes_count(config['settings'])
diff --git a/src/conf_mode/vpp_interfaces_bonding.py b/src/conf_mode/vpp_interfaces_bonding.py
index 89376ea32..553200832 100644
--- a/src/conf_mode/vpp_interfaces_bonding.py
+++ b/src/conf_mode/vpp_interfaces_bonding.py
@@ -24,7 +24,9 @@ from vyos.utils.assertion import assert_mac
from vyos.utils.process import is_systemd_service_active
from vyos.ifconfig.vpp import VPPBondInterface
+from vyos.vpp.config_deps import deps_bridge_dict
from vyos.vpp.config_deps import deps_xconnect_dict
+from vyos.vpp.config_verify import verify_vpp_remove_bridge_interface
from vyos.vpp.config_verify import verify_vpp_remove_xconnect_interface
from vyos.vpp.utils import cli_ifaces_list
@@ -96,6 +98,11 @@ def get_config(config=None) -> dict:
for xconn_iface in config['xconn_members'][ifname]:
set_dependents('vpp_interfaces_xconnect', conf, xconn_iface)
+ config['bridge_members'] = deps_bridge_dict(conf)
+ if ifname in config['bridge_members']:
+ for bridge_iface in config['bridge_members'][ifname]:
+ set_dependents('vpp_interfaces_bridge', conf, bridge_iface)
+
# NAT dependency
if conf.exists(['vpp', 'nat', 'nat44']):
set_dependents('vpp_nat_nat44', conf)
@@ -118,6 +125,7 @@ def verify(config):
return None
verify_vpp_remove_xconnect_interface(config)
+ verify_vpp_remove_bridge_interface(config)
if 'deleted' in config:
return None
diff --git a/src/conf_mode/vpp_interfaces_bridge.py b/src/conf_mode/vpp_interfaces_bridge.py
index edf3f2dd2..37cec7b94 100644
--- a/src/conf_mode/vpp_interfaces_bridge.py
+++ b/src/conf_mode/vpp_interfaces_bridge.py
@@ -16,13 +16,12 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-import os
-
from vyos.config import Config
-from vyos.configdict import node_changed
+from vyos.configdict import get_interface_dict
from vyos import ConfigError
-from vyos.vpp.interface import BridgeInterface
-from vyos.vpp.utils import iftunnel_transform
+from vyos.utils.process import is_systemd_service_active
+
+from vyos.ifconfig.vpp import VPPBridgeInterface
def get_config(config=None) -> dict:
@@ -38,65 +37,46 @@ def get_config(config=None) -> dict:
else:
conf = Config()
- base = ['vpp', 'interfaces', 'bridge']
- vpp_interfaces = ['vpp', 'settings', 'interface']
-
- ifname = os.environ['VYOS_TAGNODE_VALUE']
+ base = ['interfaces', 'vpp', 'bridge']
- # Get config_dict with default values
- config = conf.get_config_dict(
- base + [ifname],
- key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True,
- with_defaults=True,
- with_recursive_defaults=True,
- )
+ ifname, config = get_interface_dict(conf, base)
- if not conf.exists(['vpp']):
+ if not conf.exists(['vpp']) and not conf.exists(base):
config['remove_vpp'] = True
return config
- # Get effective config as we need full dicitonary per interface delete
- effective_config = conf.get_config_dict(
- base + [ifname],
+ # Get global vpp interfaces for verify
+ config['vpp_interfaces'] = conf.get_config_dict(
+ ['vpp', 'settings', 'interface'],
key_mangling=('-', '_'),
- effective=True,
get_first_key=True,
no_tag_node_value_mangle=True,
)
- if effective_config:
- config.update({'effective': effective_config})
-
- if not conf.exists(base + [ifname]):
- config['remove'] = True
-
- # Get global vpp interfaces for verify
- config['vpp_interfaces'] = conf.get_config_dict(
- vpp_interfaces,
+ # Get all gre interfaces config
+ config['gre_interfaces'] = conf.get_config_dict(
+ ['interfaces', 'vpp', 'gre'],
key_mangling=('-', '_'),
get_first_key=True,
no_tag_node_value_mangle=True,
+ with_defaults=True,
+ with_recursive_defaults=True,
)
- # determine which members have been removed
- interfaces_removed = node_changed(conf, base + [ifname, 'member', 'interface'])
- if interfaces_removed:
- config['members_removed'] = interfaces_removed
-
- config['ifname'] = ifname
-
return config
def verify(config):
- if 'remove' in config or 'remove_vpp' in config:
+ if 'deleted' in config or 'remove_vpp' in config:
return None
- # Check if interface exists in vpp before adding to bridge-domain
+ if not is_systemd_service_active('vpp.service'):
+ raise ConfigError(
+ 'Cannot configure VPP bridge interface: vpp.service is not running'
+ )
- allowed_prefixes = ('vppbond', 'vppgre', 'geneve', 'vpplo', 'vppvxlan')
+ # Check if interface exists in vpp before adding to bridge-domain
+ allowed_prefixes = ('vppbond', 'vppgre', 'vpplo', 'vppvxlan')
if 'member' in config:
bvi_exists = False
@@ -120,6 +100,18 @@ def verify(config):
raise ConfigError("BVI can only be defined on loopback interface")
bvi_exists = True
+ # check GRE tunnels as part of the bridge, only tunnel-type "teb" is allowed
+ # set interfaces vpp bridge vppbr1 member interface vppgre1
+ # set interfaces vpp gre vppgre1 tunnel-type teb
+ if member.startswith('vppgre'):
+ if member in config.get('gre_interfaces'):
+ gre_config = config.get('gre_interfaces').get(member)
+ if gre_config.get('tunnel_type') != 'teb':
+ raise ConfigError(
+ f'GRE interface "{member}" in bridge must have tunnel-type "teb". '
+ f'Current tunnel-type is "{gre_config.get("tunnel_type")}".'
+ )
+
def generate(config):
pass
@@ -130,54 +122,13 @@ def apply(config):
return None
ifname = config.get('ifname')
- # vppvxlan10 in the vpp is vxlan_tunnel10
- interface_transform_filter = ('geneve', 'vppvxlan')
- # update members
- if 'members_removed' in config:
- i = BridgeInterface(ifname)
- for member in config.get('members_removed'):
- if member.startswith(interface_transform_filter):
- member = iftunnel_transform(member)
- if member.startswith('vpplo'):
- # interface name in VPP is loopX
- member = member.replace('vpplo', 'loop')
- elif member.startswith('vppbond'):
- # interface name in VPP is BondEthernetX
- member = member.replace('vppbond', 'BondEthernet')
- i.detach_member(member=member)
-
- # Delete bridge domain
- if 'effective' in config:
- ifname = config.get('ifname')
- i = BridgeInterface(ifname)
- i.delete()
-
- if 'remove' in config:
- return None
+ bridge = VPPBridgeInterface(ifname)
+ bridge.remove()
+
+ if 'deleted' in config:
+ return
- # Add bridge domain
- members = config.get('member', {}).get('interface', '')
- i = BridgeInterface(ifname)
- i.add()
- # Add members to bridge
- if members:
- br = BridgeInterface(ifname)
- port_type = 0
- for member, member_config in members.items():
- if member.startswith(interface_transform_filter):
- member = iftunnel_transform(member)
- if member.startswith('vpplo'):
- # interface name in VPP is loopX
- member = member.replace('vpplo', 'loop')
- if 'bvi' in member_config:
- port_type = 1
- elif member.startswith('vppbond'):
- # interface name in VPP is BondEthernetX
- member = member.replace('vppbond', 'BondEthernet')
-
- br.add_member(member=member, port_type=port_type)
- # set default port type 0 (not BVI)
- port_type = 0
+ bridge.update(config)
return None
diff --git a/src/conf_mode/vpp_interfaces_gre.py b/src/conf_mode/vpp_interfaces_gre.py
index 453845bcf..1258f906c 100644
--- a/src/conf_mode/vpp_interfaces_gre.py
+++ b/src/conf_mode/vpp_interfaces_gre.py
@@ -24,11 +24,11 @@ from vyos.configdep import set_dependents, call_dependents
from vyos.utils.process import is_systemd_service_active
from vyos.ifconfig.vpp import VPPGREInterface
+from vyos.vpp.config_deps import deps_bridge_dict
from vyos.vpp.config_deps import deps_xconnect_dict
-from vyos.vpp.config_verify import (
- verify_vpp_remove_xconnect_interface,
- verify_vpp_tunnel_source_address,
-)
+from vyos.vpp.config_verify import verify_vpp_remove_bridge_interface
+from vyos.vpp.config_verify import verify_vpp_remove_xconnect_interface
+from vyos.vpp.config_verify import verify_vpp_tunnel_source_address
from vyos.vpp.utils import cli_ethernet_with_vifs_ifaces
@@ -62,6 +62,11 @@ def get_config(config=None) -> dict:
for xconn_iface in config['xconn_members'][ifname]:
set_dependents('vpp_interfaces_xconnect', conf, xconn_iface)
+ config['bridge_members'] = deps_bridge_dict(conf)
+ if ifname in config['bridge_members']:
+ for bridge_iface in config['bridge_members'][ifname]:
+ set_dependents('vpp_interfaces_bridge', conf, bridge_iface)
+
# Get 'vpp settings' config
config['vpp_settings'] = conf.get_config_dict(
['vpp', 'settings'],
@@ -99,6 +104,7 @@ def verify(config):
return None
verify_vpp_remove_xconnect_interface(config)
+ verify_vpp_remove_bridge_interface(config)
# config removed
if 'deleted' in config:
diff --git a/src/conf_mode/vpp_interfaces_vxlan.py b/src/conf_mode/vpp_interfaces_vxlan.py
index 9c24b6a01..38349cb4c 100644
--- a/src/conf_mode/vpp_interfaces_vxlan.py
+++ b/src/conf_mode/vpp_interfaces_vxlan.py
@@ -26,10 +26,9 @@ from vyos.utils.process import is_systemd_service_active
from vyos.ifconfig.vpp import VPPVXLANInterface
from vyos.vpp.config_deps import deps_bridge_dict
from vyos.vpp.config_deps import deps_xconnect_dict
-from vyos.vpp.config_verify import (
- verify_vpp_remove_xconnect_interface,
- verify_vpp_tunnel_source_address,
-)
+from vyos.vpp.config_verify import verify_vpp_remove_bridge_interface
+from vyos.vpp.config_verify import verify_vpp_remove_xconnect_interface
+from vyos.vpp.config_verify import verify_vpp_tunnel_source_address
from vyos.vpp.utils import cli_ethernet_with_vifs_ifaces
@@ -96,6 +95,7 @@ def verify(config):
return None
verify_vpp_remove_xconnect_interface(config)
+ verify_vpp_remove_bridge_interface(config)
if 'deleted' in config:
return None
diff --git a/src/migration-scripts/vpp/5-to-6 b/src/migration-scripts/vpp/5-to-6
index 03c20d970..0131dee33 100644
--- a/src/migration-scripts/vpp/5-to-6
+++ b/src/migration-scripts/vpp/5-to-6
@@ -47,6 +47,8 @@
#
# Migrate gre interface to 'interfaces vpp gre', remove interfaces with mode "point-to-multipoint" (T8325)
#
+# Migrate bridge interface from vpp section to 'interfaces vpp bridge' (T8327)
+#
from vyos.configtree import ConfigTree
@@ -496,6 +498,24 @@ def _migrate_vpp_gre_interface(config: ConfigTree) -> None:
config.delete(['vpp', 'interfaces'])
+def _migrate_vpp_bridge_interface(config: ConfigTree) -> None:
+ base = ['vpp', 'interfaces', 'bridge']
+ new_base = ['interfaces', 'vpp', 'bridge']
+
+ if not config.exists(base):
+ return
+
+ config.set(new_base)
+
+ for ifname in config.list_nodes(base):
+ _migrate_interface(config, 'bridge', ifname)
+
+ config.delete(base)
+
+ if len(config.list_nodes(['vpp', 'interfaces'])) == 0:
+ config.delete(['vpp', 'interfaces'])
+
+
def migrate(config: ConfigTree) -> None:
if not config.exists(['vpp']):
# Nothing to do
@@ -516,3 +536,4 @@ def migrate(config: ConfigTree) -> None:
_migrate_vpp_ipip_interface(config)
_migrate_vpp_loopback_interface(config)
_migrate_vpp_gre_interface(config)
+ _migrate_vpp_bridge_interface(config)
diff --git a/src/op_mode/vpp.py b/src/op_mode/vpp.py
index 868f780ae..54b46cf68 100755
--- a/src/op_mode/vpp.py
+++ b/src/op_mode/vpp.py
@@ -277,17 +277,17 @@ class VPPShow:
if ifname is None:
return None
- if not ifname.startswith('br') and not ifname[2:].isdigit():
+ if not ifname.startswith('vppbr') and not ifname[5:].isdigit():
raise vyos.opmode.IncorrectValue(
- f'"{ifname}" is not a valid bridge interface name (expected brN)'
+ f'"{ifname}" is not a valid bridge interface name (expected vppbrN)'
)
- if not self.config.exists(['vpp', 'interfaces', 'bridge', ifname]):
+ if not self.config.exists(['interfaces', 'vpp', 'bridge', ifname]):
raise vyos.opmode.IncorrectValue(
f'Bridge interface {ifname} does not exist'
)
- return int(ifname[2:])
+ return int(ifname[5:])
def _get_bridge_domain_raw(
self, bd_id: typing.Optional[int] = None
@@ -401,12 +401,12 @@ def show_lacp_details(raw: bool, ifname: typing.Optional[str]):
# -----------------------------
# Bridge op-mode entries
# -----------------------------
-@_verify('vpp interfaces bridge')
+@_verify('interfaces vpp bridge')
def show_bridge(raw: bool, ifname: typing.Optional[str] = None):
return VPPShow().bridge_domain(raw, ifname)
-@_verify('vpp interfaces bridge')
+@_verify('interfaces vpp bridge')
def show_bridge_details(raw: bool, ifname: typing.Optional[str] = None):
return VPPShow().bridge_domain_details(raw, ifname)