diff options
| author | Nataliia Solomko <natalirs1985@gmail.com> | 2026-02-20 12:42:35 +0200 |
|---|---|---|
| committer | Nataliia Solomko <natalirs1985@gmail.com> | 2026-02-25 19:25:31 +0200 |
| commit | dec957d8bf09f2a186bc12478eec275a88ef3896 (patch) | |
| tree | ec8449e209a87e52af224f28d8854bc9109b6f5a /src | |
| parent | 6fd7a87116ea91edbdbe86ed19c8274682d9ee8b (diff) | |
| download | vyos-1x-dec957d8bf09f2a186bc12478eec275a88ef3896.tar.gz vyos-1x-dec957d8bf09f2a186bc12478eec275a88ef3896.zip | |
vpp: T8283: Move bonding interface from vpp secction to 'interfaces vpp bonding'
Diffstat (limited to 'src')
| -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 |
6 files changed, 170 insertions, 118 deletions
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) |
