From bb4427bfb4a8c03784dd6f097cbe521f561edaf4 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Tue, 7 Jan 2025 21:54:47 +0000 Subject: Add vyos-vpp CLI and python3 modules --- src/conf_mode/vpp_kernel-interfaces.py | 192 +++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 src/conf_mode/vpp_kernel-interfaces.py (limited to 'src/conf_mode/vpp_kernel-interfaces.py') diff --git a/src/conf_mode/vpp_kernel-interfaces.py b/src/conf_mode/vpp_kernel-interfaces.py new file mode 100644 index 000000000..4e4ecb442 --- /dev/null +++ b/src/conf_mode/vpp_kernel-interfaces.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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. + +import os + +from vyos.config import Config +from vyos.configdict import leaf_node_changed +from vyos.configdict import node_changed +from vyos import ConfigError +from vyos.ifconfig import Interface +from vyos.utils.network import interface_exists +from vyos.utils.process import call +from vyos.vpp import VPPControl + + +def get_config(config=None) -> dict: + if config: + conf = config + else: + conf = Config() + + base = ['vpp', 'kernel-interfaces'] + + ifname = os.environ['VYOS_TAGNODE_VALUE'] + + # 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, + ) + + # Get effective config as we need full dicitonary per interface delete + if __name__ == '__main__': + effective_config = conf.get_config_dict( + base + [ifname], + key_mangling=('-', '_'), + effective=True, + get_first_key=True, + no_tag_node_value_mangle=True, + ) + # if a file was started as dependency, we are starting from empty config + else: + effective_config = {} + + if effective_config: + config.update({'effective': effective_config}) + + address_removed = leaf_node_changed(conf, base + [ifname, 'address']) + if address_removed: + config['address_removed'] = address_removed + + description_removed = leaf_node_changed(conf, base + [ifname, 'description']) + if description_removed: + config['description_removed'] = {} + + vlans_removed = node_changed(conf, base + [ifname, 'vif']) + if vlans_removed: + config['vlans_removed'] = vlans_removed + + config['ifname'] = ifname + + return config + + +def verify(config): + if 'remove' in config: + return None + + # Interface must exists before it is configured + if not interface_exists(config['ifname']): + raise ConfigError( + f'Interface {config["ifname"]} must be created before using in configuration' + ) + + +def generate(config): + pass + + +def apply(config): + ifname = config.get('ifname') + i = Interface(ifname) + # update/remove addresses + if 'address_removed' in config: + for address in config['address_removed']: + i.del_addr(address) + # remove description + if 'description_removed' in config: + i.set_alias('') + + # remove VLANs + if 'vlans_removed' in config: + for vlan in config['vlans_removed']: + call(f'ip link del dev {ifname}.{vlan}') + + # Delete + if 'remove' in config: + pass + else: + # Add address + if 'address' in config: + for address in config['address']: + i.add_addr(address) + # Set MTU + if 'mtu' in config: + i.set_mtu(config.get('mtu')) + # Set description + if 'description' in config: + i.set_alias(config.get('description')) + # Admin state down + if 'disable' in config: + i.set_admin_state('down') + else: + i.set_admin_state('up') + + for vlan, vlan_config in config.get('vif', {}).items(): + if vlan not in config.get('effective', {}).get('vif', {}).keys(): + call( + f'ip link add link {ifname} name {ifname}.{vlan} type vlan id {vlan}' + ) + call(f'ip link set dev {ifname}.{vlan} up') + v = Interface(f'{ifname}.{vlan}') + + # VLAN address + addresses_effective = ( + config.get('effective', {}) + .get('vif', {}) + .get(vlan, {}) + .get('address', []) + ) + addresses_candidate = vlan_config.get('address', []) + + for ipaddr in addresses_effective: + if ipaddr not in addresses_candidate: + v.del_addr(ipaddr) + for ipaddr in addresses_candidate: + if ipaddr not in addresses_effective: + v.add_addr(ipaddr) + + # VLAN description + description_effective = ( + config.get('effective', {}) + .get('vif', {}) + .get(vlan, {}) + .get('description', '') + ) + description_candidate = vlan_config.get('description', '') + + if description_candidate: + v.set_alias(description_candidate) + elif description_effective and not description_candidate: + v.set_alias('') + + v.set_admin_state('up') + + # Set rx-mode + rx_mode = config.get('rx_mode') + if rx_mode: + vpp_control = VPPControl() + lcp_name = vpp_control.lcp_pair_find(kernel_name=ifname).get('vpp_name_kernel') + vpp_control.iface_rxmode(lcp_name, rx_mode) + + return None + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) -- cgit v1.2.3 From 31beaf550786502f5b62034c6e4b2e0e52508f8c Mon Sep 17 00:00:00 2001 From: Nataliia Solomko Date: Tue, 27 May 2025 14:27:50 +0300 Subject: T7488: Make VPP restartable --- data/templates/vpp/override.conf.j2 | 4 ++-- src/conf_mode/vpp_acl.py | 9 ++++++++- src/conf_mode/vpp_interfaces_bonding.py | 10 ++++++++++ src/conf_mode/vpp_interfaces_bridge.py | 9 ++++++++- src/conf_mode/vpp_interfaces_geneve.py | 11 +++++++++++ src/conf_mode/vpp_interfaces_gre.py | 11 +++++++++++ src/conf_mode/vpp_interfaces_ipip.py | 11 +++++++++++ src/conf_mode/vpp_interfaces_loopback.py | 11 +++++++++++ src/conf_mode/vpp_interfaces_vxlan.py | 11 +++++++++++ src/conf_mode/vpp_interfaces_xconnect.py | 9 ++++++++- src/conf_mode/vpp_kernel-interfaces.py | 9 ++++++++- src/conf_mode/vpp_nat.py | 9 ++++++++- src/conf_mode/vpp_nat_cgnat.py | 9 ++++++++- src/systemd/vpp-failure-handler.service | 9 +++++++++ 14 files changed, 124 insertions(+), 8 deletions(-) create mode 100644 src/systemd/vpp-failure-handler.service (limited to 'src/conf_mode/vpp_kernel-interfaces.py') diff --git a/data/templates/vpp/override.conf.j2 b/data/templates/vpp/override.conf.j2 index a2c2b04ed..04bb7be15 100644 --- a/data/templates/vpp/override.conf.j2 +++ b/data/templates/vpp/override.conf.j2 @@ -3,6 +3,7 @@ After= After=vyos-router.service ConditionPathExists= ConditionPathExists=/run/vpp/vpp.conf +OnFailure=vpp-failure-handler.service [Service] EnvironmentFile= @@ -10,5 +11,4 @@ ExecStart= ExecStart=/usr/bin/vpp -c /run/vpp/vpp.conf WorkingDirectory= WorkingDirectory=/run/vpp -Restart=always -RestartSec=10 +Restart=no diff --git a/src/conf_mode/vpp_acl.py b/src/conf_mode/vpp_acl.py index 28eae485b..d6a9150d1 100644 --- a/src/conf_mode/vpp_acl.py +++ b/src/conf_mode/vpp_acl.py @@ -147,6 +147,10 @@ def get_config(config=None) -> dict: with_recursive_defaults=True, ) + if not conf.exists(['vpp']): + config['remove_vpp'] = True + return config + # Get effective config as we need full dictionary for deletion effective_config = conf.get_config_dict( base, @@ -190,7 +194,7 @@ def get_config(config=None) -> dict: def verify(config): - if 'remove' in config: + if 'remove' in config or 'remove_vpp' in config: return None for acl_type in ['ip', 'macip']: @@ -319,6 +323,9 @@ def generate(config): def apply(config): + if 'remove_vpp' in config: + return None + acl = Acl() if 'effective' in config: diff --git a/src/conf_mode/vpp_interfaces_bonding.py b/src/conf_mode/vpp_interfaces_bonding.py index 2b17acb24..c9aa6276c 100644 --- a/src/conf_mode/vpp_interfaces_bonding.py +++ b/src/conf_mode/vpp_interfaces_bonding.py @@ -86,6 +86,10 @@ def get_config(config=None) -> dict: with_recursive_defaults=True, ) + if not conf.exists(['vpp']): + 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], @@ -155,6 +159,9 @@ def get_config(config=None) -> dict: def verify(config): + if 'remove_vpp' in config: + return None + verify_vpp_remove_kernel_interface(config) verify_vpp_remove_xconnect_interface(config) @@ -175,6 +182,9 @@ def generate(config): def apply(config): + if 'remove_vpp' in config: + return None + ifname = config.get('ifname') # remove old members if 'effective' in config: diff --git a/src/conf_mode/vpp_interfaces_bridge.py b/src/conf_mode/vpp_interfaces_bridge.py index dd5b49599..4272db616 100644 --- a/src/conf_mode/vpp_interfaces_bridge.py +++ b/src/conf_mode/vpp_interfaces_bridge.py @@ -53,6 +53,10 @@ def get_config(config=None) -> dict: with_recursive_defaults=True, ) + if not conf.exists(['vpp']): + 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], @@ -87,7 +91,7 @@ def get_config(config=None) -> dict: def verify(config): - if 'remove' in config: + if 'remove' in config or 'remove_vpp' in config: return None # Check if interface exists in vpp before adding to bridge-domain @@ -122,6 +126,9 @@ def generate(config): def apply(config): + if 'remove_vpp' in config: + return None + ifname = config.get('ifname') # vxlan10 in the vpp is vxlan_tunnel10 interface_transform_filter = ('geneve', 'vxlan') diff --git a/src/conf_mode/vpp_interfaces_geneve.py b/src/conf_mode/vpp_interfaces_geneve.py index a7e7ae8e5..12fac83fb 100644 --- a/src/conf_mode/vpp_interfaces_geneve.py +++ b/src/conf_mode/vpp_interfaces_geneve.py @@ -65,6 +65,10 @@ def get_config(config=None) -> dict: with_recursive_defaults=True, ) + if not conf.exists(['vpp']): + 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], @@ -112,6 +116,10 @@ def get_config(config=None) -> dict: def verify(config): + # No need to verify anything if vpp is removed + if 'remove_vpp' in config: + return None + # Verify that removed kernel interface is not used in 'vpp kernel-interfaces'. # vpp interfaces geneve geneveX kernel-interface vpp-tunX # vpp kernel-interface vpp-tunX @@ -141,6 +149,9 @@ def generate(config): def apply(config): + if 'remove_vpp' in config: + return None + ifname = config.get('ifname') # Delete interface if 'effective' in config: diff --git a/src/conf_mode/vpp_interfaces_gre.py b/src/conf_mode/vpp_interfaces_gre.py index f6da2d55d..5ec6feeb0 100644 --- a/src/conf_mode/vpp_interfaces_gre.py +++ b/src/conf_mode/vpp_interfaces_gre.py @@ -65,6 +65,10 @@ def get_config(config=None) -> dict: with_recursive_defaults=True, ) + if not conf.exists(['vpp']): + 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], @@ -125,6 +129,10 @@ def get_config(config=None) -> dict: def verify(config): + # No need to verify anything if vpp is removed + if 'remove_vpp' in config: + return None + # Verify that removed kernel interface is not used in 'vpp kernel-interfaces'. # vpp interfaces gre greX kernel-interface vpp-tunX # vpp kernel-interface vpp-tunX @@ -167,6 +175,9 @@ def generate(config): def apply(config): + if 'remove_vpp' in config: + return None + ifname = config.get('ifname') # Delete interface if 'effective' in config: diff --git a/src/conf_mode/vpp_interfaces_ipip.py b/src/conf_mode/vpp_interfaces_ipip.py index f40a4e243..430b7b334 100644 --- a/src/conf_mode/vpp_interfaces_ipip.py +++ b/src/conf_mode/vpp_interfaces_ipip.py @@ -64,6 +64,10 @@ def get_config(config=None) -> dict: with_recursive_defaults=True, ) + if not conf.exists(['vpp']): + 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], @@ -125,6 +129,10 @@ def get_config(config=None) -> dict: def verify(config): + # No need to verify anything if vpp is removed + if 'remove_vpp' in config: + return None + # Verify that removed kernel interface is not used in 'vpp kernel-interfaces'. # vpp interfaces ipip ipipX kernel-interface vpp-tunX # vpp kernel-interface vpp-tunX @@ -161,6 +169,9 @@ def generate(config): def apply(config): + if 'remove_vpp' in config: + return None + ifname = config.get('ifname') # Delete interface if 'effective' in config: diff --git a/src/conf_mode/vpp_interfaces_loopback.py b/src/conf_mode/vpp_interfaces_loopback.py index 2b37b5e1c..92ba110f1 100644 --- a/src/conf_mode/vpp_interfaces_loopback.py +++ b/src/conf_mode/vpp_interfaces_loopback.py @@ -62,6 +62,10 @@ def get_config(config=None) -> dict: with_recursive_defaults=True, ) + if not conf.exists(['vpp']): + 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], @@ -114,6 +118,10 @@ def get_config(config=None) -> dict: def verify(config): + # No need to verify anything if vpp is removed + if 'remove_vpp' in config: + return None + verify_vpp_remove_kernel_interface(config) if 'remove' in config: @@ -128,6 +136,9 @@ def generate(config): def apply(config): + if 'remove_vpp' in config: + return None + ifname = config.get('ifname') # Delete interface if 'effective' in config: diff --git a/src/conf_mode/vpp_interfaces_vxlan.py b/src/conf_mode/vpp_interfaces_vxlan.py index 065a8eafb..c4068818e 100644 --- a/src/conf_mode/vpp_interfaces_vxlan.py +++ b/src/conf_mode/vpp_interfaces_vxlan.py @@ -66,6 +66,10 @@ def get_config(config=None) -> dict: with_recursive_defaults=True, ) + if not conf.exists(['vpp']): + 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], @@ -131,6 +135,10 @@ def get_config(config=None) -> dict: def verify(config): + # No need to verify anything if vpp is removed + if 'remove_vpp' in config: + return None + # Verify that removed kernel interface is not used in 'vpp kernel-interfaces'. # vpp interfaces vxlan vxlanX kernel-interface vpp-tunX # vpp kernel-interface vpp-tunX @@ -165,6 +173,9 @@ def generate(config): def apply(config): + if 'remove_vpp' in config: + return None + ifname = config.get('ifname') # Delete interface if 'effective' in config: diff --git a/src/conf_mode/vpp_interfaces_xconnect.py b/src/conf_mode/vpp_interfaces_xconnect.py index c323684a3..220c1d44e 100644 --- a/src/conf_mode/vpp_interfaces_xconnect.py +++ b/src/conf_mode/vpp_interfaces_xconnect.py @@ -53,6 +53,10 @@ def get_config(config=None) -> dict: with_recursive_defaults=True, ) + if not conf.exists(['vpp']): + 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], @@ -85,7 +89,7 @@ def get_config(config=None) -> dict: def verify(config): - if 'remove' in config: + if 'remove' in config or 'remove_vpp' in config: return None # Xconnect requires 2 members @@ -110,6 +114,9 @@ def generate(config): def apply(config): + if 'remove_vpp' in config: + return None + ifname = config.get('ifname') # Delete xconnect diff --git a/src/conf_mode/vpp_kernel-interfaces.py b/src/conf_mode/vpp_kernel-interfaces.py index 4e4ecb442..f9354c964 100644 --- a/src/conf_mode/vpp_kernel-interfaces.py +++ b/src/conf_mode/vpp_kernel-interfaces.py @@ -47,6 +47,10 @@ def get_config(config=None) -> dict: with_defaults=True, ) + if not conf.exists(['vpp']): + config['remove_vpp'] = True + return config + # Get effective config as we need full dicitonary per interface delete if __name__ == '__main__': effective_config = conf.get_config_dict( @@ -81,7 +85,7 @@ def get_config(config=None) -> dict: def verify(config): - if 'remove' in config: + if 'remove' in config or 'remove_vpp' in config: return None # Interface must exists before it is configured @@ -96,6 +100,9 @@ def generate(config): def apply(config): + if 'remove_vpp' in config: + return None + ifname = config.get('ifname') i = Interface(ifname) # update/remove addresses diff --git a/src/conf_mode/vpp_nat.py b/src/conf_mode/vpp_nat.py index a6bc70032..f8ac4f0e3 100644 --- a/src/conf_mode/vpp_nat.py +++ b/src/conf_mode/vpp_nat.py @@ -55,6 +55,10 @@ def get_config(config=None) -> dict: with_recursive_defaults=True, ) + if not conf.exists(['vpp']): + config['remove_vpp'] = True + return config + # Get effective config as we need full dictionary for deletion effective_config = conf.get_config_dict( base, @@ -131,7 +135,7 @@ def convert_range_to_list_ips(address_range) -> list: def verify(config): - if 'remove' in config: + if 'remove' in config or 'remove_vpp' in config: return None if 'interface' not in config: @@ -329,6 +333,9 @@ def generate(config): def apply(config): + if 'remove_vpp' in config: + return None + n = Nat44() if 'remove' in config: diff --git a/src/conf_mode/vpp_nat_cgnat.py b/src/conf_mode/vpp_nat_cgnat.py index 05260c476..68695b78e 100644 --- a/src/conf_mode/vpp_nat_cgnat.py +++ b/src/conf_mode/vpp_nat_cgnat.py @@ -41,6 +41,10 @@ def get_config(config=None) -> dict: no_tag_node_value_mangle=True, ) + if not conf.exists(['vpp']): + config['remove_vpp'] = True + return config + # Get effective config as we need full dictionary to delete effective_config = conf.get_config_dict( base, @@ -91,7 +95,7 @@ def get_config(config=None) -> dict: def verify(config): - if 'remove' in config: + if 'remove' in config or 'remove_vpp' in config: return None if 'interface' not in config: @@ -142,6 +146,9 @@ def generate(config): def apply(config): + if 'remove_vpp' in config: + return None + cgnat = Det44() if 'remove' in config: diff --git a/src/systemd/vpp-failure-handler.service b/src/systemd/vpp-failure-handler.service new file mode 100644 index 000000000..3a75f9519 --- /dev/null +++ b/src/systemd/vpp-failure-handler.service @@ -0,0 +1,9 @@ +[Unit] +Description=Restart VPP on failure + +[Service] +Type=oneshot +User=root +Group=vyattacfg +UMask=0002 +ExecStart=/usr/bin/python3 /usr/libexec/vyos/reset_section.py vpp --reload -- cgit v1.2.3