diff options
| author | KyleM <103862795+ServerForge@users.noreply.github.com> | 2025-07-31 10:39:01 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-07-31 15:39:01 +0100 |
| commit | 5631aed811981ff5fb3351eb368a2f7f544b8fbb (patch) | |
| tree | 40a75d92d71eb990b9bd9b75c0185da1e9a431c5 | |
| parent | d37cca0672b71691614da0a0f770eaf0fe117fa4 (diff) | |
| download | vyos-1x-5631aed811981ff5fb3351eb368a2f7f544b8fbb.tar.gz vyos-1x-5631aed811981ff5fb3351eb368a2f7f544b8fbb.zip | |
VPP: T7175: Add sFlow conf mode CLI and startup template (#32)
* VPP: T7175
Added conf mode CLI for VPP sflow plugin and updated VPP template to include plugin.
* VPP: T7175: Conf mode CLI and startup template.
* T7175: VPP fix sFlow verify use vpp enstead of enable_vpp
* T7175: VPP add sFlow smoketest
* T7175: VPP remove unused config_changed variable
---------
Co-authored-by: Viacheslav <v.gletenko@vyos.io>
| -rw-r--r-- | data/config-mode-dependencies/vyos-vpp.json | 3 | ||||
| -rw-r--r-- | data/templates/vpp/startup.conf.j2 | 1 | ||||
| -rw-r--r-- | interface-definitions/vpp.xml.in | 30 | ||||
| -rwxr-xr-x | smoketest/scripts/cli/test_vpp.py | 24 | ||||
| -rwxr-xr-x | src/conf_mode/vpp.py | 4 | ||||
| -rw-r--r-- | src/conf_mode/vpp_sflow.py | 145 |
6 files changed, 206 insertions, 1 deletions
diff --git a/data/config-mode-dependencies/vyos-vpp.json b/data/config-mode-dependencies/vyos-vpp.json index 2a5613559..32b53508e 100644 --- a/data/config-mode-dependencies/vyos-vpp.json +++ b/data/config-mode-dependencies/vyos-vpp.json @@ -13,7 +13,8 @@ "vpp_acl": ["vpp_acl"], "vpp_nat": ["vpp_nat"], "vpp_nat_cgnat": ["vpp_nat_cgnat"], - "vpp_kernel_interface": ["vpp_kernel-interfaces"] + "vpp_kernel_interface": ["vpp_kernel-interfaces"], + "vpp_sflow": ["vpp_sflow"] }, "vpp_interfaces_bonding": { "vpp_interfaces_xconnect": ["vpp_interfaces_xconnect"], diff --git a/data/templates/vpp/startup.conf.j2 b/data/templates/vpp/startup.conf.j2 index 79a551338..b7515889a 100644 --- a/data/templates/vpp/startup.conf.j2 +++ b/data/templates/vpp/startup.conf.j2 @@ -92,6 +92,7 @@ plugins { plugin linux_cp_plugin.so { enable } plugin linux_nl_plugin.so { enable } plugin pppoe_plugin.so { enable } + plugin sflow_plugin.so { enable } # NAT uncomment if needed # plugin cnat_plugin.so { enable } plugin nat_plugin.so { enable } diff --git a/interface-definitions/vpp.xml.in b/interface-definitions/vpp.xml.in index 76a2f245a..dff56a228 100644 --- a/interface-definitions/vpp.xml.in +++ b/interface-definitions/vpp.xml.in @@ -936,6 +936,36 @@ </node> </children> </node> + <node name="sflow" owner="${vyos_conf_scripts_dir}/vpp_sflow.py"> + <properties> + <help>VPP data-plane sFlow</help> + <priority>322</priority> + </properties> + <children> + <leafNode name="interface"> + <properties> + <help>Interface name</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface name</description> + </valueHelp> + <multi/> + </properties> + </leafNode> + <leafNode name="sample-rate"> + <properties> + <help>sFlow sample rate</help> + <valueHelp> + <format>u32</format> + <description>sFlow sample rate</description> + </valueHelp> + </properties> + </leafNode> + </children> + </node> <node name="nat"> <properties> <help>Network Address Translation (NAT) settings</help> diff --git a/smoketest/scripts/cli/test_vpp.py b/smoketest/scripts/cli/test_vpp.py index feb0f3fb3..16302adea 100755 --- a/smoketest/scripts/cli/test_vpp.py +++ b/smoketest/scripts/cli/test_vpp.py @@ -1393,6 +1393,30 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): _, out = rc_cmd('sudo vppctl show nat44 summary') self.assertIn(f'max translations per thread: {sess_limit} fib 0', out) + def test_18_vpp_sflow(self): + base_sflow = ['system', 'sflow'] + + self.cli_set(base_path + ['sflow', 'interface', interface]) + self.cli_set(base_sflow + ['interface', interface]) + self.cli_set(base_sflow + ['server', '127.0.0.1']) + self.cli_set(base_sflow + ['vpp']) + self.cli_commit() + + # Check sFlow + _, out = rc_cmd('sudo vppctl show sflow') + + expected_entries = ( + 'sflow sampling-direction ingress', + f'sflow enable {interface}', + 'interfaces enabled: 1', + ) + + for expected_entry in expected_entries: + self.assertIn(expected_entry, out) + + self.cli_delete(base_sflow) + self.cli_commit() + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/vpp.py b/src/conf_mode/vpp.py index 9b9a3466b..0ea8d261b 100755 --- a/src/conf_mode/vpp.py +++ b/src/conf_mode/vpp.py @@ -149,6 +149,10 @@ def get_config(config=None): if conf.exists(['vpp', 'nat', 'cgnat']): set_dependents('vpp_nat_cgnat', conf) + # sFlow dependency + if conf.exists(['vpp', 'sflow']): + set_dependents('vpp_sflow', conf) + # ACL dependency if conf.exists(['vpp', 'acl']): set_dependents('vpp_acl', conf) diff --git a/src/conf_mode/vpp_sflow.py b/src/conf_mode/vpp_sflow.py new file mode 100644 index 000000000..ebe91617b --- /dev/null +++ b/src/conf_mode/vpp_sflow.py @@ -0,0 +1,145 @@ +# Copyright (C) 2025 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# 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, see <http://www.gnu.org/licenses/>. +# + +from vyos import ConfigError +from vyos.config import Config +from vyos.vpp.utils import cli_ifaces_list +from vyos.vpp import VPPControl + + +def get_config(config=None) -> dict: + if config: + conf = config + else: + conf = Config() + + base = ['vpp', 'sflow'] + + # Get config_dict with default values + config = conf.get_config_dict( + base, + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + with_defaults=True, + with_recursive_defaults=True, + ) + + # Get effective config as we need full dictionary for deletion + effective_config = conf.get_config_dict( + base, + key_mangling=('-', '_'), + effective=True, + get_first_key=True, + no_tag_node_value_mangle=True, + ) + + # Get system sflow configuration to check for server + system_sflow = conf.get_config_dict( + ['system', 'sflow'], + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + ) + + if system_sflow: + config['system_sflow'] = system_sflow + + if not config: + config['remove'] = True + return config + + # Add list of VPP interfaces to the config + config.update({'vpp_ifaces': cli_ifaces_list(conf)}) + + if effective_config: + config.update({'effective': effective_config}) + + return config + + +def verify(config): + if 'remove' in config: + return None + + # Check if interface section exists + if 'interface' not in config: + return None + + # Verify that all interfaces specified exist in VPP + for interface in config['interface']: + if interface not in config['vpp_ifaces']: + raise ConfigError( + f'{interface} must be a VPP interface for sFlow monitoring' + ) + + # Verify sample rate is a positive integer + if 'sample_rate' in config: + try: + sample_rate = int(config['sample_rate']) + if sample_rate <= 0: + raise ConfigError('sFlow sample rate must be a positive integer') + except ValueError: + raise ConfigError('sFlow sample rate must be a valid integer') + + # Verify that system sflow has enable-vpp defined + if 'system_sflow' not in config or 'vpp' not in config.get('system_sflow', {}): + raise ConfigError( + 'sFlow enable-vpp must be defined under system sflow configuration' + ) + + +def generate(config): + # No templates to render for sFlow + pass + + +def apply(config): + # Initialize VPP control API + vpp = VPPControl(attempts=20, interval=500) + + if 'remove' in config: + # Disable sFlow on all interfaces + for interface in config.get('effective', {}).get('interface', []): + vpp.cli_cmd(f'sflow enable-disable {interface} disable') + return None + + # Configure sample rate if specified + if 'sample_rate' in config: + vpp.cli_cmd(f'sflow sampling-rate {config["sample_rate"]}') + + # Configure interfaces + if 'interface' in config: + # Enable sFlow on specified interfaces + for interface in config['interface']: + vpp.cli_cmd(f'sflow enable-disable {interface}') + + # Disable sFlow on interfaces that were removed from config + effective_interfaces = config.get('effective', {}).get('interface', []) + if effective_interfaces: + for interface in effective_interfaces: + if interface not in config['interface']: + vpp.cli_cmd(f'sflow enable-disable {interface} disable') + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) |
