diff options
| author | Viacheslav Hletenko <v.gletenko@vyos.io> | 2026-03-02 17:21:21 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-03-02 17:21:21 +0200 |
| commit | 00361b88005948802344adbe6184057cee91cde4 (patch) | |
| tree | 76124d6c8502666c6fe91c2433ee938a2ef09feb | |
| parent | ef710a5acbd7af9f60ba63716912b0c9a20f5329 (diff) | |
| parent | 61d607295fa826e707b82432c46668a2fb94ab22 (diff) | |
| download | vyos-1x-00361b88005948802344adbe6184057cee91cde4.tar.gz vyos-1x-00361b88005948802344adbe6184057cee91cde4.zip | |
Merge pull request #5019 from natali-rs1985/T8324
vpp: T8324: Migrate loopback interface to 'interfaces vpp loopback'
| -rw-r--r-- | interface-definitions/vpp.xml.in | 19 | ||||
| -rw-r--r-- | interface-definitions/vpp_interface_loopback.xml.in | 32 | ||||
| -rw-r--r-- | python/vyos/ifconfig/vpp/__init__.py | 2 | ||||
| -rw-r--r-- | python/vyos/ifconfig/vpp/loopback.py | 105 | ||||
| -rw-r--r-- | python/vyos/vpp/interface/__init__.py | 2 | ||||
| -rw-r--r-- | python/vyos/vpp/interface/loopback.py | 72 | ||||
| -rw-r--r-- | smoketest/configs/assert/vpp | 2 | ||||
| -rwxr-xr-x | smoketest/scripts/cli/test_vpp.py | 78 | ||||
| -rw-r--r-- | src/conf_mode/vpp_interfaces_bridge.py | 12 | ||||
| -rw-r--r-- | src/conf_mode/vpp_interfaces_loopback.py | 98 | ||||
| -rw-r--r-- | src/migration-scripts/vpp/5-to-6 | 27 |
11 files changed, 202 insertions, 247 deletions
diff --git a/interface-definitions/vpp.xml.in b/interface-definitions/vpp.xml.in index ca9838af0..733e8eb83 100644 --- a/interface-definitions/vpp.xml.in +++ b/interface-definitions/vpp.xml.in @@ -146,25 +146,6 @@ #include <include/kernel-interface-tun.xml.i> </children> </tagNode> - <tagNode name="loopback" owner="${vyos_conf_scripts_dir}/vpp_interfaces_loopback.py"> - <properties> - <priority>325</priority> - <help>Loopback Interface</help> - <constraint> - <regex>lo[0-9]+</regex> - </constraint> - <constraintErrorMessage>Loopback interface must be named loN</constraintErrorMessage> - <valueHelp> - <format>loN</format> - <description>Loopback interface name</description> - </valueHelp> - </properties> - <children> - #include <include/generic-description.xml.i> - #include <include/interface/disable.xml.i> - #include <include/kernel-interface-tun.xml.i> - </children> - </tagNode> <tagNode name="xconnect" owner="${vyos_conf_scripts_dir}/vpp_interfaces_xconnect.py"> <properties> <help>Layer 2 cross connect</help> diff --git a/interface-definitions/vpp_interface_loopback.xml.in b/interface-definitions/vpp_interface_loopback.xml.in new file mode 100644 index 000000000..9998c34d2 --- /dev/null +++ b/interface-definitions/vpp_interface_loopback.xml.in @@ -0,0 +1,32 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name='interfaces'> + <children> + <node name='vpp'> + <children> + <tagNode name="loopback" owner="${vyos_conf_scripts_dir}/vpp_interfaces_loopback.py"> + <properties> + <priority>325</priority> + <help>Loopback Interface</help> + <constraint> + <regex>vpplo[0-9]+</regex> + </constraint> + <constraintErrorMessage>Loopback interface must be named vpploN</constraintErrorMessage> + <valueHelp> + <format>vpploN</format> + <description>Loopback interface name</description> + </valueHelp> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/interface/disable.xml.i> + #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 index a7cfe79a4..b0a477c28 100644 --- a/python/vyos/ifconfig/vpp/__init__.py +++ b/python/vyos/ifconfig/vpp/__init__.py @@ -18,11 +18,13 @@ from .bond import VPPBondInterface from .interface import VPPInterface from .ipip import VPPIPIPInterface +from .loopback import VPPLoopbackInterface from .vxlan import VPPVXLANInterface __all__ = [ 'VPPBondInterface', 'VPPInterface', 'VPPIPIPInterface', + 'VPPLoopbackInterface', 'VPPVXLANInterface', ] diff --git a/python/vyos/ifconfig/vpp/loopback.py b/python/vyos/ifconfig/vpp/loopback.py new file mode 100644 index 000000000..36a30cb58 --- /dev/null +++ b/python/vyos/ifconfig/vpp/loopback.py @@ -0,0 +1,105 @@ +# VyOS implementation of VPP Loopback interface +# +# 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 VPPLoopbackInterface(Interface, VPPInterface): + """Interface Loopback""" + + def __init__(self, ifname, config): + self.ifname = ifname + self.instance = int(ifname.removeprefix('vpplo')) + self.vpp_ifname = f'loop{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' + + def _create(self): + pass + + def add(self): + """Create Loopback interface + https://github.com/FDio/vpp/blob/stable/2306/src/vnet/interface.api + Example: + from vyos.ifconfig.vpp import VPPLoopbackInterface + a = VPPLoopbackInterface(ifname='vpplo1', config) + a.add() + """ + self.vpp.api.create_loopback_instance( + is_specified=True, user_instance=self.instance + ) + # 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(self): + """Delete Loopback interface + Example: + from vyos.ifconfig.vpp import VPPLoopbackInterface + a = VPPLoopbackInterface(ifname='vpplo1', config) + a.delete() + """ + return self.vpp.api.delete_loopback(sw_if_index=self.index) + + def kernel_add(self): + """Add LCP pair + Example: + from vyos.ifconfig.vpp import VPPLoopbackInterface + a = VPPLoopbackInterface(ifname='vpplo1') + 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 VPPLoopbackInterface + a = VPPLoopbackInterface(ifname='vpplo1') + a.kernel_delete() + """ + self.vpp.lcp_pair_del(self.vpp_ifname, self.ifname) + + def remove(self): + if self.index: + # Delete lcp pair interface + if self.vpp.lcp_pair_find(vpp_name_hw=self.vpp_ifname): + self.kernel_delete() + + # Delete loopback interface + self.delete() + + def update(self, config): + # Add loopback interface + self.add() + + # 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/vpp/interface/__init__.py b/python/vyos/vpp/interface/__init__.py index f970edb63..4ae96f486 100644 --- a/python/vyos/vpp/interface/__init__.py +++ b/python/vyos/vpp/interface/__init__.py @@ -20,7 +20,6 @@ from .ethernet import EthernetInterface from .geneve import GeneveInterface from .gre import GREInterface from .interface import Interface -from .loopback import LoopbackInterface from .wireguard import WireguardInterface from .xconnect import XconnectInterface @@ -30,7 +29,6 @@ __all__ = [ 'GeneveInterface', 'GREInterface', 'Interface', - 'LoopbackInterface', 'WireguardInterface', 'XconnectInterface', ] diff --git a/python/vyos/vpp/interface/loopback.py b/python/vyos/vpp/interface/loopback.py deleted file mode 100644 index 357423780..000000000 --- a/python/vyos/vpp/interface/loopback.py +++ /dev/null @@ -1,72 +0,0 @@ -# VyOS implementation of VPP Loopback interface -# -# 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.interface.interface import Interface - - -class LoopbackInterface(Interface): - """Interface Loopback""" - - def __init__(self, ifname, kernel_interface: str = '', state: str = 'up'): - super().__init__(ifname) - self.instance = int(ifname.removeprefix('lo')) - self.ifname = f'loop{self.instance}' - self.kernel_interface = kernel_interface - self.initial_state = state - - def add(self): - """Create Loopback interface - https://github.com/FDio/vpp/blob/stable/2306/src/vnet/interface.api - Example: - from vyos.vpp.interface import LoopbackInterface - a = LoopbackInterface(ifname='lo1') - a.add() - """ - self.vpp.api.create_loopback_instance( - is_specified=True, user_instance=self.instance - ) - # Set interface state - self.set_state(self.initial_state) - - def delete(self): - """Delete Loopback interface - Example: - from vyos.vpp.interface import LoopbackInterface - a = LoopbackInterface(ifname='lo1') - a.delete() - """ - loopback_if_index = self.vpp.get_sw_if_index(f'loop{self.instance}') - return self.vpp.api.delete_loopback(sw_if_index=loopback_if_index) - - def kernel_add(self): - """Add LCP pair - Example: - from vyos.vpp.interface import LoopbackInterface - a = LoopbackInterface(ifname='lo1') - 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 LoopbackInterface - a = LoopbackInterface(ifname='lo1') - a.kernel_delete() - """ - self.vpp.lcp_pair_del(self.ifname, self.kernel_interface) diff --git a/smoketest/configs/assert/vpp b/smoketest/configs/assert/vpp index 8a68852b7..f8cab98f4 100644 --- a/smoketest/configs/assert/vpp +++ b/smoketest/configs/assert/vpp @@ -38,7 +38,7 @@ set vpp interfaces gre gre12 source-address '192.0.2.21' set vpp interfaces gre gre12 tunnel-type 'l3' set interfaces vpp ipip vppipip20 remote '192.0.2.2' set interfaces vpp ipip vppipip20 source-address '192.0.2.1' -set vpp interfaces loopback lo12 description 'Loop12' +set interfaces vpp loopback vpplo12 description 'Loop12' set interfaces vpp vxlan vppvxlan10 remote '192.0.2.2' set interfaces vpp vxlan vppvxlan10 source-address '192.0.2.1' set interfaces vpp vxlan vppvxlan10 vni '10' diff --git a/smoketest/scripts/cli/test_vpp.py b/smoketest/scripts/cli/test_vpp.py index 20baed178..90e4e4538 100755 --- a/smoketest/scripts/cli/test_vpp.py +++ b/smoketest/scripts/cli/test_vpp.py @@ -517,84 +517,28 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): self.cli_commit() def test_05_vpp_loopback(self): - interface_loopback = 'lo11' - interface_kernel = 'vpptun11' - new_interface_kernel = 'vpptun12' - kernel_address = '192.0.2.54' + loopback_path = interfaces_path + ['loopback'] + interface_loopback = 'vpplo11' + address = '192.0.2.54' - self.cli_set(base_path + ['interfaces', 'loopback', interface_loopback]) - self.cli_set( - base_path - + [ - 'interfaces', - 'loopback', - interface_loopback, - 'kernel-interface', - interface_kernel, - ] - ) - self.cli_set( - base_path - + ['kernel-interfaces', interface_kernel, 'address', f'{kernel_address}/25'] - ) + self.cli_set(loopback_path + [interface_loopback]) + self.cli_set(loopback_path + [interface_loopback, 'address', f'{address}/25']) # 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_loopback}')) - current_address = get_address(interface_kernel) - self.assertEqual(kernel_address, current_address) + current_address = get_address(interface_loopback) + self.assertEqual(address, current_address) # check loopback interface _, out = rc_cmd('sudo vppctl show interface loop11') required_str = 'loop11' self.assertIn(required_str, out) - # delete loopback kernel-interface but do not delete 'vpp kernel-interface' - # expect raise ConfigError - self.cli_delete( - base_path - + [ - 'interfaces', - 'loopback', - interface_loopback, - 'kernel-interface', - interface_kernel, - ] - ) - with self.assertRaises(ConfigSessionError): - self.cli_commit() - - # update loopback kernel-interface but do not change 'vpp kernel-interface' - # expect raise ConfigError - self.cli_set( - base_path - + [ - 'interfaces', - 'loopback', - interface_loopback, - 'kernel-interface', - new_interface_kernel, - ] - ) - with self.assertRaises(ConfigSessionError): - self.cli_commit() - - # delete vpp kernel-interface - self.cli_delete(base_path + ['kernel-interfaces', interface_kernel]) - self.cli_commit() - - # delete loopback kernel-interface - self.cli_delete( - base_path - + ['interfaces', 'loopback', interface_loopback, 'kernel-interface'] - ) - self.cli_commit() - self.assertFalse(os.path.isdir(f'/sys/class/net/{interface_kernel}')) - # delete loopback interface - self.cli_delete(base_path + ['interfaces', 'loopback', interface_loopback]) + self.cli_delete(loopback_path + [interface_loopback]) self.cli_commit() def test_06_vpp_bonding(self): @@ -801,7 +745,7 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): self.assertRegex(out, r'\s*vxlan_tunnel23\s+\d+\s+\d+') # Add Loopback BVI to the bridge - self.cli_set(base_path + ['interfaces', 'loopback', f'lo{vni}']) + self.cli_set(interfaces_path + ['loopback', f'vpplo{vni}']) self.cli_set( base_path + [ @@ -810,7 +754,7 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): interface_bridge, 'member', 'interface', - f'lo{vni}', + f'vpplo{vni}', 'bvi', ] ) diff --git a/src/conf_mode/vpp_interfaces_bridge.py b/src/conf_mode/vpp_interfaces_bridge.py index 8d25cba00..d53173f04 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 = ('vppbond', 'gre', 'geneve', 'lo', 'vppvxlan') + allowed_prefixes = ('vppbond', 'gre', 'geneve', 'vpplo', 'vppvxlan') if 'member' in config: bvi_exists = False @@ -116,7 +116,7 @@ def verify(config): if 'bvi' in member_config: if bvi_exists: raise ConfigError("Only one BVI per bridge domain is allowed") - if not member.startswith('lo'): + if not member.startswith('vpplo'): raise ConfigError("BVI can only be defined on loopback interface") bvi_exists = True @@ -138,9 +138,9 @@ def apply(config): for member in config.get('members_removed'): if member.startswith(interface_transform_filter): member = iftunnel_transform(member) - if member.startswith('lo'): + if member.startswith('vpplo'): # interface name in VPP is loopX - member = member.replace('lo', 'loop') + member = member.replace('vpplo', 'loop') elif member.startswith('vppbond'): # interface name in VPP is BondEthernetX member = member.replace('vppbond', 'BondEthernet') @@ -166,9 +166,9 @@ def apply(config): for member, member_config in members.items(): if member.startswith(interface_transform_filter): member = iftunnel_transform(member) - if member.startswith('lo'): + if member.startswith('vpplo'): # interface name in VPP is loopX - member = member.replace('lo', 'loop') + member = member.replace('vpplo', 'loop') if 'bvi' in member_config: port_type = 1 elif member.startswith('vppbond'): diff --git a/src/conf_mode/vpp_interfaces_loopback.py b/src/conf_mode/vpp_interfaces_loopback.py index d08557959..2f7b59354 100644 --- a/src/conf_mode/vpp_interfaces_loopback.py +++ b/src/conf_mode/vpp_interfaces_loopback.py @@ -16,22 +16,14 @@ # 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 import ConfigError 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.template import is_interface +from vyos.utils.process import is_systemd_service_active -from vyos.vpp.interface import LoopbackInterface -from vyos.vpp.config_verify import ( - verify_vpp_remove_kernel_interface, - verify_vpp_change_kernel_interface, - verify_vpp_exists_kernel_interface, -) -from vyos.vpp.utils import cli_ifaces_lcp_kernel_list +from vyos.ifconfig.vpp import VPPLoopbackInterface def get_config(config=None) -> dict: @@ -47,62 +39,22 @@ def get_config(config=None) -> dict: else: conf = Config() - base = ['vpp', 'interfaces', 'loopback'] - base_kernel_interfaces = ['vpp', 'kernel-interfaces'] - - ifname = os.environ['VYOS_TAGNODE_VALUE'] + base = ['interfaces', 'vpp', 'loopback'] - # 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 'vpp settings' config + config['vpp_settings'] = conf.get_config_dict( + ['vpp', 'settings'], 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 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, - ) - - tmp = leaf_node_changed(conf, base + [ifname, 'kernel-interface']) - if tmp: - config['kernel_interface_removed'] = tmp - - # list of all kernel interfaces `vpp interface xxx kernel-interface xxx` - config['candidate_kernel_interfaces'] = cli_ifaces_lcp_kernel_list(conf) - - # Dependency - if effective_config.get('kernel_interface'): - if conf.exists(base + [ifname, 'kernel-interface']): - 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) @@ -113,7 +65,6 @@ def get_config(config=None) -> dict: if conf.exists(['vpp', 'acl']): set_dependents('vpp_acl', conf) - config['ifname'] = ifname return config @@ -122,13 +73,10 @@ def verify(config): if 'remove_vpp' in config: return None - verify_vpp_remove_kernel_interface(config) - - if 'remove' in config: - return None - - verify_vpp_change_kernel_interface(config) - verify_vpp_exists_kernel_interface(config) + if not is_systemd_service_active('vpp.service'): + raise ConfigError( + 'Cannot configure VPP loopback interface: vpp.service is not running' + ) def generate(config): @@ -140,23 +88,13 @@ def apply(config): return None ifname = config.get('ifname') - # Delete interface - if 'effective' in config: - i = LoopbackInterface(ifname) - i.delete() - - if 'remove' in config: - return None + loopback = VPPLoopbackInterface(ifname, config) + loopback.remove() - # Add interface - kernel_interface = config.get('kernel_interface', '') - state = 'up' if 'disable' not in config else 'down' - i = LoopbackInterface(ifname, kernel_interface, state) - i.add() + if 'deleted' in config: + return - # Add kernel-interface (LCP) if interface is not exist - if 'kernel_interface' in config and not is_interface(kernel_interface): - i.kernel_add() + loopback.update(config) call_dependents() diff --git a/src/migration-scripts/vpp/5-to-6 b/src/migration-scripts/vpp/5-to-6 index 18f0f70d2..23b2d4eaa 100644 --- a/src/migration-scripts/vpp/5-to-6 +++ b/src/migration-scripts/vpp/5-to-6 @@ -43,6 +43,8 @@ # # Move ipip interface from vpp section to 'interfaces vpp ipip' (T8314) # +# Migrate loopback interface from vpp section to 'interfaces vpp loopback' (T8324) +# from vyos.configtree import ConfigTree @@ -437,6 +439,30 @@ def _migrate_vpp_ipip_interface(config: ConfigTree) -> None: config.delete(['vpp', 'interfaces']) +def _migrate_vpp_loopback_interface(config: ConfigTree) -> None: + base = ['vpp', 'interfaces', 'loopback'] + new_base = ['interfaces', 'vpp', 'loopback'] + kernel_interface_base = ['vpp', 'kernel-interfaces'] + + if not config.exists(base): + return + + config.set(new_base) + + for ifname in config.list_nodes(base): + _migrate_interface(config, 'loopback', ifname) + + _migrate_members_bridge(config, ifname) + + 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']) + + def migrate(config: ConfigTree) -> None: if not config.exists(['vpp']): # Nothing to do @@ -455,3 +481,4 @@ def migrate(config: ConfigTree) -> None: _migrate_vpp_bonding_interface(config) _migrate_vpp_vxlan_interface(config) _migrate_vpp_ipip_interface(config) + _migrate_vpp_loopback_interface(config) |
