diff options
| author | Christian Breunig <christian@breunig.cc> | 2026-02-25 22:39:46 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-02-25 22:39:46 +0100 |
| commit | 61d8e945ee356befde46c94cfed859d3e9f37d55 (patch) | |
| tree | 90a995015c35ceb5fc3ded3b21ee699648748dae | |
| parent | c0c2d0bde843a4fd2921ba8b70c15565e8f618d6 (diff) | |
| parent | dec957d8bf09f2a186bc12478eec275a88ef3896 (diff) | |
| download | vyos-1x-61d8e945ee356befde46c94cfed859d3e9f37d55.tar.gz vyos-1x-61d8e945ee356befde46c94cfed859d3e9f37d55.zip | |
Merge pull request #5001 from natali-rs1985/T8283
vpp: T8283: Move bonding interface from vpp section to 'interfaces vpp bonding'
| -rw-r--r-- | interface-definitions/include/version/vpp-version.xml.i | 2 | ||||
| -rw-r--r-- | interface-definitions/vpp.xml.in | 98 | ||||
| -rw-r--r-- | interface-definitions/vpp_interface_bonding.xml.in | 118 | ||||
| -rw-r--r-- | python/vyos/ifconfig/vpp/__init__.py | 24 | ||||
| -rw-r--r-- | python/vyos/ifconfig/vpp/bond.py | 152 | ||||
| -rw-r--r-- | python/vyos/ifconfig/vpp/interface.py | 56 | ||||
| -rw-r--r-- | python/vyos/vpp/interface/__init__.py | 2 | ||||
| -rw-r--r-- | python/vyos/vpp/interface/bond.py | 122 | ||||
| -rw-r--r-- | python/vyos/vpp/utils.py | 4 | ||||
| -rw-r--r-- | smoketest/configs/assert/vpp | 6 | ||||
| -rwxr-xr-x | smoketest/scripts/cli/test_vpp.py | 97 | ||||
| -rwxr-xr-x | src/conf_mode/vpp.py | 31 | ||||
| -rw-r--r-- | src/conf_mode/vpp_interfaces_bonding.py | 137 | ||||
| -rw-r--r-- | src/conf_mode/vpp_interfaces_bridge.py | 10 | ||||
| -rw-r--r-- | src/conf_mode/vpp_ipfix.py | 5 | ||||
| -rw-r--r-- | src/migration-scripts/vpp/8-to-9 | 88 | ||||
| -rwxr-xr-x | src/op_mode/vpp.py | 17 |
17 files changed, 550 insertions, 419 deletions
diff --git a/interface-definitions/include/version/vpp-version.xml.i b/interface-definitions/include/version/vpp-version.xml.i index 59d13f8cf..b354faa08 100644 --- a/interface-definitions/include/version/vpp-version.xml.i +++ b/interface-definitions/include/version/vpp-version.xml.i @@ -1,3 +1,3 @@ <!-- include start from include/version/vpp-version.xml.i --> -<syntaxVersion component='vpp' version='8'></syntaxVersion> +<syntaxVersion component='vpp' version='9'></syntaxVersion> <!-- include end --> diff --git a/interface-definitions/vpp.xml.in b/interface-definitions/vpp.xml.in index 86568c1e9..9f245aaf3 100644 --- a/interface-definitions/vpp.xml.in +++ b/interface-definitions/vpp.xml.in @@ -12,104 +12,6 @@ <priority>300</priority> </properties> <children> - <tagNode name="bonding" owner="${vyos_conf_scripts_dir}/vpp_interfaces_bonding.py"> - <properties> - <priority>324</priority> - <help>Bonding Interface/Link Aggregation</help> - <constraint> - <regex>bond[0-9]+</regex> - </constraint> - <constraintErrorMessage>Bonding interface must be named bondN</constraintErrorMessage> - <valueHelp> - <format>bondN</format> - <description>Bonding interface name</description> - </valueHelp> - </properties> - <children> - #include <include/generic-description.xml.i> - #include <include/interface/disable.xml.i> - <leafNode name="hash-policy"> - <properties> - <help>Bonding transmit hash policy</help> - <completionHelp> - <list>layer2 layer2+3 layer3+4</list> - </completionHelp> - <valueHelp> - <format>layer2</format> - <description>use MAC addresses to generate the hash</description> - </valueHelp> - <valueHelp> - <format>layer2+3</format> - <description>combine MAC address and IP address to make hash</description> - </valueHelp> - <valueHelp> - <format>layer3+4</format> - <description>combine IP address and port to make hash</description> - </valueHelp> - <constraint> - <regex>(layer2\+3|layer3\+4|layer2)</regex> - </constraint> - <constraintErrorMessage>hash-policy must be layer2 layer2+3 layer3+4</constraintErrorMessage> - </properties> - <defaultValue>layer2</defaultValue> - </leafNode> - #include <include/interface/mac.xml.i> - #include <include/kernel-interface-tun.xml.i> - <leafNode name="mode"> - <properties> - <help>Bonding mode</help> - <completionHelp> - <list>802.3ad active-backup broadcast round-robin xor-hash</list> - </completionHelp> - <valueHelp> - <format>802.3ad</format> - <description>IEEE 802.3ad Dynamic link aggregation</description> - </valueHelp> - <valueHelp> - <format>active-backup</format> - <description>Fault tolerant: only one slave in the bond is active</description> - </valueHelp> - <valueHelp> - <format>broadcast</format> - <description>Fault tolerant: transmits everything on all slave interfaces</description> - </valueHelp> - <valueHelp> - <format>round-robin</format> - <description>Load balance: transmit packets in sequential order</description> - </valueHelp> - <valueHelp> - <format>xor-hash</format> - <description>Distribute based on MAC address</description> - </valueHelp> - <constraint> - <regex>(802.3ad|active-backup|broadcast|round-robin|xor-hash)</regex> - </constraint> - <constraintErrorMessage>mode must be 802.3ad, active-backup, broadcast, round-robin, or xor</constraintErrorMessage> - </properties> - <defaultValue>802.3ad</defaultValue> - </leafNode> - <node name="member"> - <properties> - <help>Bonding member interfaces</help> - </properties> - <children> - <leafNode name="interface"> - <properties> - <help>Member interface name</help> - <completionHelp> - <script>${vyos_completion_dir}/list_interfaces --bondable</script> - </completionHelp> - <valueHelp> - <format>txt</format> - <description>Interface name</description> - </valueHelp> - <multi/> - </properties> - </leafNode> - </children> - </node> - </children> - </tagNode> <tagNode name="bridge" owner="${vyos_conf_scripts_dir}/vpp_interfaces_bridge.py"> <properties> <priority>327</priority> diff --git a/interface-definitions/vpp_interface_bonding.xml.in b/interface-definitions/vpp_interface_bonding.xml.in new file mode 100644 index 000000000..9f114d157 --- /dev/null +++ b/interface-definitions/vpp_interface_bonding.xml.in @@ -0,0 +1,118 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name='interfaces'> + <children> + <node name='vpp'> + <properties> + <help>Network interfaces</help> + <priority>300</priority> + </properties> + <children> + <tagNode name="bonding" owner="${vyos_conf_scripts_dir}/vpp_interfaces_bonding.py"> + <properties> + <priority>324</priority> + <help>Bonding Interface/Link Aggregation</help> + <constraint> + <regex>vppbond[0-9]+</regex> + </constraint> + <constraintErrorMessage>Bonding interface must be named vppbondN</constraintErrorMessage> + <valueHelp> + <format>vppbondN</format> + <description>Bonding interface name</description> + </valueHelp> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/interface/disable.xml.i> + <leafNode name="hash-policy"> + <properties> + <help>Bonding transmit hash policy</help> + <completionHelp> + <list>layer2 layer2+3 layer3+4</list> + </completionHelp> + <valueHelp> + <format>layer2</format> + <description>use MAC addresses to generate the hash</description> + </valueHelp> + <valueHelp> + <format>layer2+3</format> + <description>combine MAC address and IP address to make hash</description> + </valueHelp> + <valueHelp> + <format>layer3+4</format> + <description>combine IP address and port to make hash</description> + </valueHelp> + <constraint> + <regex>(layer2\+3|layer3\+4|layer2)</regex> + </constraint> + <constraintErrorMessage>hash-policy must be layer2 layer2+3 layer3+4</constraintErrorMessage> + </properties> + <defaultValue>layer2</defaultValue> + </leafNode> + #include <include/interface/mac.xml.i> + <leafNode name="mode"> + <properties> + <help>Bonding mode</help> + <completionHelp> + <list>802.3ad active-backup broadcast round-robin xor-hash</list> + </completionHelp> + <valueHelp> + <format>802.3ad</format> + <description>IEEE 802.3ad Dynamic link aggregation</description> + </valueHelp> + <valueHelp> + <format>active-backup</format> + <description>Fault tolerant: only one slave in the bond is active</description> + </valueHelp> + <valueHelp> + <format>broadcast</format> + <description>Fault tolerant: transmits everything on all slave interfaces</description> + </valueHelp> + <valueHelp> + <format>round-robin</format> + <description>Load balance: transmit packets in sequential order</description> + </valueHelp> + <valueHelp> + <format>xor-hash</format> + <description>Distribute based on MAC address</description> + </valueHelp> + <constraint> + <regex>(802.3ad|active-backup|broadcast|round-robin|xor-hash)</regex> + </constraint> + <constraintErrorMessage>mode must be 802.3ad, active-backup, broadcast, round-robin, or xor-hash</constraintErrorMessage> + </properties> + <defaultValue>802.3ad</defaultValue> + </leafNode> + <node name="member"> + <properties> + <help>Bonding member interfaces</help> + </properties> + <children> + <leafNode name="interface"> + <properties> + <help>Member interface name</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces --bondable</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface name</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </node> + #include <include/interface/address-ipv4-ipv6.xml.i> + #include <include/interface/mtu-68-16000.xml.i> + #include <include/vpp/vif.xml.i> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/python/vyos/ifconfig/vpp/__init__.py b/python/vyos/ifconfig/vpp/__init__.py new file mode 100644 index 000000000..117acb8d0 --- /dev/null +++ b/python/vyos/ifconfig/vpp/__init__.py @@ -0,0 +1,24 @@ +# +# Copyright (C) VyOS Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from .bond import VPPBondInterface +from .interface import VPPInterface + +__all__ = [ + 'VPPBondInterface', + 'VPPInterface', +] diff --git a/python/vyos/ifconfig/vpp/bond.py b/python/vyos/ifconfig/vpp/bond.py new file mode 100644 index 000000000..75849a620 --- /dev/null +++ b/python/vyos/ifconfig/vpp/bond.py @@ -0,0 +1,152 @@ +# +# Copyright (C) VyOS Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from vyos.ifconfig import Interface +from vyos.ifconfig.vpp.interface import VPPInterface + + +class VPPBondInterface(Interface, VPPInterface): + def __init__(self, ifname, config): + self.ifname = ifname + self.instance = int(ifname.removeprefix('vppbond')) + self.vpp_ifname = f'BondEthernet{self.instance}' + + # Initialize Interface (kernel) and VPP part + super().__init__(ifname) + VPPInterface.__init__(self, self.vpp_ifname) + + self.index = self.vpp.get_sw_if_index(self.vpp_ifname) + self.state = 'up' if 'disable' not in config else 'down' + self.mode = config.get('mode') + self.load_balance = config.get('hash_policy') + self.mac = config.get('mac') + + def _create(self): + pass + + def add_bond(self): + """Create Bond interface + https://github.com/FDio/vpp/blob/stable/2306/src/vnet/bonding/bond.api + Example: + from vyos.ifconfig.vpp import VPPBondInterface + a = BondInterface(ifname='vppbond0', config) + a.add_bond() + """ + # Create interface 'BondEthernetX' + create_args = { + 'id': self.instance, + 'mode': self.mode, + 'lb': self.load_balance, + } + if self.mac: + create_args.update({'use_custom_mac': True, 'mac_address': self.mac}) + self.vpp.api.bond_create2(**create_args) + # Add LCP pair (kernel) interface + self.kernel_add() + # Set interface state + self.set_state(self.state) + self.set_admin_state(self.state) + self.index = self.vpp.get_sw_if_index(self.vpp_ifname) + + def delete_bond(self): + """Delete Bond interface + Example: + from vyos.ifconfig.vpp import VPPBondInterface + a = VPPBondInterface(ifname='vppbond0', config) + a.delete_bond() + """ + self.vpp.api.bond_delete(sw_if_index=self.index) + + def add_member(self, interface): + """Add member to Bond interface + Example: + from vyos.ifconfig.vpp import VPPBondInterface + a = VPPBondInterface(ifname='vppbond0', config) + a.add_member(interface='eth0') + """ + member_if_index = self.vpp.get_sw_if_index(interface) + self.vpp.api.bond_add_member( + bond_sw_if_index=self.index, sw_if_index=member_if_index + ) + self.vpp.api.sw_interface_set_promisc( + sw_if_index=member_if_index, promisc_on=True + ) + + def detach_member(self, interface): + """Detach member from Bond interface + Example: + from vyos.ifconfig.vpp import VPPBondInterface + a = VPPBondInterface(ifname='vppbond0') + a.detach_member(interface='eth0') + """ + member_if_index = self.vpp.get_sw_if_index(interface) + self.vpp.api.bond_detach_member(sw_if_index=member_if_index) + + def get_members(self): + members = [] + tmp = self.vpp.api.sw_member_interface_dump(sw_if_index=self.index) + for member in tmp: + members.append(member.interface_name) + return members + + def kernel_add(self): + """Add LCP pair + Example: + from vyos.ifconfig.vpp import VPPBondInterface + a = VPPBondInterface(ifname='vppbond0') + a.kernel_add() + """ + self.vpp.lcp_pair_add(self.vpp_ifname, self.ifname) + + def kernel_delete(self): + """Delete LCP pair + Example: + from vyos.ifconfig.vpp import VPPBondInterface + a = VPPBondInterface(ifname='vppbond0') + a.kernel_delete() + """ + self.vpp.lcp_pair_del(self.vpp_ifname, self.ifname) + + def remove(self): + if self.index: + # Detach all existing members + members = self.get_members() + for member in members: + self.detach_member(interface=member) + + # Delete lcp pair interface + if self.vpp.lcp_pair_find(vpp_name_hw=self.vpp_ifname): + self.kernel_delete() + + # Delete bonding interface + self.delete_bond() + + def update(self, config): + # Add bond interface + self.add_bond() + + # Add members to bond + for member in config.get('member', {}).get('interface', []): + self.add_member(interface=member) + + # Set rx-mode + rx_mode = config.get('vpp_settings', {}).get('interface_rx_mode') + if rx_mode: + self.set_rx_mode(rx_mode) + + # Apply all settings to the lcp pair (kernel) interface + super().update(config) diff --git a/python/vyos/ifconfig/vpp/interface.py b/python/vyos/ifconfig/vpp/interface.py new file mode 100644 index 000000000..1b87af4d2 --- /dev/null +++ b/python/vyos/ifconfig/vpp/interface.py @@ -0,0 +1,56 @@ +# +# Copyright (C) VyOS Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# 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 VPPInterface: + def __init__(self, vpp_ifname): + self.vpp_ifname = vpp_ifname + self.vpp = VPPControl() + + def set_state(self, state: str): + """Set interface state to UP or DOWN + Args: + state (str): The state of the interface. Options are 'up' and 'down'. + Example: + from vyos.interface.vpp import VPPInterface + a = VPPInterface(vpp_ifname='eth0') + a.set_state(state='up') + """ + if state not in ['up', 'down']: + raise ValueError(f"Invalid state: {state}") + state_flag = 1 if state == 'up' else 0 + if_index = self.vpp.get_sw_if_index(self.vpp_ifname) + self.vpp.api.sw_interface_set_flags(sw_if_index=if_index, flags=state_flag) + + def get_state(self): + """Get interface state + Example: + from vyos.interface.vpp import VPPInterface + a = VPPInterface(vpp_ifname='eth0') + a.get_state() + """ + if_index = self.vpp.get_sw_if_index(self.vpp_ifname) + return self.vpp.api.sw_interface_dump(sw_if_index=if_index)[0]['flags'] + + def set_rx_mode(self, rx_mode): + lcp_pair = self.vpp.lcp_pair_find(vpp_name_hw=self.vpp_ifname) + if lcp_pair: + lcp_name = lcp_pair.get('vpp_name_kernel') + if lcp_name: + self.vpp.iface_rxmode(lcp_name, rx_mode) diff --git a/python/vyos/vpp/interface/__init__.py b/python/vyos/vpp/interface/__init__.py index 903b7cbfc..c54e5ca60 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 .bond import BondInterface from .bridge import BridgeInterface from .ethernet import EthernetInterface from .geneve import GeneveInterface @@ -28,7 +27,6 @@ from .wireguard import WireguardInterface from .xconnect import XconnectInterface __all__ = [ - 'BondInterface', 'BridgeInterface', 'EthernetInterface', 'GeneveInterface', diff --git a/python/vyos/vpp/interface/bond.py b/python/vyos/vpp/interface/bond.py deleted file mode 100644 index abfcf5d0d..000000000 --- a/python/vyos/vpp/interface/bond.py +++ /dev/null @@ -1,122 +0,0 @@ -# -# Copyright (C) VyOS Inc. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# 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.control_host import set_promisc -from vyos.vpp.interface.interface import Interface - - -class BondInterface(Interface): - def __init__( - self, - ifname, - mode: str = '', - load_balance: int = 0, - mac: str = '', - kernel_interface: str = '', - state: str = 'up', - ): - super().__init__(ifname) - self.instance = int(ifname.removeprefix('bond')) - self.ifname = f'BondEthernet{self.instance}' - self.mode = mode - self.load_balance = load_balance - self.mac = mac - self.kernel_interface = kernel_interface - self.state = state - - def add(self): - """Create Bond interface - https://github.com/FDio/vpp/blob/stable/2306/src/vnet/bonding/bond.api - Example: - from vyos.vpp.interface import BondInterface - a = BondInterface(ifname='bond0', mode=5) - a.add() - """ - # Create interface 'bondX' - create_args = { - 'id': self.instance, - 'mode': self.mode, - 'lb': self.load_balance, - } - if self.mac: - create_args.update({'use_custom_mac': True, 'mac_address': self.mac}) - self.vpp.api.bond_create2(**create_args) - if self.kernel_interface: - self.vpp.lcp_pair_add(self.ifname, self.kernel_interface) - # Set interface state - self.set_state(self.state) - - def delete(self): - """Delete Bond interface - Example: - from vyos.vpp.interface import BondInterface - a = BondInterface(ifname='bond0') - a.delete() - """ - bond_if_index = self.vpp.get_sw_if_index(self.ifname) - self.vpp.api.bond_delete(sw_if_index=bond_if_index) - - def add_member(self, interface): - """Add member to Bond interface - Example: - from vyos.vpp.interface import BondInterface - a = BondInterface(ifname='bond0') - a.add_member(interface='eth0') - """ - bond_if_index = self.vpp.get_sw_if_index(f'BondEthernet{self.instance}') - member_if_index = self.vpp.get_sw_if_index(interface) - member_if_type = self.vpp.get_sw_if_dev_type(interface) - self.vpp.api.bond_add_member( - bond_sw_if_index=bond_if_index, sw_if_index=member_if_index - ) - self.vpp.api.sw_interface_set_promisc( - sw_if_index=member_if_index, promisc_on=True - ) - if member_if_type == 'AF_XDP interface': - set_promisc(f'defunct_{interface}', 'on') - - def detach_member(self, interface): - """Detach member from Bond interface - Example: - from vyos.vpp.interface import BondInterface - a = BondInterface(ifname='bond0') - a.detach_member(interface='eth0') - """ - member_if_index = self.vpp.get_sw_if_index(interface) - self.vpp.api.bond_detach_member(sw_if_index=member_if_index) - - def kernel_add(self): - """Add LCP pair - Example: - from vyos.vpp.interface import BondInterface - a = BondInterface(ifname='bond0', mode=5) - a.kernel_add() - """ - self.vpp.lcp_pair_add(self.ifname, self.kernel_interface) - - def kernel_delete(self): - """Delete LCP pair - Example: - from vyos.vpp.interface import BondInterface - a = BondInterface(ifname='bond0', mode=5) - a.kernel_delete() - """ - self.vpp.lcp_pair_del(self.ifname, self.kernel_interface) - - def lcp_pair_exists(self): - """Check if LCP pair exists""" - return bool(self.vpp.lcp_pair_find(self.kernel_interface)) diff --git a/python/vyos/vpp/utils.py b/python/vyos/vpp/utils.py index 0c247e48a..5ae4cd296 100644 --- a/python/vyos/vpp/utils.py +++ b/python/vyos/vpp/utils.py @@ -58,9 +58,9 @@ def vpp_iface_name_transform(iface: str) -> str: str: Interface name formatted as recognized by VPP (e.g., "BondEthernet0"). """ vpp_iface_name = iface - if vpp_iface_name.startswith('bond'): + if vpp_iface_name.startswith('vppbond'): # interface name in VPP is BondEthernetX - vpp_iface_name = vpp_iface_name.replace('bond', 'BondEthernet') + vpp_iface_name = vpp_iface_name.replace('vppbond', 'BondEthernet') return vpp_iface_name diff --git a/smoketest/configs/assert/vpp b/smoketest/configs/assert/vpp index 536d1a656..0859e7a1a 100644 --- a/smoketest/configs/assert/vpp +++ b/smoketest/configs/assert/vpp @@ -19,9 +19,9 @@ set system option kernel memory hugepage-size 2M hugepage-count '1800' set system option time-format '24-hour' set system syslog local facility all level 'info' set system syslog local facility local7 level 'debug' -set vpp interfaces bonding bond0 hash-policy 'layer2+3' -set vpp interfaces bonding bond0 member interface 'eth4' -set vpp interfaces bonding bond0 mode '802.3ad' +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 vpp interfaces gre gre10 description 'Layer-3' set vpp interfaces gre gre10 mode 'point-to-point' set vpp interfaces gre gre10 remote '192.0.2.2' diff --git a/smoketest/scripts/cli/test_vpp.py b/smoketest/scripts/cli/test_vpp.py index 88c146022..db4c811cc 100755 --- a/smoketest/scripts/cli/test_vpp.py +++ b/smoketest/scripts/cli/test_vpp.py @@ -40,6 +40,7 @@ PROCESS_NAME = 'vpp_main' VPP_CONF = '/run/vpp/vpp.conf' base_path = ['vpp'] resource_path = base_path + ['settings', 'resource-allocation'] +interfaces_path = ['interfaces', 'vpp'] interface = 'eth1' @@ -115,6 +116,7 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): finally: # Ensure these cleanup operations always run self.cli_delete(base_path) + self.cli_delete(interfaces_path) self.cli_commit() # delete address for Ethernet interface @@ -667,31 +669,17 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): self.cli_commit() def test_06_vpp_bonding(self): - interface_bond = 'bond23' - interface_kernel = 'vpptun23' + bond_path = interfaces_path + ['bonding'] + interface_bond = 'vppbond23' hash = 'layer3+4' mode = '802.3ad' description = 'Interface-Bonding' vlans = ['123', '456'] vlan_description = 'My-vlan-123' - self.cli_set( - base_path - + [ - 'interfaces', - 'bonding', - interface_bond, - 'member', - 'interface', - interface, - ] - ) - self.cli_set( - base_path + ['interfaces', 'bonding', interface_bond, 'hash-policy', hash] - ) - self.cli_set( - base_path + ['interfaces', 'bonding', interface_bond, 'mode', mode] - ) + self.cli_set(bond_path + [interface_bond, 'member', 'interface', interface]) + self.cli_set(bond_path + [interface_bond, 'hash-policy', hash]) + self.cli_set(bond_path + [interface_bond, 'mode', mode]) # commit changes self.cli_commit() @@ -706,42 +694,21 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): "Interface BondEthernet23 is not in the expected state 'up'.", ) - # set kernel interface - self.cli_set( - base_path - + [ - 'interfaces', - 'bonding', - interface_bond, - 'kernel-interface', - interface_kernel, - ] - ) - self.cli_set( - base_path - + ['kernel-interfaces', interface_kernel, 'description', description] - ) + self.cli_set(bond_path + [interface_bond, 'description', description]) for vlan in vlans: self.cli_set( - base_path - + [ - 'kernel-interfaces', - interface_kernel, - 'vif', - vlan, - 'description', - vlan_description, - ] + bond_path + + [interface_bond, 'vif', vlan, 'description', vlan_description] ) # commit changes self.cli_commit() - self.assertTrue(os.path.isdir(f'/sys/class/net/{interface_kernel}')) - self.assertTrue(os.path.isdir(f'/sys/class/net/{interface_kernel}.{vlan}')) + self.assertTrue(os.path.isdir(f'/sys/class/net/{interface_bond}')) + self.assertTrue(os.path.isdir(f'/sys/class/net/{interface_bond}.{vlan}')) - current_alias = read_file(f'/sys/class/net/{interface_kernel}/ifalias') - vlan_alias = read_file(f'/sys/class/net/{interface_kernel}.{vlan}/ifalias') + current_alias = read_file(f'/sys/class/net/{interface_bond}/ifalias') + vlan_alias = read_file(f'/sys/class/net/{interface_bond}.{vlan}/ifalias') self.assertEqual(current_alias, description) self.assertEqual(vlan_alias, vlan_description) @@ -772,23 +739,12 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): ) # delete vpp kernel-interface vlan - self.cli_delete(base_path + ['kernel-interfaces', interface_kernel, 'vif']) + self.cli_delete(bond_path + [interface_bond, 'vif']) self.cli_commit() - self.assertFalse(os.path.isdir(f'/sys/class/net/{interface_kernel}.{vlan}')) - - # delete vpp kernel-interface - self.cli_delete(base_path + ['kernel-interfaces', interface_kernel]) - self.cli_commit() - - # delete bonding kernel-interface - self.cli_delete( - base_path + ['interfaces', 'bonding', interface_bond, 'kernel-interface'] - ) - self.cli_commit() - self.assertFalse(os.path.isdir(f'/sys/class/net/{interface_kernel}')) + self.assertFalse(os.path.isdir(f'/sys/class/net/{interface_bond}.{vlan}')) # delete bonding interface - self.cli_delete(base_path + ['interfaces', 'bonding']) + self.cli_delete(bond_path) self.cli_commit() # check deleting bonding interface @@ -1278,10 +1234,8 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): def test_14_2_vpp_cgnat_bond_with_vifs(self): base_cgnat = base_path + ['nat', 'cgnat'] - base_kernel = base_path + ['kernel-interfaces'] - base_bond = base_path + ['interfaces', 'bonding'] - iface_kernel = 'vpptun0' - iface_bond = 'bond0' + base_bond = interfaces_path + ['bonding'] + iface_bond = 'vppbond0' vif_1 = '23' vif_2 = '24' iface_out = f'{iface_bond}.{vif_1}' @@ -1289,11 +1243,9 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): address_1 = '100.64.0.23/32' address_2 = '192.0.2.1/32' - self.cli_set(base_bond + [iface_bond, 'kernel-interface', iface_kernel]) self.cli_set(base_bond + [iface_bond, 'member', 'interface', interface]) - - self.cli_set(base_kernel + [iface_kernel, 'vif', vif_1, 'address', address_1]) - self.cli_set(base_kernel + [iface_kernel, 'vif', vif_2, 'address', address_2]) + self.cli_set(base_bond + [iface_bond, 'vif', vif_1, 'address', address_1]) + self.cli_set(base_bond + [iface_bond, 'vif', vif_2, 'address', address_2]) self.cli_set(base_cgnat + ['interface', 'inside', iface_inside]) self.cli_set(base_cgnat + ['interface', 'outside', iface_out]) @@ -1701,13 +1653,12 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): def test_21_2_vpp_ipfix_bond(self): base_ipfix = base_path + ['ipfix'] - base_bond = base_path + ['interfaces', 'bonding'] - iface_bond = 'bond0' + base_bond = interfaces_path + ['bonding'] + iface_bond = 'vppbond0' collector_ip = '127.0.0.2' collector_src = '127.0.0.1' - self.cli_set(base_bond + [iface_bond, 'kernel-interface', 'vpptun0']) - self.cli_set(base_bond + [iface_bond, 'member', 'interface', iface_bond]) + self.cli_set(base_bond + [iface_bond, 'member', 'interface', interface]) self.cli_set( base_ipfix + ['collector', collector_ip, 'source-address', collector_src] diff --git a/src/conf_mode/vpp.py b/src/conf_mode/vpp.py index 90356e6f4..6d2e2866f 100755 --- a/src/conf_mode/vpp.py +++ b/src/conf_mode/vpp.py @@ -279,6 +279,15 @@ def get_config(config=None): iface for iface in pppoe_ifaces if iface.split('.')[0] in tmp ] + interfaces_config = conf.get_config_dict( + ['interfaces', 'vpp'], + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + with_defaults=True, + with_recursive_defaults=True, + ) + if not conf.exists(base): if changed_pppoe_ifaces: set_dependents('pppoe_server', conf) @@ -286,6 +295,8 @@ def get_config(config=None): 'removed_ifaces': removed_ifaces, 'xconn_members': xconn_members, 'persist_config': eth_ifaces_persist, + 'interfaces_vpp': interfaces_config, + 'remove': {}, } config = conf.get_config_dict( @@ -423,6 +434,8 @@ def get_config(config=None): config['removed_ifaces'] = removed_ifaces config['xconn_members'] = xconn_members + config['interfaces_vpp'] = interfaces_config + # Dependencies for dependency, interface_type in dependency_interface_type_map.items(): # if conf.exists(base + ['interfaces', interface_type]): @@ -435,6 +448,13 @@ def get_config(config=None): iface_filter_eth(conf, iface) set_dependents(dependency, conf, iface) + for dependency, interface_type in dependency_interface_type_map.items(): + if conf.exists(['interfaces', 'vpp', interface_type]): + for iface, iface_config in interfaces_config.get( + interface_type, {} + ).items(): + set_dependents(dependency, conf, iface) + config['ipoe_conf'] = conf.get_config_dict( ['service', 'ipoe-server'], key_mangling=('-', '_'), @@ -484,8 +504,13 @@ def verify(config): config, 'IPFIX monitoring', config.get('ipfix', {}).get('interface', {}) ) + if config.get('interfaces_vpp') and 'remove' in config: + raise ConfigError( + 'VPP cannot be removed while VPP interfaces exist. Remove all "interfaces vpp" first!' + ) + # bail out early - looks like removal from running config - if not config or ('removed_ifaces' in config and 'settings' not in config): + if not config or 'remove' in config: return None if 'settings' not in config: @@ -640,7 +665,7 @@ def verify(config): def generate(config): - if not config or ('removed_ifaces' in config and 'settings' not in config): + if not config or 'remove' in config: # Remove old config and return service_conf.unlink(missing_ok=True) return None @@ -692,7 +717,7 @@ def apply(config): modules = ('vfio_iommu_type1', 'vfio_pci', 'vfio_pci_core', 'vfio') # Open persistent config # It is required for operations with interfaces - if not config or ('removed_ifaces' in config and 'settings' not in config): + if not config or 'remove' in config: # Cleanup persistent config with JSONStorage('vpp_conf') as persist_config: persist_config.delete() diff --git a/src/conf_mode/vpp_interfaces_bonding.py b/src/conf_mode/vpp_interfaces_bonding.py index 23d0720bf..89376ea32 100644 --- a/src/conf_mode/vpp_interfaces_bonding.py +++ b/src/conf_mode/vpp_interfaces_bonding.py @@ -16,22 +16,17 @@ # 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 leaf_node_changed +from vyos.configdict import get_interface_dict from vyos.configdep import set_dependents, call_dependents from vyos import ConfigError +from vyos.utils.assertion import assert_mac +from vyos.utils.process import is_systemd_service_active -from vyos.vpp.interface import BondInterface +from vyos.ifconfig.vpp import VPPBondInterface from vyos.vpp.config_deps import deps_xconnect_dict -from vyos.vpp.config_verify import ( - verify_vpp_remove_xconnect_interface, - verify_vpp_remove_kernel_interface, - verify_vpp_change_kernel_interface, - verify_vpp_exists_kernel_interface, -) -from vyos.vpp.utils import cli_ifaces_list, cli_ifaces_lcp_kernel_list +from vyos.vpp.config_verify import verify_vpp_remove_xconnect_interface +from vyos.vpp.utils import cli_ifaces_list def _get_bond_mode(mode_name: str) -> int: @@ -71,65 +66,29 @@ def get_config(config=None) -> dict: else: conf = Config() - base = ['vpp', 'interfaces', 'bonding'] - base_kernel_interfaces = ['vpp', 'kernel-interfaces'] + base = ['interfaces', 'vpp', 'bonding'] - ifname = os.environ['VYOS_TAGNODE_VALUE'] + ifname, config = get_interface_dict(conf, base) - # 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, - ) - - 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], - 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 - - if effective_config: - config.update({'effective': effective_config}) - config['vpp_ifaces'] = cli_ifaces_list(conf, 'candidate') - # Get global 'vpp kernel-interfaces' for verify - config['vpp_kernel_interfaces'] = conf.get_config_dict( - base_kernel_interfaces, - key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True, - ) - - # list of all kernel interfaces `vpp interface xxx kernel-interface xxx` - config['candidate_kernel_interfaces'] = cli_ifaces_lcp_kernel_list(conf) - # convert values to VPP compatible if 'mode' in config: config['mode'] = _get_bond_mode(config['mode']) if 'hash_policy' in config: config['hash_policy'] = _get_bond_lb(config['hash_policy']) - tmp = leaf_node_changed(conf, base + [ifname, 'kernel-interface']) - if tmp: - config['kernel_interface_removed'] = tmp + # Get 'vpp settings' config with default values + config['vpp_settings'] = conf.get_config_dict( + ['vpp', 'settings'], + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + ) # Dependency config['xconn_members'] = deps_xconnect_dict(conf) @@ -137,12 +96,6 @@ def get_config(config=None) -> dict: for xconn_iface in config['xconn_members'][ifname]: set_dependents('vpp_interfaces_xconnect', conf, xconn_iface) - if conf.exists(base + [ifname, 'kernel-interface']): - if effective_config.get('kernel_interface') or __name__ != '__main__': - iface = config.get('kernel_interface') - if conf.exists(['vpp', 'kernel-interfaces', iface]): - set_dependents('vpp_kernel_interface', conf, iface) - # NAT dependency if conf.exists(['vpp', 'nat', 'nat44']): set_dependents('vpp_nat_nat44', conf) @@ -157,8 +110,6 @@ def get_config(config=None) -> dict: if conf.exists(['vpp', 'ipfix']): set_dependents('vpp_ipfix', conf) - config['ifname'] = ifname - return config @@ -166,19 +117,29 @@ def verify(config): if 'remove_vpp' in config: return None - verify_vpp_remove_kernel_interface(config) verify_vpp_remove_xconnect_interface(config) + if 'deleted' in config: + return None + + if not is_systemd_service_active('vpp.service'): + raise ConfigError( + 'Cannot configure VPP bonding interface: vpp.service is not running' + ) + # Member must belong to VPP for iface in config.get('member', {}).get('interface', []): if iface not in config['vpp_ifaces']: raise ConfigError(f'{iface} must be a VPP interface for bonding') - if 'remove' in config: - return None - - verify_vpp_change_kernel_interface(config) - verify_vpp_exists_kernel_interface(config) + if 'mac' in config: + mac = config['mac'] + try: + assert_mac(mac, test_all_zero=False) + except: + raise ConfigError( + f'Cannot use {mac}: it is a multicast MAC address. Please provide a unicast MAC address.' + ) def generate(config): @@ -190,37 +151,13 @@ def apply(config): return None ifname = config.get('ifname') - # remove old members - if 'effective' in config: - members = config['effective'].get('member', {}).get('interface', []) - - kernel_interface = config['effective'].get('kernel_interface', '') - i = BondInterface(ifname, kernel_interface=kernel_interface) - for member in members: - i.detach_member(interface=member) + bond = VPPBondInterface(ifname, config) + bond.remove() - if 'kernel_interface' in config['effective'] and i.lcp_pair_exists(): - i.kernel_delete() - # Delete bonding interface - i.delete() - - if 'remove' in config: - return None + if 'deleted' in config: + return - # Create a new one - mode = config.get('mode') - lb = config.get('hash_policy') - members = config.get('member', {}).get('interface', []) - mac = config.get('mac', '') - kernel_interface = config.get('kernel_interface', '') - state = 'up' if 'disable' not in config else 'down' - - i = BondInterface(ifname, mode, lb, mac, kernel_interface, state) - i.add() - # Add members to bond - if members: - for member in members: - i.add_member(interface=member) + bond.update(config) call_dependents() diff --git a/src/conf_mode/vpp_interfaces_bridge.py b/src/conf_mode/vpp_interfaces_bridge.py index 64722c10c..e86381cc3 100644 --- a/src/conf_mode/vpp_interfaces_bridge.py +++ b/src/conf_mode/vpp_interfaces_bridge.py @@ -96,7 +96,7 @@ def verify(config): # Check if interface exists in vpp before adding to bridge-domain - allowed_prefixes = ('bond', 'gre', 'geneve', 'lo', 'vxlan') + allowed_prefixes = ('vppbond', 'gre', 'geneve', 'lo', 'vxlan') if 'member' in config: bvi_exists = False @@ -141,9 +141,9 @@ def apply(config): if member.startswith('lo'): # interface name in VPP is loopX member = member.replace('lo', 'loop') - elif member.startswith('bond'): + elif member.startswith('vppbond'): # interface name in VPP is BondEthernetX - member = member.replace('bond', 'BondEthernet') + member = member.replace('vppbond', 'BondEthernet') i.detach_member(member=member) # Delete bridge domain @@ -171,9 +171,9 @@ def apply(config): member = member.replace('lo', 'loop') if 'bvi' in member_config: port_type = 1 - elif member.startswith('bond'): + elif member.startswith('vppbond'): # interface name in VPP is BondEthernetX - member = member.replace('bond', 'BondEthernet') + member = member.replace('vppbond', 'BondEthernet') br.add_member(member=member, port_type=port_type) # set default port type 0 (not BVI) diff --git a/src/conf_mode/vpp_ipfix.py b/src/conf_mode/vpp_ipfix.py index a23498c99..b69651ee4 100644 --- a/src/conf_mode/vpp_ipfix.py +++ b/src/conf_mode/vpp_ipfix.py @@ -20,6 +20,7 @@ from vyos.config import Config from vyos.vpp.ipfix import IPFIX from vyos.vpp.utils import cli_ifaces_list from vyos.vpp.utils import vpp_iface_name_transform +from vyos.vpp.control_vpp import VPPControl def get_config(config=None) -> dict: @@ -73,8 +74,10 @@ def verify(config): ) # Verify that all interfaces specified exist in VPP + vpp = VPPControl() for interface in config['interface']: - if interface not in config['vpp_ifaces']: + vpp_iface_name = vpp_iface_name_transform(interface) + if vpp.get_sw_if_index(vpp_iface_name) is None: raise ConfigError( f'{interface} must be a VPP interface for IPFIX monitoring' ) diff --git a/src/migration-scripts/vpp/8-to-9 b/src/migration-scripts/vpp/8-to-9 new file mode 100644 index 000000000..2232692a5 --- /dev/null +++ b/src/migration-scripts/vpp/8-to-9 @@ -0,0 +1,88 @@ +# Copyright VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# Move bonding interface from vpp section to 'interfaces vpp bonding' (T8283) + + +from vyos.configtree import ConfigTree + +base = ['vpp', 'interfaces', 'bonding'] +new_base = ['interfaces', 'vpp', 'bonding'] +kernel_interface_base = ['vpp', 'kernel-interfaces'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + return + + config.set(new_base) + + for ifname in config.list_nodes(base): + bond_kernel_interface_path = base + [ifname, 'kernel-interface'] + kernel_iface = None + if config.exists(bond_kernel_interface_path): + kernel_iface = config.return_value(bond_kernel_interface_path) + config.delete(bond_kernel_interface_path) + new_ifname = f'vpp{ifname}' + iface_base = new_base + [new_ifname] + config.copy(base + [ifname], iface_base) + config.set_tag(new_base) + + if kernel_iface and config.exists(kernel_interface_base + [kernel_iface]): + if config.exists(kernel_interface_base + [kernel_iface, 'vif']): + config.copy(kernel_interface_base + [kernel_iface, 'vif'], iface_base + ['vif']) + if config.exists(kernel_interface_base + [kernel_iface, 'mtu']): + config.copy(kernel_interface_base + [kernel_iface, 'mtu'], iface_base + ['mtu']) + if config.exists(kernel_interface_base + [kernel_iface, 'address']): + config.copy(kernel_interface_base + [kernel_iface, 'address'], iface_base + ['address']) + config.delete(kernel_interface_base + [kernel_iface]) + + for feature in ['nat44', 'cgnat']: + for direction in ['inside', 'outside']: + tmp_path = ['vpp', 'nat', feature, 'interface', direction] + + if not config.exists(tmp_path): + continue + + names = config.return_values(tmp_path) + for name in names: + if name.split('.')[0] == ifname: + new_name = f'vpp{name}' + config.delete_value(tmp_path, name) + config.set(tmp_path, value=new_name, replace=False) + + ipfix_path = ['vpp', 'ipfix', 'interface'] + if config.exists(ipfix_path): + for name in config.list_nodes(ipfix_path): + if name.split('.')[0] == ifname: + new_name = f'vpp{name}' + config.rename(ipfix_path + [name], new_name) + + bridge_path = ['vpp', 'interfaces', 'bridge'] + if config.exists(bridge_path): + for iface in config.list_nodes(bridge_path): + tmp = bridge_path + [iface, 'member', 'interface'] + if config.exists(tmp): + for name in config.list_nodes(tmp): + if name == ifname: + new_name = f'vpp{name}' + config.rename(tmp + [name], new_name) + + config.delete(base) + + if config.exists(kernel_interface_base) and len(config.list_nodes(kernel_interface_base)) == 0: + config.delete(['vpp', 'kernel-interfaces']) + + if len(config.list_nodes(['vpp', 'interfaces'])) == 0: + config.delete(['vpp', 'interfaces']) diff --git a/src/op_mode/vpp.py b/src/op_mode/vpp.py index 5de7b3f74..868f780ae 100755 --- a/src/op_mode/vpp.py +++ b/src/op_mode/vpp.py @@ -31,13 +31,12 @@ def _verify(target: typing.Optional[str]): """Decorator checks if config for VPP feature exists""" from functools import wraps - target = target.split() if target else [] + path = target.split() if target else [] def _verify_target(func): @wraps(func) def _wrapper(*args, **kwargs): config = ConfigTreeQuery() - path = ['vpp'] + target if not config.exists(path): raise vyos.opmode.UnconfiguredSubsystem( f'"{" ".join(path)}" is not configured' @@ -371,17 +370,17 @@ class VPPShow: # ----------------------------- # VyOS IPFIX op-mode entries # ----------------------------- -@_verify('ipfix interface') +@_verify('vpp ipfix interface') def show_ipfix_interfaces(raw: bool): return VPPShow().ipfix_interfaces(raw) -@_verify('ipfix collector') +@_verify('vpp ipfix collector') def show_ipfix_collectors(raw: bool): return VPPShow().ipfix_collectors(raw) -@_verify('ipfix') +@_verify('vpp ipfix') def show_ipfix_table(raw: bool): return VPPShow().ipfix_table(raw) @@ -389,12 +388,12 @@ def show_ipfix_table(raw: bool): # ----------------------------- # VPP LACP information # ----------------------------- -@_verify('interfaces bonding') +@_verify('interfaces vpp bonding') def show_lacp(raw: bool, ifname: typing.Optional[str]): return VPPShow().lacp_info(raw, ifname) -@_verify('interfaces bonding') +@_verify('interfaces vpp bonding') def show_lacp_details(raw: bool, ifname: typing.Optional[str]): return VPPShow().lacp_details(raw, ifname) @@ -402,12 +401,12 @@ def show_lacp_details(raw: bool, ifname: typing.Optional[str]): # ----------------------------- # Bridge op-mode entries # ----------------------------- -@_verify('interfaces bridge') +@_verify('vpp interfaces bridge') def show_bridge(raw: bool, ifname: typing.Optional[str] = None): return VPPShow().bridge_domain(raw, ifname) -@_verify('interfaces bridge') +@_verify('vpp interfaces bridge') def show_bridge_details(raw: bool, ifname: typing.Optional[str] = None): return VPPShow().bridge_domain_details(raw, ifname) |
