diff options
| author | Nataliia Solomko <natalirs1985@gmail.com> | 2026-02-27 21:22:15 +0200 |
|---|---|---|
| committer | Nataliia Solomko <natalirs1985@gmail.com> | 2026-03-03 17:52:40 +0200 |
| commit | c90512f68dbabe6f005c259f6e787abe527fe975 (patch) | |
| tree | 86166dcd8b9ddf17611a0b6f2142d1130604e981 | |
| parent | d47be4a8e2119e2be0be5fe9d7c29d7d8eaf24d9 (diff) | |
| download | vyos-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.json | 3 | ||||
| -rw-r--r-- | interface-definitions/vpp.xml.in | 46 | ||||
| -rw-r--r-- | interface-definitions/vpp_interface_bridge.xml.in | 56 | ||||
| -rw-r--r-- | op-mode-definitions/vpp-bridge.xml.in | 2 | ||||
| -rw-r--r-- | python/vyos/ifconfig/vpp/__init__.py | 2 | ||||
| -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.py | 4 | ||||
| -rw-r--r-- | python/vyos/vpp/config_verify.py | 11 | ||||
| -rw-r--r-- | python/vyos/vpp/interface/__init__.py | 2 | ||||
| -rw-r--r-- | smoketest/configs/assert/vpp | 2 | ||||
| -rw-r--r-- | smoketest/configs/vpp | 6 | ||||
| -rwxr-xr-x | smoketest/scripts/cli/test_vpp.py | 60 | ||||
| -rwxr-xr-x | src/conf_mode/vpp.py | 30 | ||||
| -rw-r--r-- | src/conf_mode/vpp_interfaces_bonding.py | 8 | ||||
| -rw-r--r-- | src/conf_mode/vpp_interfaces_bridge.py | 129 | ||||
| -rw-r--r-- | src/conf_mode/vpp_interfaces_gre.py | 14 | ||||
| -rw-r--r-- | src/conf_mode/vpp_interfaces_vxlan.py | 8 | ||||
| -rw-r--r-- | src/migration-scripts/vpp/5-to-6 | 21 | ||||
| -rwxr-xr-x | src/op_mode/vpp.py | 12 |
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) |
