diff --git a/smoketest/scripts/cli/test_system_sflow.py b/smoketest/scripts/cli/test_system_sflow.py index f50052d31..fef88b56a 100755 --- a/smoketest/scripts/cli/test_system_sflow.py +++ b/smoketest/scripts/cli/test_system_sflow.py @@ -1,96 +1,100 @@ #!/usr/bin/env python3 # # Copyright (C) 2023 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/>. import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section from vyos.util import cmd from vyos.util import process_named_running from vyos.util import read_file PROCESS_NAME = 'hsflowd' base_path = ['system', 'sflow'] hsflowd_conf = '/run/sflow/hsflowd.conf' class TestSystemFlowAccounting(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): super(TestSystemFlowAccounting, cls).setUpClass() # ensure we can also run this test on a live system - so lets clean # out the current configuration :) cls.cli_delete(cls, base_path) def tearDown(self): # after service removal process must no longer run self.assertTrue(process_named_running(PROCESS_NAME)) self.cli_delete(base_path) self.cli_commit() # after service removal process must no longer run self.assertFalse(process_named_running(PROCESS_NAME)) def test_sflow(self): agent_address = '192.0.2.5' agent_interface = 'eth0' polling = '24' sampling_rate = '128' server = '192.0.2.254' + local_server = '127.0.0.1' port = '8192' + default_port = '6343' mon_limit = '50' self.cli_set( ['interfaces', 'dummy', 'dum0', 'address', f'{agent_address}/24']) self.cli_set(base_path + ['agent-address', agent_address]) self.cli_set(base_path + ['agent-interface', agent_interface]) # You need to configure at least one interface for sflow with self.assertRaises(ConfigSessionError): self.cli_commit() for interface in Section.interfaces('ethernet'): self.cli_set(base_path + ['interface', interface]) self.cli_set(base_path + ['polling', polling]) self.cli_set(base_path + ['sampling-rate', sampling_rate]) self.cli_set(base_path + ['server', server, 'port', port]) + self.cli_set(base_path + ['server', local_server]) self.cli_set(base_path + ['drop-monitor-limit', mon_limit]) # commit changes self.cli_commit() # verify configuration hsflowd = read_file(hsflowd_conf) self.assertIn(f'polling={polling}', hsflowd) self.assertIn(f'sampling={sampling_rate}', hsflowd) self.assertIn(f'agentIP={agent_address}', hsflowd) self.assertIn(f'agent={agent_interface}', hsflowd) self.assertIn(f'collector {{ ip = {server} udpport = {port} }}', hsflowd) + self.assertIn(f'collector {{ ip = {local_server} udpport = {default_port} }}', hsflowd) self.assertIn(f'dropmon {{ limit={mon_limit} start=on sw=on hw=off }}', hsflowd) for interface in Section.interfaces('ethernet'): self.assertIn(f'pcap {{ dev={interface} }}', hsflowd) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/system_sflow.py b/src/conf_mode/system_sflow.py index 2e19a5d76..a0c3fca7f 100755 --- a/src/conf_mode/system_sflow.py +++ b/src/conf_mode/system_sflow.py @@ -1,118 +1,124 @@ #!/usr/bin/env python3 # # Copyright (C) 2023 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/>. import os from sys import exit from vyos.config import Config from vyos.configdict import dict_merge from vyos.template import render from vyos.util import call from vyos.validate import is_addr_assigned from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() hsflowd_conf_path = '/run/sflow/hsflowd.conf' systemd_service = 'hsflowd.service' systemd_override = f'/run/systemd/system/{systemd_service}.d/override.conf' def get_config(config=None): if config: conf = config else: conf = Config() base = ['system', 'sflow'] if not conf.exists(base): return None sflow = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) # We have gathered the dict representation of the CLI, but there are default # options which we need to update into the dictionary retrived. default_values = defaults(base) sflow = dict_merge(default_values, sflow) # Ignore default XML values if config doesn't exists # Delete key from dict if 'port' in sflow['server']: del sflow['server']['port'] + # Set default values per server + if 'server' in sflow: + for server in sflow['server']: + default_values = defaults(base + ['server']) + sflow['server'][server] = dict_merge(default_values, sflow['server'][server]) + return sflow def verify(sflow): if not sflow: return None # Check if configured sflow agent-address exist in the system if 'agent_address' in sflow: tmp = sflow['agent_address'] if not is_addr_assigned(tmp): raise ConfigError( f'Configured "sflow agent-address {tmp}" does not exist in the system!' ) # Check if at least one interface is configured if 'interface' not in sflow: raise ConfigError( 'sFlow requires at least one interface to be configured!') # Check if at least one server is configured if 'server' not in sflow: raise ConfigError('You need to configure at least one sFlow server!') # return True if all checks were passed return True def generate(sflow): if not sflow: return None render(hsflowd_conf_path, 'sflow/hsflowd.conf.j2', sflow) render(systemd_override, 'sflow/override.conf.j2', sflow) # Reload systemd manager configuration call('systemctl daemon-reload') def apply(sflow): if not sflow: # Stop flow-accounting daemon and remove configuration file call(f'systemctl stop {systemd_service}') if os.path.exists(hsflowd_conf_path): os.unlink(hsflowd_conf_path) return # Start/reload flow-accounting daemon call(f'systemctl restart {systemd_service}') if __name__ == '__main__': try: config = get_config() verify(config) generate(config) apply(config) except ConfigError as e: print(e) exit(1)