From 4ef110fd2c501b718344c72d495ad7e16d2bd465 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sat, 30 Dec 2023 23:25:20 +0100 Subject: T5474: establish common file name pattern for XML conf mode commands We will use _ as CLI level divider. The XML definition filename and also the Python helper should match the CLI node. Example: set interfaces ethernet -> interfaces_ethernet.xml.in set interfaces bond -> interfaces_bond.xml.in set service dhcp-server -> service_dhcp-server-xml.in --- smoketest/scripts/cli/test_ha_virtual_server.py | 152 ------------ smoketest/scripts/cli/test_ha_vrrp.py | 241 ------------------- .../cli/test_high-availability_virtual-server.py | 152 ++++++++++++ .../scripts/cli/test_high-availability_vrrp.py | 241 +++++++++++++++++++ .../scripts/cli/test_interfaces_pseudo-ethernet.py | 46 ++++ .../scripts/cli/test_interfaces_pseudo_ethernet.py | 46 ---- .../cli/test_interfaces_virtual-ethernet.py | 62 +++++ .../cli/test_interfaces_virtual_ethernet.py | 62 ----- .../cli/test_load-balancing_reverse-proxy.py | 114 +++++++++ smoketest/scripts/cli/test_load-balancing_wan.py | 259 +++++++++++++++++++++ .../cli/test_load_balancing_reverse_proxy.py | 114 --------- smoketest/scripts/cli/test_load_balancing_wan.py | 259 --------------------- .../scripts/cli/test_protocols_segment-routing.py | 112 +++++++++ .../scripts/cli/test_protocols_segment_routing.py | 112 --------- smoketest/scripts/cli/test_service_bcast-relay.py | 68 ------ .../scripts/cli/test_service_broadcast-relay.py | 68 ++++++ smoketest/scripts/cli/test_service_ids.py | 116 --------- .../cli/test_service_ids_ddos-protection.py | 116 +++++++++ .../scripts/cli/test_service_mdns-repeater.py | 134 ----------- .../scripts/cli/test_service_mdns_repeater.py | 134 +++++++++++ smoketest/scripts/cli/test_service_salt-minion.py | 105 +++++++++ smoketest/scripts/cli/test_service_salt.py | 105 --------- 22 files changed, 1409 insertions(+), 1409 deletions(-) delete mode 100755 smoketest/scripts/cli/test_ha_virtual_server.py delete mode 100755 smoketest/scripts/cli/test_ha_vrrp.py create mode 100755 smoketest/scripts/cli/test_high-availability_virtual-server.py create mode 100755 smoketest/scripts/cli/test_high-availability_vrrp.py create mode 100755 smoketest/scripts/cli/test_interfaces_pseudo-ethernet.py delete mode 100755 smoketest/scripts/cli/test_interfaces_pseudo_ethernet.py create mode 100755 smoketest/scripts/cli/test_interfaces_virtual-ethernet.py delete mode 100755 smoketest/scripts/cli/test_interfaces_virtual_ethernet.py create mode 100755 smoketest/scripts/cli/test_load-balancing_reverse-proxy.py create mode 100755 smoketest/scripts/cli/test_load-balancing_wan.py delete mode 100755 smoketest/scripts/cli/test_load_balancing_reverse_proxy.py delete mode 100755 smoketest/scripts/cli/test_load_balancing_wan.py create mode 100755 smoketest/scripts/cli/test_protocols_segment-routing.py delete mode 100755 smoketest/scripts/cli/test_protocols_segment_routing.py delete mode 100755 smoketest/scripts/cli/test_service_bcast-relay.py create mode 100755 smoketest/scripts/cli/test_service_broadcast-relay.py delete mode 100755 smoketest/scripts/cli/test_service_ids.py create mode 100755 smoketest/scripts/cli/test_service_ids_ddos-protection.py delete mode 100755 smoketest/scripts/cli/test_service_mdns-repeater.py create mode 100755 smoketest/scripts/cli/test_service_mdns_repeater.py create mode 100755 smoketest/scripts/cli/test_service_salt-minion.py delete mode 100755 smoketest/scripts/cli/test_service_salt.py (limited to 'smoketest/scripts') diff --git a/smoketest/scripts/cli/test_ha_virtual_server.py b/smoketest/scripts/cli/test_ha_virtual_server.py deleted file mode 100755 index 51ccfa4df..000000000 --- a/smoketest/scripts/cli/test_ha_virtual_server.py +++ /dev/null @@ -1,152 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2021-2022 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 . - -import unittest - -from base_vyostest_shim import VyOSUnitTestSHIM - -from vyos.configsession import ConfigSessionError -from vyos.ifconfig.vrrp import VRRP -from vyos.utils.process import cmd -from vyos.utils.process import process_named_running -from vyos.utils.file import read_file -from vyos.template import inc_ip - -PROCESS_NAME = 'keepalived' -KEEPALIVED_CONF = VRRP.location['config'] -base_path = ['high-availability'] -vrrp_interface = 'eth1' - -class TestHAVirtualServer(VyOSUnitTestSHIM.TestCase): - def tearDown(self): - # Check for running process - self.assertTrue(process_named_running(PROCESS_NAME)) - - self.cli_delete(['interfaces', 'ethernet', vrrp_interface, 'address']) - self.cli_delete(base_path) - self.cli_commit() - - # Process must be terminated after deleting the config - self.assertFalse(process_named_running(PROCESS_NAME)) - - def test_01_ha_virtual_server(self): - algo = 'least-connection' - delay = '10' - method = 'nat' - persistence_timeout = '600' - vs = 'serv-one' - vip = '203.0.113.111' - vport = '2222' - rservers = ['192.0.2.21', '192.0.2.22', '192.0.2.23'] - rport = '22' - proto = 'tcp' - connection_timeout = '30' - - vserver_base = base_path + ['virtual-server'] - - self.cli_set(vserver_base + [vs, 'address', vip]) - self.cli_set(vserver_base + [vs, 'algorithm', algo]) - self.cli_set(vserver_base + [vs, 'delay-loop', delay]) - self.cli_set(vserver_base + [vs, 'forward-method', method]) - self.cli_set(vserver_base + [vs, 'persistence-timeout', persistence_timeout]) - self.cli_set(vserver_base + [vs, 'port', vport]) - self.cli_set(vserver_base + [vs, 'protocol', proto]) - for rs in rservers: - self.cli_set(vserver_base + [vs, 'real-server', rs, 'connection-timeout', connection_timeout]) - self.cli_set(vserver_base + [vs, 'real-server', rs, 'port', rport]) - - # commit changes - self.cli_commit() - - config = read_file(KEEPALIVED_CONF) - - self.assertIn(f'virtual_server {vip} {vport}', config) - self.assertIn(f'delay_loop {delay}', config) - self.assertIn(f'lb_algo lc', config) - self.assertIn(f'lb_kind {method.upper()}', config) - self.assertIn(f'persistence_timeout {persistence_timeout}', config) - self.assertIn(f'protocol {proto.upper()}', config) - for rs in rservers: - self.assertIn(f'real_server {rs} {rport}', config) - self.assertIn(f'{proto.upper()}_CHECK', config) - self.assertIn(f'connect_timeout {connection_timeout}', config) - - def test_02_ha_virtual_server_and_vrrp(self): - algo = 'least-connection' - delay = '15' - method = 'nat' - persistence_timeout = '300' - vs = 'serv-two' - vip = '203.0.113.222' - vport = '22322' - rservers = ['192.0.2.11', '192.0.2.12'] - rport = '222' - proto = 'tcp' - connection_timeout = '23' - group = 'VyOS' - vrid = '99' - - vrrp_base = base_path + ['vrrp', 'group'] - vserver_base = base_path + ['virtual-server'] - - self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'address', '203.0.113.10/24']) - - # VRRP config - self.cli_set(vrrp_base + [group, 'description', group]) - self.cli_set(vrrp_base + [group, 'interface', vrrp_interface]) - self.cli_set(vrrp_base + [group, 'address', vip + '/24']) - self.cli_set(vrrp_base + [group, 'vrid', vrid]) - - # Virtual-server config - self.cli_set(vserver_base + [vs, 'address', vip]) - self.cli_set(vserver_base + [vs, 'algorithm', algo]) - self.cli_set(vserver_base + [vs, 'delay-loop', delay]) - self.cli_set(vserver_base + [vs, 'forward-method', method]) - self.cli_set(vserver_base + [vs, 'persistence-timeout', persistence_timeout]) - self.cli_set(vserver_base + [vs, 'port', vport]) - self.cli_set(vserver_base + [vs, 'protocol', proto]) - for rs in rservers: - self.cli_set(vserver_base + [vs, 'real-server', rs, 'connection-timeout', connection_timeout]) - self.cli_set(vserver_base + [vs, 'real-server', rs, 'port', rport]) - - # commit changes - self.cli_commit() - - config = read_file(KEEPALIVED_CONF) - - # Keepalived vrrp - self.assertIn(f'# {group}', config) - self.assertIn(f'interface {vrrp_interface}', config) - self.assertIn(f'virtual_router_id {vrid}', config) - self.assertIn(f'priority 100', config) # default value - self.assertIn(f'advert_int 1', config) # default value - self.assertIn(f'preempt_delay 0', config) # default value - - # Keepalived virtual-server - self.assertIn(f'virtual_server {vip} {vport}', config) - self.assertIn(f'delay_loop {delay}', config) - self.assertIn(f'lb_algo lc', config) - self.assertIn(f'lb_kind {method.upper()}', config) - self.assertIn(f'persistence_timeout {persistence_timeout}', config) - self.assertIn(f'protocol {proto.upper()}', config) - for rs in rservers: - self.assertIn(f'real_server {rs} {rport}', config) - self.assertIn(f'{proto.upper()}_CHECK', config) - self.assertIn(f'connect_timeout {connection_timeout}', config) - - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_ha_vrrp.py b/smoketest/scripts/cli/test_ha_vrrp.py deleted file mode 100755 index 98259d830..000000000 --- a/smoketest/scripts/cli/test_ha_vrrp.py +++ /dev/null @@ -1,241 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2021 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 . - -import unittest - -from base_vyostest_shim import VyOSUnitTestSHIM - -from vyos.configsession import ConfigSessionError -from vyos.ifconfig.vrrp import VRRP -from vyos.utils.process import cmd -from vyos.utils.process import process_named_running -from vyos.utils.file import read_file -from vyos.template import inc_ip - -PROCESS_NAME = 'keepalived' -KEEPALIVED_CONF = VRRP.location['config'] -base_path = ['high-availability'] - -vrrp_interface = 'eth1' -groups = ['VLAN77', 'VLAN78', 'VLAN201'] - -def getConfig(string, end='}'): - command = f'cat {KEEPALIVED_CONF} | sed -n "/^{string}/,/^{end}/p"' - out = cmd(command) - return out - -class TestVRRP(VyOSUnitTestSHIM.TestCase): - def tearDown(self): - # Check for running process - self.assertTrue(process_named_running(PROCESS_NAME)) - - for group in groups: - vlan_id = group.lstrip('VLAN') - self.cli_delete(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id]) - - self.cli_delete(base_path) - self.cli_commit() - - # Process must be terminated after deleting the config - self.assertFalse(process_named_running(PROCESS_NAME)) - - def test_01_default_values(self): - for group in groups: - vlan_id = group.lstrip('VLAN') - vip = f'100.64.{vlan_id}.1/24' - group_base = base_path + ['vrrp', 'group', group] - - self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', inc_ip(vip, 1) + '/' + vip.split('/')[-1]]) - - self.cli_set(group_base + ['description', group]) - self.cli_set(group_base + ['interface', f'{vrrp_interface}.{vlan_id}']) - self.cli_set(group_base + ['address', vip]) - self.cli_set(group_base + ['vrid', vlan_id]) - - # commit changes - self.cli_commit() - - for group in groups: - vlan_id = group.lstrip('VLAN') - vip = f'100.64.{vlan_id}.1/24' - - config = getConfig(f'vrrp_instance {group}') - - self.assertIn(f'# {group}', config) - self.assertIn(f'interface {vrrp_interface}.{vlan_id}', config) - self.assertIn(f'virtual_router_id {vlan_id}', config) - self.assertIn(f'priority 100', config) # default value - self.assertIn(f'advert_int 1', config) # default value - self.assertIn(f'preempt_delay 0', config) # default value - self.assertNotIn(f'use_vmac', config) - self.assertIn(f' {vip}', config) - - def test_02_simple_options(self): - advertise_interval = '77' - priority = '123' - preempt_delay = '400' - startup_delay = '120' - garp_master_delay = '2' - garp_master_repeat = '3' - garp_master_refresh = '4' - garp_master_refresh_repeat = '5' - garp_interval = '1.5' - group_garp_master_delay = '12' - group_garp_master_repeat = '13' - group_garp_master_refresh = '14' - vrrp_version = '3' - - for group in groups: - vlan_id = group.lstrip('VLAN') - vip = f'100.64.{vlan_id}.1/24' - group_base = base_path + ['vrrp', 'group', group] - global_param_base = base_path + ['vrrp', 'global-parameters'] - - self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', inc_ip(vip, 1) + '/' + vip.split('/')[-1]]) - - self.cli_set(group_base + ['description', group]) - self.cli_set(group_base + ['interface', f'{vrrp_interface}.{vlan_id}']) - self.cli_set(group_base + ['address', vip]) - self.cli_set(group_base + ['vrid', vlan_id]) - - self.cli_set(group_base + ['advertise-interval', advertise_interval]) - self.cli_set(group_base + ['priority', priority]) - self.cli_set(group_base + ['preempt-delay', preempt_delay]) - - self.cli_set(group_base + ['rfc3768-compatibility']) - - # Authentication - self.cli_set(group_base + ['authentication', 'type', 'plaintext-password']) - self.cli_set(group_base + ['authentication', 'password', f'{group}']) - - # GARP - self.cli_set(group_base + ['garp', 'master-delay', group_garp_master_delay]) - self.cli_set(group_base + ['garp', 'master-repeat', group_garp_master_repeat]) - self.cli_set(group_base + ['garp', 'master-refresh', group_garp_master_refresh]) - - # Global parameters - #config = getConfig(f'global_defs') - self.cli_set(global_param_base + ['startup-delay', f'{startup_delay}']) - self.cli_set(global_param_base + ['garp', 'interval', f'{garp_interval}']) - self.cli_set(global_param_base + ['garp', 'master-delay', f'{garp_master_delay}']) - self.cli_set(global_param_base + ['garp', 'master-repeat', f'{garp_master_repeat}']) - self.cli_set(global_param_base + ['garp', 'master-refresh', f'{garp_master_refresh}']) - self.cli_set(global_param_base + ['garp', 'master-refresh-repeat', f'{garp_master_refresh_repeat}']) - self.cli_set(global_param_base + ['version', vrrp_version]) - - # commit changes - self.cli_commit() - - # Check Global parameters - config = getConfig(f'global_defs') - self.assertIn(f'vrrp_startup_delay {startup_delay}', config) - self.assertIn(f'vrrp_garp_interval {garp_interval}', config) - self.assertIn(f'vrrp_garp_master_delay {garp_master_delay}', config) - self.assertIn(f'vrrp_garp_master_repeat {garp_master_repeat}', config) - self.assertIn(f'vrrp_garp_master_refresh {garp_master_refresh}', config) - self.assertIn(f'vrrp_garp_master_refresh_repeat {garp_master_refresh_repeat}', config) - self.assertIn(f'vrrp_version {vrrp_version}', config) - - for group in groups: - vlan_id = group.lstrip('VLAN') - vip = f'100.64.{vlan_id}.1/24' - - config = getConfig(f'vrrp_instance {group}') - self.assertIn(f'# {group}', config) - self.assertIn(f'state BACKUP', config) - self.assertIn(f'interface {vrrp_interface}.{vlan_id}', config) - self.assertIn(f'virtual_router_id {vlan_id}', config) - self.assertIn(f'priority {priority}', config) - self.assertIn(f'advert_int {advertise_interval}', config) - self.assertIn(f'preempt_delay {preempt_delay}', config) - self.assertIn(f'use_vmac {vrrp_interface}.{vlan_id}v{vlan_id}', config) - self.assertIn(f' {vip}', config) - - # Authentication - self.assertIn(f'auth_pass "{group}"', config) - self.assertIn(f'auth_type PASS', config) - - #GARP - self.assertIn(f'garp_master_delay {group_garp_master_delay}', config) - self.assertIn(f'garp_master_refresh {group_garp_master_refresh}', config) - self.assertIn(f'garp_master_repeat {group_garp_master_repeat}', config) - - def test_03_sync_group(self): - sync_group = 'VyOS' - - for group in groups: - vlan_id = group.lstrip('VLAN') - vip = f'100.64.{vlan_id}.1/24' - group_base = base_path + ['vrrp', 'group', group] - - self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', inc_ip(vip, 1) + '/' + vip.split('/')[-1]]) - - self.cli_set(group_base + ['interface', f'{vrrp_interface}.{vlan_id}']) - self.cli_set(group_base + ['address', vip]) - self.cli_set(group_base + ['vrid', vlan_id]) - - self.cli_set(base_path + ['vrrp', 'sync-group', sync_group, 'member', group]) - - # commit changes - self.cli_commit() - - for group in groups: - vlan_id = group.lstrip('VLAN') - vip = f'100.64.{vlan_id}.1/24' - config = getConfig(f'vrrp_instance {group}') - - self.assertIn(f'interface {vrrp_interface}.{vlan_id}', config) - self.assertIn(f'virtual_router_id {vlan_id}', config) - self.assertNotIn(f'use_vmac', config) - self.assertIn(f' {vip}', config) - - config = getConfig(f'vrrp_sync_group {sync_group}') - self.assertIn(r'group {', config) - for group in groups: - self.assertIn(f'{group}', config) - - def test_04_exclude_vrrp_interface(self): - group = 'VyOS-WAN' - none_vrrp_interface = 'eth2' - vlan_id = '24' - vip = '100.64.24.1/24' - vip_dev = '192.0.2.2/24' - vrid = '150' - group_base = base_path + ['vrrp', 'group', group] - - self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', '100.64.24.11/24']) - self.cli_set(group_base + ['interface', f'{vrrp_interface}.{vlan_id}']) - self.cli_set(group_base + ['address', vip]) - self.cli_set(group_base + ['address', vip_dev, 'interface', none_vrrp_interface]) - self.cli_set(group_base + ['track', 'exclude-vrrp-interface']) - self.cli_set(group_base + ['track', 'interface', none_vrrp_interface]) - self.cli_set(group_base + ['vrid', vrid]) - - # commit changes - self.cli_commit() - - config = getConfig(f'vrrp_instance {group}') - - self.assertIn(f'interface {vrrp_interface}.{vlan_id}', config) - self.assertIn(f'virtual_router_id {vrid}', config) - self.assertIn(f'dont_track_primary', config) - self.assertIn(f' {vip}', config) - self.assertIn(f' {vip_dev} dev {none_vrrp_interface}', config) - self.assertIn(f'track_interface', config) - self.assertIn(f' {none_vrrp_interface}', config) - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_high-availability_virtual-server.py b/smoketest/scripts/cli/test_high-availability_virtual-server.py new file mode 100755 index 000000000..51ccfa4df --- /dev/null +++ b/smoketest/scripts/cli/test_high-availability_virtual-server.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-2022 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 . + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.ifconfig.vrrp import VRRP +from vyos.utils.process import cmd +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file +from vyos.template import inc_ip + +PROCESS_NAME = 'keepalived' +KEEPALIVED_CONF = VRRP.location['config'] +base_path = ['high-availability'] +vrrp_interface = 'eth1' + +class TestHAVirtualServer(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + self.cli_delete(['interfaces', 'ethernet', vrrp_interface, 'address']) + self.cli_delete(base_path) + self.cli_commit() + + # Process must be terminated after deleting the config + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_01_ha_virtual_server(self): + algo = 'least-connection' + delay = '10' + method = 'nat' + persistence_timeout = '600' + vs = 'serv-one' + vip = '203.0.113.111' + vport = '2222' + rservers = ['192.0.2.21', '192.0.2.22', '192.0.2.23'] + rport = '22' + proto = 'tcp' + connection_timeout = '30' + + vserver_base = base_path + ['virtual-server'] + + self.cli_set(vserver_base + [vs, 'address', vip]) + self.cli_set(vserver_base + [vs, 'algorithm', algo]) + self.cli_set(vserver_base + [vs, 'delay-loop', delay]) + self.cli_set(vserver_base + [vs, 'forward-method', method]) + self.cli_set(vserver_base + [vs, 'persistence-timeout', persistence_timeout]) + self.cli_set(vserver_base + [vs, 'port', vport]) + self.cli_set(vserver_base + [vs, 'protocol', proto]) + for rs in rservers: + self.cli_set(vserver_base + [vs, 'real-server', rs, 'connection-timeout', connection_timeout]) + self.cli_set(vserver_base + [vs, 'real-server', rs, 'port', rport]) + + # commit changes + self.cli_commit() + + config = read_file(KEEPALIVED_CONF) + + self.assertIn(f'virtual_server {vip} {vport}', config) + self.assertIn(f'delay_loop {delay}', config) + self.assertIn(f'lb_algo lc', config) + self.assertIn(f'lb_kind {method.upper()}', config) + self.assertIn(f'persistence_timeout {persistence_timeout}', config) + self.assertIn(f'protocol {proto.upper()}', config) + for rs in rservers: + self.assertIn(f'real_server {rs} {rport}', config) + self.assertIn(f'{proto.upper()}_CHECK', config) + self.assertIn(f'connect_timeout {connection_timeout}', config) + + def test_02_ha_virtual_server_and_vrrp(self): + algo = 'least-connection' + delay = '15' + method = 'nat' + persistence_timeout = '300' + vs = 'serv-two' + vip = '203.0.113.222' + vport = '22322' + rservers = ['192.0.2.11', '192.0.2.12'] + rport = '222' + proto = 'tcp' + connection_timeout = '23' + group = 'VyOS' + vrid = '99' + + vrrp_base = base_path + ['vrrp', 'group'] + vserver_base = base_path + ['virtual-server'] + + self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'address', '203.0.113.10/24']) + + # VRRP config + self.cli_set(vrrp_base + [group, 'description', group]) + self.cli_set(vrrp_base + [group, 'interface', vrrp_interface]) + self.cli_set(vrrp_base + [group, 'address', vip + '/24']) + self.cli_set(vrrp_base + [group, 'vrid', vrid]) + + # Virtual-server config + self.cli_set(vserver_base + [vs, 'address', vip]) + self.cli_set(vserver_base + [vs, 'algorithm', algo]) + self.cli_set(vserver_base + [vs, 'delay-loop', delay]) + self.cli_set(vserver_base + [vs, 'forward-method', method]) + self.cli_set(vserver_base + [vs, 'persistence-timeout', persistence_timeout]) + self.cli_set(vserver_base + [vs, 'port', vport]) + self.cli_set(vserver_base + [vs, 'protocol', proto]) + for rs in rservers: + self.cli_set(vserver_base + [vs, 'real-server', rs, 'connection-timeout', connection_timeout]) + self.cli_set(vserver_base + [vs, 'real-server', rs, 'port', rport]) + + # commit changes + self.cli_commit() + + config = read_file(KEEPALIVED_CONF) + + # Keepalived vrrp + self.assertIn(f'# {group}', config) + self.assertIn(f'interface {vrrp_interface}', config) + self.assertIn(f'virtual_router_id {vrid}', config) + self.assertIn(f'priority 100', config) # default value + self.assertIn(f'advert_int 1', config) # default value + self.assertIn(f'preempt_delay 0', config) # default value + + # Keepalived virtual-server + self.assertIn(f'virtual_server {vip} {vport}', config) + self.assertIn(f'delay_loop {delay}', config) + self.assertIn(f'lb_algo lc', config) + self.assertIn(f'lb_kind {method.upper()}', config) + self.assertIn(f'persistence_timeout {persistence_timeout}', config) + self.assertIn(f'protocol {proto.upper()}', config) + for rs in rservers: + self.assertIn(f'real_server {rs} {rport}', config) + self.assertIn(f'{proto.upper()}_CHECK', config) + self.assertIn(f'connect_timeout {connection_timeout}', config) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_high-availability_vrrp.py b/smoketest/scripts/cli/test_high-availability_vrrp.py new file mode 100755 index 000000000..98259d830 --- /dev/null +++ b/smoketest/scripts/cli/test_high-availability_vrrp.py @@ -0,0 +1,241 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 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 . + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.ifconfig.vrrp import VRRP +from vyos.utils.process import cmd +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file +from vyos.template import inc_ip + +PROCESS_NAME = 'keepalived' +KEEPALIVED_CONF = VRRP.location['config'] +base_path = ['high-availability'] + +vrrp_interface = 'eth1' +groups = ['VLAN77', 'VLAN78', 'VLAN201'] + +def getConfig(string, end='}'): + command = f'cat {KEEPALIVED_CONF} | sed -n "/^{string}/,/^{end}/p"' + out = cmd(command) + return out + +class TestVRRP(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + for group in groups: + vlan_id = group.lstrip('VLAN') + self.cli_delete(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id]) + + self.cli_delete(base_path) + self.cli_commit() + + # Process must be terminated after deleting the config + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_01_default_values(self): + for group in groups: + vlan_id = group.lstrip('VLAN') + vip = f'100.64.{vlan_id}.1/24' + group_base = base_path + ['vrrp', 'group', group] + + self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', inc_ip(vip, 1) + '/' + vip.split('/')[-1]]) + + self.cli_set(group_base + ['description', group]) + self.cli_set(group_base + ['interface', f'{vrrp_interface}.{vlan_id}']) + self.cli_set(group_base + ['address', vip]) + self.cli_set(group_base + ['vrid', vlan_id]) + + # commit changes + self.cli_commit() + + for group in groups: + vlan_id = group.lstrip('VLAN') + vip = f'100.64.{vlan_id}.1/24' + + config = getConfig(f'vrrp_instance {group}') + + self.assertIn(f'# {group}', config) + self.assertIn(f'interface {vrrp_interface}.{vlan_id}', config) + self.assertIn(f'virtual_router_id {vlan_id}', config) + self.assertIn(f'priority 100', config) # default value + self.assertIn(f'advert_int 1', config) # default value + self.assertIn(f'preempt_delay 0', config) # default value + self.assertNotIn(f'use_vmac', config) + self.assertIn(f' {vip}', config) + + def test_02_simple_options(self): + advertise_interval = '77' + priority = '123' + preempt_delay = '400' + startup_delay = '120' + garp_master_delay = '2' + garp_master_repeat = '3' + garp_master_refresh = '4' + garp_master_refresh_repeat = '5' + garp_interval = '1.5' + group_garp_master_delay = '12' + group_garp_master_repeat = '13' + group_garp_master_refresh = '14' + vrrp_version = '3' + + for group in groups: + vlan_id = group.lstrip('VLAN') + vip = f'100.64.{vlan_id}.1/24' + group_base = base_path + ['vrrp', 'group', group] + global_param_base = base_path + ['vrrp', 'global-parameters'] + + self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', inc_ip(vip, 1) + '/' + vip.split('/')[-1]]) + + self.cli_set(group_base + ['description', group]) + self.cli_set(group_base + ['interface', f'{vrrp_interface}.{vlan_id}']) + self.cli_set(group_base + ['address', vip]) + self.cli_set(group_base + ['vrid', vlan_id]) + + self.cli_set(group_base + ['advertise-interval', advertise_interval]) + self.cli_set(group_base + ['priority', priority]) + self.cli_set(group_base + ['preempt-delay', preempt_delay]) + + self.cli_set(group_base + ['rfc3768-compatibility']) + + # Authentication + self.cli_set(group_base + ['authentication', 'type', 'plaintext-password']) + self.cli_set(group_base + ['authentication', 'password', f'{group}']) + + # GARP + self.cli_set(group_base + ['garp', 'master-delay', group_garp_master_delay]) + self.cli_set(group_base + ['garp', 'master-repeat', group_garp_master_repeat]) + self.cli_set(group_base + ['garp', 'master-refresh', group_garp_master_refresh]) + + # Global parameters + #config = getConfig(f'global_defs') + self.cli_set(global_param_base + ['startup-delay', f'{startup_delay}']) + self.cli_set(global_param_base + ['garp', 'interval', f'{garp_interval}']) + self.cli_set(global_param_base + ['garp', 'master-delay', f'{garp_master_delay}']) + self.cli_set(global_param_base + ['garp', 'master-repeat', f'{garp_master_repeat}']) + self.cli_set(global_param_base + ['garp', 'master-refresh', f'{garp_master_refresh}']) + self.cli_set(global_param_base + ['garp', 'master-refresh-repeat', f'{garp_master_refresh_repeat}']) + self.cli_set(global_param_base + ['version', vrrp_version]) + + # commit changes + self.cli_commit() + + # Check Global parameters + config = getConfig(f'global_defs') + self.assertIn(f'vrrp_startup_delay {startup_delay}', config) + self.assertIn(f'vrrp_garp_interval {garp_interval}', config) + self.assertIn(f'vrrp_garp_master_delay {garp_master_delay}', config) + self.assertIn(f'vrrp_garp_master_repeat {garp_master_repeat}', config) + self.assertIn(f'vrrp_garp_master_refresh {garp_master_refresh}', config) + self.assertIn(f'vrrp_garp_master_refresh_repeat {garp_master_refresh_repeat}', config) + self.assertIn(f'vrrp_version {vrrp_version}', config) + + for group in groups: + vlan_id = group.lstrip('VLAN') + vip = f'100.64.{vlan_id}.1/24' + + config = getConfig(f'vrrp_instance {group}') + self.assertIn(f'# {group}', config) + self.assertIn(f'state BACKUP', config) + self.assertIn(f'interface {vrrp_interface}.{vlan_id}', config) + self.assertIn(f'virtual_router_id {vlan_id}', config) + self.assertIn(f'priority {priority}', config) + self.assertIn(f'advert_int {advertise_interval}', config) + self.assertIn(f'preempt_delay {preempt_delay}', config) + self.assertIn(f'use_vmac {vrrp_interface}.{vlan_id}v{vlan_id}', config) + self.assertIn(f' {vip}', config) + + # Authentication + self.assertIn(f'auth_pass "{group}"', config) + self.assertIn(f'auth_type PASS', config) + + #GARP + self.assertIn(f'garp_master_delay {group_garp_master_delay}', config) + self.assertIn(f'garp_master_refresh {group_garp_master_refresh}', config) + self.assertIn(f'garp_master_repeat {group_garp_master_repeat}', config) + + def test_03_sync_group(self): + sync_group = 'VyOS' + + for group in groups: + vlan_id = group.lstrip('VLAN') + vip = f'100.64.{vlan_id}.1/24' + group_base = base_path + ['vrrp', 'group', group] + + self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', inc_ip(vip, 1) + '/' + vip.split('/')[-1]]) + + self.cli_set(group_base + ['interface', f'{vrrp_interface}.{vlan_id}']) + self.cli_set(group_base + ['address', vip]) + self.cli_set(group_base + ['vrid', vlan_id]) + + self.cli_set(base_path + ['vrrp', 'sync-group', sync_group, 'member', group]) + + # commit changes + self.cli_commit() + + for group in groups: + vlan_id = group.lstrip('VLAN') + vip = f'100.64.{vlan_id}.1/24' + config = getConfig(f'vrrp_instance {group}') + + self.assertIn(f'interface {vrrp_interface}.{vlan_id}', config) + self.assertIn(f'virtual_router_id {vlan_id}', config) + self.assertNotIn(f'use_vmac', config) + self.assertIn(f' {vip}', config) + + config = getConfig(f'vrrp_sync_group {sync_group}') + self.assertIn(r'group {', config) + for group in groups: + self.assertIn(f'{group}', config) + + def test_04_exclude_vrrp_interface(self): + group = 'VyOS-WAN' + none_vrrp_interface = 'eth2' + vlan_id = '24' + vip = '100.64.24.1/24' + vip_dev = '192.0.2.2/24' + vrid = '150' + group_base = base_path + ['vrrp', 'group', group] + + self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', '100.64.24.11/24']) + self.cli_set(group_base + ['interface', f'{vrrp_interface}.{vlan_id}']) + self.cli_set(group_base + ['address', vip]) + self.cli_set(group_base + ['address', vip_dev, 'interface', none_vrrp_interface]) + self.cli_set(group_base + ['track', 'exclude-vrrp-interface']) + self.cli_set(group_base + ['track', 'interface', none_vrrp_interface]) + self.cli_set(group_base + ['vrid', vrid]) + + # commit changes + self.cli_commit() + + config = getConfig(f'vrrp_instance {group}') + + self.assertIn(f'interface {vrrp_interface}.{vlan_id}', config) + self.assertIn(f'virtual_router_id {vrid}', config) + self.assertIn(f'dont_track_primary', config) + self.assertIn(f' {vip}', config) + self.assertIn(f' {vip_dev} dev {none_vrrp_interface}', config) + self.assertIn(f'track_interface', config) + self.assertIn(f' {none_vrrp_interface}', config) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_pseudo-ethernet.py b/smoketest/scripts/cli/test_interfaces_pseudo-ethernet.py new file mode 100755 index 000000000..0d6f5bc1f --- /dev/null +++ b/smoketest/scripts/cli/test_interfaces_pseudo-ethernet.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-2022 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 . + +import os +import unittest + +from vyos.ifconfig import Section +from base_interfaces_test import BasicInterfaceTest + +class PEthInterfaceTest(BasicInterfaceTest.TestCase): + @classmethod + def setUpClass(cls): + cls._base_path = ['interfaces', 'pseudo-ethernet'] + + cls._options = {} + # we need to filter out VLAN interfaces identified by a dot (.) + # in their name - just in case! + if 'TEST_ETH' in os.environ: + for tmp in os.environ['TEST_ETH'].split(): + cls._options.update({f'p{tmp}' : [f'source-interface {tmp}']}) + + else: + for tmp in Section.interfaces('ethernet'): + if '.' in tmp: + continue + cls._options.update({f'p{tmp}' : [f'source-interface {tmp}']}) + + cls._interfaces = list(cls._options) + # call base-classes classmethod + super(PEthInterfaceTest, cls).setUpClass() + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_pseudo_ethernet.py b/smoketest/scripts/cli/test_interfaces_pseudo_ethernet.py deleted file mode 100755 index 0d6f5bc1f..000000000 --- a/smoketest/scripts/cli/test_interfaces_pseudo_ethernet.py +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2020-2022 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 . - -import os -import unittest - -from vyos.ifconfig import Section -from base_interfaces_test import BasicInterfaceTest - -class PEthInterfaceTest(BasicInterfaceTest.TestCase): - @classmethod - def setUpClass(cls): - cls._base_path = ['interfaces', 'pseudo-ethernet'] - - cls._options = {} - # we need to filter out VLAN interfaces identified by a dot (.) - # in their name - just in case! - if 'TEST_ETH' in os.environ: - for tmp in os.environ['TEST_ETH'].split(): - cls._options.update({f'p{tmp}' : [f'source-interface {tmp}']}) - - else: - for tmp in Section.interfaces('ethernet'): - if '.' in tmp: - continue - cls._options.update({f'p{tmp}' : [f'source-interface {tmp}']}) - - cls._interfaces = list(cls._options) - # call base-classes classmethod - super(PEthInterfaceTest, cls).setUpClass() - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_virtual-ethernet.py b/smoketest/scripts/cli/test_interfaces_virtual-ethernet.py new file mode 100755 index 000000000..7874589ca --- /dev/null +++ b/smoketest/scripts/cli/test_interfaces_virtual-ethernet.py @@ -0,0 +1,62 @@ +#!/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 . + +import unittest + +from netifaces import interfaces + +from vyos.ifconfig import Section +from vyos.utils.process import process_named_running +from base_interfaces_test import BasicInterfaceTest + +class VEthInterfaceTest(BasicInterfaceTest.TestCase): + @classmethod + def setUpClass(cls): + cls._base_path = ['interfaces', 'virtual-ethernet'] + cls._options = { + 'veth0': ['peer-name veth1'], + 'veth1': ['peer-name veth0'], + } + + cls._interfaces = list(cls._options) + # call base-classes classmethod + super(VEthInterfaceTest, cls).setUpClass() + + def test_vif_8021q_mtu_limits(self): + self.skipTest('not supported') + + # As we always need a pair of veth interfaces, we can not rely on the base + # class check to determine if there is a dhcp6c or dhclient instance running. + # This test will always fail as there is an instance running on the peer + # interface. + def tearDown(self): + self.cli_delete(self._base_path) + self.cli_commit() + + # Verify that no previously interface remained on the system + for intf in self._interfaces: + self.assertNotIn(intf, interfaces()) + + @classmethod + def tearDownClass(cls): + # No daemon started during tests should remain running + for daemon in ['dhcp6c', 'dhclient']: + cls.assertFalse(cls, process_named_running(daemon)) + + super(VEthInterfaceTest, cls).tearDownClass() + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_virtual_ethernet.py b/smoketest/scripts/cli/test_interfaces_virtual_ethernet.py deleted file mode 100755 index 7874589ca..000000000 --- a/smoketest/scripts/cli/test_interfaces_virtual_ethernet.py +++ /dev/null @@ -1,62 +0,0 @@ -#!/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 . - -import unittest - -from netifaces import interfaces - -from vyos.ifconfig import Section -from vyos.utils.process import process_named_running -from base_interfaces_test import BasicInterfaceTest - -class VEthInterfaceTest(BasicInterfaceTest.TestCase): - @classmethod - def setUpClass(cls): - cls._base_path = ['interfaces', 'virtual-ethernet'] - cls._options = { - 'veth0': ['peer-name veth1'], - 'veth1': ['peer-name veth0'], - } - - cls._interfaces = list(cls._options) - # call base-classes classmethod - super(VEthInterfaceTest, cls).setUpClass() - - def test_vif_8021q_mtu_limits(self): - self.skipTest('not supported') - - # As we always need a pair of veth interfaces, we can not rely on the base - # class check to determine if there is a dhcp6c or dhclient instance running. - # This test will always fail as there is an instance running on the peer - # interface. - def tearDown(self): - self.cli_delete(self._base_path) - self.cli_commit() - - # Verify that no previously interface remained on the system - for intf in self._interfaces: - self.assertNotIn(intf, interfaces()) - - @classmethod - def tearDownClass(cls): - # No daemon started during tests should remain running - for daemon in ['dhcp6c', 'dhclient']: - cls.assertFalse(cls, process_named_running(daemon)) - - super(VEthInterfaceTest, cls).tearDownClass() - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_load-balancing_reverse-proxy.py b/smoketest/scripts/cli/test_load-balancing_reverse-proxy.py new file mode 100755 index 000000000..274b97f22 --- /dev/null +++ b/smoketest/scripts/cli/test_load-balancing_reverse-proxy.py @@ -0,0 +1,114 @@ +#!/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 . + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file + +PROCESS_NAME = 'haproxy' +HAPROXY_CONF = '/run/haproxy/haproxy.cfg' +base_path = ['load-balancing', 'reverse-proxy'] +proxy_interface = 'eth1' + + +class TestLoadBalancingReverseProxy(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + self.cli_delete(['interfaces', 'ethernet', proxy_interface, 'address']) + self.cli_delete(base_path) + self.cli_commit() + + # Process must be terminated after deleting the config + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_01_lb_reverse_proxy_domain(self): + domains_bk_first = ['n1.example.com', 'n2.example.com', 'n3.example.com'] + domain_bk_second = 'n5.example.com' + frontend = 'https_front' + front_port = '4433' + bk_server_first = '192.0.2.11' + bk_server_second = '192.0.2.12' + bk_first_name = 'bk-01' + bk_second_name = 'bk-02' + bk_server_port = '9090' + mode = 'http' + rule_ten = '10' + rule_twenty = '20' + send_proxy = 'send-proxy' + max_connections = '1000' + + back_base = base_path + ['backend'] + + self.cli_set(base_path + ['service', frontend, 'mode', mode]) + self.cli_set(base_path + ['service', frontend, 'port', front_port]) + for domain in domains_bk_first: + self.cli_set(base_path + ['service', frontend, 'rule', rule_ten, 'domain-name', domain]) + self.cli_set(base_path + ['service', frontend, 'rule', rule_ten, 'set', 'backend', bk_first_name]) + self.cli_set(base_path + ['service', frontend, 'rule', rule_twenty, 'domain-name', domain_bk_second]) + self.cli_set(base_path + ['service', frontend, 'rule', rule_twenty, 'set', 'backend', bk_second_name]) + + self.cli_set(back_base + [bk_first_name, 'mode', mode]) + self.cli_set(back_base + [bk_first_name, 'server', bk_first_name, 'address', bk_server_first]) + self.cli_set(back_base + [bk_first_name, 'server', bk_first_name, 'port', bk_server_port]) + self.cli_set(back_base + [bk_first_name, 'server', bk_first_name, send_proxy]) + + self.cli_set(back_base + [bk_second_name, 'mode', mode]) + self.cli_set(back_base + [bk_second_name, 'server', bk_second_name, 'address', bk_server_second]) + self.cli_set(back_base + [bk_second_name, 'server', bk_second_name, 'port', bk_server_port]) + self.cli_set(back_base + [bk_second_name, 'server', bk_second_name, 'backup']) + + self.cli_set(base_path + ['global-parameters', 'max-connections', max_connections]) + + # commit changes + self.cli_commit() + + config = read_file(HAPROXY_CONF) + + # Global + self.assertIn(f'maxconn {max_connections}', config) + + # Frontend + self.assertIn(f'frontend {frontend}', config) + self.assertIn(f'bind :::{front_port} v4v6', config) + self.assertIn(f'mode {mode}', config) + for domain in domains_bk_first: + self.assertIn(f'acl {rule_ten} hdr(host) -i {domain}', config) + self.assertIn(f'use_backend {bk_first_name} if {rule_ten}', config) + self.assertIn(f'acl {rule_twenty} hdr(host) -i {domain_bk_second}', config) + self.assertIn(f'use_backend {bk_second_name} if {rule_twenty}', config) + + # Backend + self.assertIn(f'backend {bk_first_name}', config) + self.assertIn(f'balance roundrobin', config) + self.assertIn(f'option forwardfor', config) + self.assertIn('http-request add-header X-Forwarded-Proto https if { ssl_fc }', config) + self.assertIn(f'mode {mode}', config) + self.assertIn(f'server {bk_first_name} {bk_server_first}:{bk_server_port} send-proxy', config) + + self.assertIn(f'backend {bk_second_name}', config) + self.assertIn(f'mode {mode}', config) + self.assertIn(f'server {bk_second_name} {bk_server_second}:{bk_server_port}', config) + self.assertIn(f'server {bk_second_name} {bk_server_second}:{bk_server_port} backup', config) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_load-balancing_wan.py b/smoketest/scripts/cli/test_load-balancing_wan.py new file mode 100755 index 000000000..47ca19b27 --- /dev/null +++ b/smoketest/scripts/cli/test_load-balancing_wan.py @@ -0,0 +1,259 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-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 . + +import os +import unittest +import time + +from base_vyostest_shim import VyOSUnitTestSHIM +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Section +from vyos.utils.process import call +from vyos.utils.process import cmd + + +base_path = ['load-balancing'] + + +def create_netns(name): + return call(f'sudo ip netns add {name}') + +def create_veth_pair(local='veth0', peer='ceth0'): + return call(f'sudo ip link add {local} type veth peer name {peer}') + +def move_interface_to_netns(iface, netns_name): + return call(f'sudo ip link set {iface} netns {netns_name}') + +def rename_interface(iface, new_name): + return call(f'sudo ip link set {iface} name {new_name}') + +def cmd_in_netns(netns, cmd): + return call(f'sudo ip netns exec {netns} {cmd}') + +def delete_netns(name): + return call(f'sudo ip netns del {name}') + +class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestLoadBalancingWan, 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): + self.cli_delete(base_path) + self.cli_commit() + + def test_table_routes(self): + ns1 = 'ns201' + ns2 = 'ns202' + ns3 = 'ns203' + iface1 = 'eth201' + iface2 = 'eth202' + iface3 = 'eth203' + container_iface1 = 'ceth0' + container_iface2 = 'ceth1' + container_iface3 = 'ceth2' + + # Create network namespeces + create_netns(ns1) + create_netns(ns2) + create_netns(ns3) + create_veth_pair(iface1, container_iface1) + create_veth_pair(iface2, container_iface2) + create_veth_pair(iface3, container_iface3) + + move_interface_to_netns(container_iface1, ns1) + move_interface_to_netns(container_iface2, ns2) + move_interface_to_netns(container_iface3, ns3) + call(f'sudo ip address add 203.0.113.10/24 dev {iface1}') + call(f'sudo ip address add 192.0.2.10/24 dev {iface2}') + call(f'sudo ip address add 198.51.100.10/24 dev {iface3}') + call(f'sudo ip link set dev {iface1} up') + call(f'sudo ip link set dev {iface2} up') + call(f'sudo ip link set dev {iface3} up') + cmd_in_netns(ns1, f'ip link set {container_iface1} name eth0') + cmd_in_netns(ns2, f'ip link set {container_iface2} name eth0') + cmd_in_netns(ns3, f'ip link set {container_iface3} name eth0') + cmd_in_netns(ns1, 'ip address add 203.0.113.1/24 dev eth0') + cmd_in_netns(ns2, 'ip address add 192.0.2.1/24 dev eth0') + cmd_in_netns(ns3, 'ip address add 198.51.100.1/24 dev eth0') + cmd_in_netns(ns1, 'ip link set dev eth0 up') + cmd_in_netns(ns2, 'ip link set dev eth0 up') + cmd_in_netns(ns3, 'ip link set dev eth0 up') + + # Set load-balancing configuration + self.cli_set(base_path + ['wan', 'interface-health', iface1, 'failure-count', '2']) + self.cli_set(base_path + ['wan', 'interface-health', iface1, 'nexthop', '203.0.113.1']) + self.cli_set(base_path + ['wan', 'interface-health', iface1, 'success-count', '1']) + self.cli_set(base_path + ['wan', 'interface-health', iface2, 'failure-count', '2']) + self.cli_set(base_path + ['wan', 'interface-health', iface2, 'nexthop', '192.0.2.1']) + self.cli_set(base_path + ['wan', 'interface-health', iface2, 'success-count', '1']) + + self.cli_set(base_path + ['wan', 'rule', '10', 'inbound-interface', iface3]) + self.cli_set(base_path + ['wan', 'rule', '10', 'source', 'address', '198.51.100.0/24']) + + + # commit changes + self.cli_commit() + + time.sleep(5) + # Check default routes in tables 201, 202 + # Expected values + original = 'default via 203.0.113.1 dev eth201' + tmp = cmd('sudo ip route show table 201') + self.assertEqual(tmp, original) + + original = 'default via 192.0.2.1 dev eth202' + tmp = cmd('sudo ip route show table 202') + self.assertEqual(tmp, original) + + # Delete veth interfaces and netns + for iface in [iface1, iface2, iface3]: + call(f'sudo ip link del dev {iface}') + + delete_netns(ns1) + delete_netns(ns2) + delete_netns(ns3) + + def test_check_chains(self): + + ns1 = 'nsA' + ns2 = 'nsB' + ns3 = 'nsC' + iface1 = 'veth1' + iface2 = 'veth2' + iface3 = 'veth3' + container_iface1 = 'ceth0' + container_iface2 = 'ceth1' + container_iface3 = 'ceth2' + mangle_isp1 = """table ip mangle { + chain ISP_veth1 { + counter ct mark set 0xc9 + counter meta mark set 0xc9 + counter accept + } +}""" + mangle_isp2 = """table ip mangle { + chain ISP_veth2 { + counter ct mark set 0xca + counter meta mark set 0xca + counter accept + } +}""" + mangle_prerouting = """table ip mangle { + chain PREROUTING { + type filter hook prerouting priority mangle; policy accept; + counter jump WANLOADBALANCE_PRE + } +}""" + mangle_wanloadbalance_pre = """table ip mangle { + chain WANLOADBALANCE_PRE { + iifname "veth3" ip saddr 198.51.100.0/24 ct state new meta random & 2147483647 < 1073741824 counter jump ISP_veth1 + iifname "veth3" ip saddr 198.51.100.0/24 ct state new counter jump ISP_veth2 + iifname "veth3" ip saddr 198.51.100.0/24 counter meta mark set ct mark + } +}""" + nat_wanloadbalance = """table ip nat { + chain WANLOADBALANCE { + ct mark 0xc9 counter snat to 203.0.113.10 + ct mark 0xca counter snat to 192.0.2.10 + } +}""" + nat_vyos_pre_snat_hook = """table ip nat { + chain VYOS_PRE_SNAT_HOOK { + type nat hook postrouting priority srcnat - 1; policy accept; + counter jump WANLOADBALANCE + } +}""" + + # Create network namespeces + create_netns(ns1) + create_netns(ns2) + create_netns(ns3) + create_veth_pair(iface1, container_iface1) + create_veth_pair(iface2, container_iface2) + create_veth_pair(iface3, container_iface3) + move_interface_to_netns(container_iface1, ns1) + move_interface_to_netns(container_iface2, ns2) + move_interface_to_netns(container_iface3, ns3) + call(f'sudo ip address add 203.0.113.10/24 dev {iface1}') + call(f'sudo ip address add 192.0.2.10/24 dev {iface2}') + call(f'sudo ip address add 198.51.100.10/24 dev {iface3}') + + for iface in [iface1, iface2, iface3]: + call(f'sudo ip link set dev {iface} up') + + cmd_in_netns(ns1, f'ip link set {container_iface1} name eth0') + cmd_in_netns(ns2, f'ip link set {container_iface2} name eth0') + cmd_in_netns(ns3, f'ip link set {container_iface3} name eth0') + cmd_in_netns(ns1, 'ip address add 203.0.113.1/24 dev eth0') + cmd_in_netns(ns2, 'ip address add 192.0.2.1/24 dev eth0') + cmd_in_netns(ns3, 'ip address add 198.51.100.1/24 dev eth0') + cmd_in_netns(ns1, 'ip link set dev eth0 up') + cmd_in_netns(ns2, 'ip link set dev eth0 up') + cmd_in_netns(ns3, 'ip link set dev eth0 up') + + # Set load-balancing configuration + self.cli_set(base_path + ['wan', 'interface-health', iface1, 'failure-count', '2']) + self.cli_set(base_path + ['wan', 'interface-health', iface1, 'nexthop', '203.0.113.1']) + self.cli_set(base_path + ['wan', 'interface-health', iface1, 'success-count', '1']) + self.cli_set(base_path + ['wan', 'interface-health', iface2, 'failure-count', '2']) + self.cli_set(base_path + ['wan', 'interface-health', iface2, 'nexthop', '192.0.2.1']) + self.cli_set(base_path + ['wan', 'interface-health', iface2, 'success-count', '1']) + self.cli_set(base_path + ['wan', 'rule', '10', 'inbound-interface', iface3]) + self.cli_set(base_path + ['wan', 'rule', '10', 'source', 'address', '198.51.100.0/24']) + self.cli_set(base_path + ['wan', 'rule', '10', 'interface', iface1]) + self.cli_set(base_path + ['wan', 'rule', '10', 'interface', iface2]) + + # commit changes + self.cli_commit() + + time.sleep(5) + + # Check mangle chains + tmp = cmd(f'sudo nft -s list chain mangle ISP_{iface1}') + self.assertEqual(tmp, mangle_isp1) + + tmp = cmd(f'sudo nft -s list chain mangle ISP_{iface2}') + self.assertEqual(tmp, mangle_isp2) + + tmp = cmd(f'sudo nft -s list chain mangle PREROUTING') + self.assertEqual(tmp, mangle_prerouting) + + tmp = cmd(f'sudo nft -s list chain mangle WANLOADBALANCE_PRE') + self.assertEqual(tmp, mangle_wanloadbalance_pre) + + # Check nat chains + tmp = cmd(f'sudo nft -s list chain nat WANLOADBALANCE') + self.assertEqual(tmp, nat_wanloadbalance) + + tmp = cmd(f'sudo nft -s list chain nat VYOS_PRE_SNAT_HOOK') + self.assertEqual(tmp, nat_vyos_pre_snat_hook) + + # Delete veth interfaces and netns + for iface in [iface1, iface2, iface3]: + call(f'sudo ip link del dev {iface}') + + delete_netns(ns1) + delete_netns(ns2) + delete_netns(ns3) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_load_balancing_reverse_proxy.py b/smoketest/scripts/cli/test_load_balancing_reverse_proxy.py deleted file mode 100755 index 274b97f22..000000000 --- a/smoketest/scripts/cli/test_load_balancing_reverse_proxy.py +++ /dev/null @@ -1,114 +0,0 @@ -#!/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 . - -import unittest - -from base_vyostest_shim import VyOSUnitTestSHIM - -from vyos.configsession import ConfigSessionError -from vyos.utils.process import process_named_running -from vyos.utils.file import read_file - -PROCESS_NAME = 'haproxy' -HAPROXY_CONF = '/run/haproxy/haproxy.cfg' -base_path = ['load-balancing', 'reverse-proxy'] -proxy_interface = 'eth1' - - -class TestLoadBalancingReverseProxy(VyOSUnitTestSHIM.TestCase): - def tearDown(self): - # Check for running process - self.assertTrue(process_named_running(PROCESS_NAME)) - - self.cli_delete(['interfaces', 'ethernet', proxy_interface, 'address']) - self.cli_delete(base_path) - self.cli_commit() - - # Process must be terminated after deleting the config - self.assertFalse(process_named_running(PROCESS_NAME)) - - def test_01_lb_reverse_proxy_domain(self): - domains_bk_first = ['n1.example.com', 'n2.example.com', 'n3.example.com'] - domain_bk_second = 'n5.example.com' - frontend = 'https_front' - front_port = '4433' - bk_server_first = '192.0.2.11' - bk_server_second = '192.0.2.12' - bk_first_name = 'bk-01' - bk_second_name = 'bk-02' - bk_server_port = '9090' - mode = 'http' - rule_ten = '10' - rule_twenty = '20' - send_proxy = 'send-proxy' - max_connections = '1000' - - back_base = base_path + ['backend'] - - self.cli_set(base_path + ['service', frontend, 'mode', mode]) - self.cli_set(base_path + ['service', frontend, 'port', front_port]) - for domain in domains_bk_first: - self.cli_set(base_path + ['service', frontend, 'rule', rule_ten, 'domain-name', domain]) - self.cli_set(base_path + ['service', frontend, 'rule', rule_ten, 'set', 'backend', bk_first_name]) - self.cli_set(base_path + ['service', frontend, 'rule', rule_twenty, 'domain-name', domain_bk_second]) - self.cli_set(base_path + ['service', frontend, 'rule', rule_twenty, 'set', 'backend', bk_second_name]) - - self.cli_set(back_base + [bk_first_name, 'mode', mode]) - self.cli_set(back_base + [bk_first_name, 'server', bk_first_name, 'address', bk_server_first]) - self.cli_set(back_base + [bk_first_name, 'server', bk_first_name, 'port', bk_server_port]) - self.cli_set(back_base + [bk_first_name, 'server', bk_first_name, send_proxy]) - - self.cli_set(back_base + [bk_second_name, 'mode', mode]) - self.cli_set(back_base + [bk_second_name, 'server', bk_second_name, 'address', bk_server_second]) - self.cli_set(back_base + [bk_second_name, 'server', bk_second_name, 'port', bk_server_port]) - self.cli_set(back_base + [bk_second_name, 'server', bk_second_name, 'backup']) - - self.cli_set(base_path + ['global-parameters', 'max-connections', max_connections]) - - # commit changes - self.cli_commit() - - config = read_file(HAPROXY_CONF) - - # Global - self.assertIn(f'maxconn {max_connections}', config) - - # Frontend - self.assertIn(f'frontend {frontend}', config) - self.assertIn(f'bind :::{front_port} v4v6', config) - self.assertIn(f'mode {mode}', config) - for domain in domains_bk_first: - self.assertIn(f'acl {rule_ten} hdr(host) -i {domain}', config) - self.assertIn(f'use_backend {bk_first_name} if {rule_ten}', config) - self.assertIn(f'acl {rule_twenty} hdr(host) -i {domain_bk_second}', config) - self.assertIn(f'use_backend {bk_second_name} if {rule_twenty}', config) - - # Backend - self.assertIn(f'backend {bk_first_name}', config) - self.assertIn(f'balance roundrobin', config) - self.assertIn(f'option forwardfor', config) - self.assertIn('http-request add-header X-Forwarded-Proto https if { ssl_fc }', config) - self.assertIn(f'mode {mode}', config) - self.assertIn(f'server {bk_first_name} {bk_server_first}:{bk_server_port} send-proxy', config) - - self.assertIn(f'backend {bk_second_name}', config) - self.assertIn(f'mode {mode}', config) - self.assertIn(f'server {bk_second_name} {bk_server_second}:{bk_server_port}', config) - self.assertIn(f'server {bk_second_name} {bk_server_second}:{bk_server_port} backup', config) - - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_load_balancing_wan.py b/smoketest/scripts/cli/test_load_balancing_wan.py deleted file mode 100755 index 47ca19b27..000000000 --- a/smoketest/scripts/cli/test_load_balancing_wan.py +++ /dev/null @@ -1,259 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2022-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 . - -import os -import unittest -import time - -from base_vyostest_shim import VyOSUnitTestSHIM -from vyos.configsession import ConfigSessionError -from vyos.ifconfig import Section -from vyos.utils.process import call -from vyos.utils.process import cmd - - -base_path = ['load-balancing'] - - -def create_netns(name): - return call(f'sudo ip netns add {name}') - -def create_veth_pair(local='veth0', peer='ceth0'): - return call(f'sudo ip link add {local} type veth peer name {peer}') - -def move_interface_to_netns(iface, netns_name): - return call(f'sudo ip link set {iface} netns {netns_name}') - -def rename_interface(iface, new_name): - return call(f'sudo ip link set {iface} name {new_name}') - -def cmd_in_netns(netns, cmd): - return call(f'sudo ip netns exec {netns} {cmd}') - -def delete_netns(name): - return call(f'sudo ip netns del {name}') - -class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase): - @classmethod - def setUpClass(cls): - super(TestLoadBalancingWan, 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): - self.cli_delete(base_path) - self.cli_commit() - - def test_table_routes(self): - ns1 = 'ns201' - ns2 = 'ns202' - ns3 = 'ns203' - iface1 = 'eth201' - iface2 = 'eth202' - iface3 = 'eth203' - container_iface1 = 'ceth0' - container_iface2 = 'ceth1' - container_iface3 = 'ceth2' - - # Create network namespeces - create_netns(ns1) - create_netns(ns2) - create_netns(ns3) - create_veth_pair(iface1, container_iface1) - create_veth_pair(iface2, container_iface2) - create_veth_pair(iface3, container_iface3) - - move_interface_to_netns(container_iface1, ns1) - move_interface_to_netns(container_iface2, ns2) - move_interface_to_netns(container_iface3, ns3) - call(f'sudo ip address add 203.0.113.10/24 dev {iface1}') - call(f'sudo ip address add 192.0.2.10/24 dev {iface2}') - call(f'sudo ip address add 198.51.100.10/24 dev {iface3}') - call(f'sudo ip link set dev {iface1} up') - call(f'sudo ip link set dev {iface2} up') - call(f'sudo ip link set dev {iface3} up') - cmd_in_netns(ns1, f'ip link set {container_iface1} name eth0') - cmd_in_netns(ns2, f'ip link set {container_iface2} name eth0') - cmd_in_netns(ns3, f'ip link set {container_iface3} name eth0') - cmd_in_netns(ns1, 'ip address add 203.0.113.1/24 dev eth0') - cmd_in_netns(ns2, 'ip address add 192.0.2.1/24 dev eth0') - cmd_in_netns(ns3, 'ip address add 198.51.100.1/24 dev eth0') - cmd_in_netns(ns1, 'ip link set dev eth0 up') - cmd_in_netns(ns2, 'ip link set dev eth0 up') - cmd_in_netns(ns3, 'ip link set dev eth0 up') - - # Set load-balancing configuration - self.cli_set(base_path + ['wan', 'interface-health', iface1, 'failure-count', '2']) - self.cli_set(base_path + ['wan', 'interface-health', iface1, 'nexthop', '203.0.113.1']) - self.cli_set(base_path + ['wan', 'interface-health', iface1, 'success-count', '1']) - self.cli_set(base_path + ['wan', 'interface-health', iface2, 'failure-count', '2']) - self.cli_set(base_path + ['wan', 'interface-health', iface2, 'nexthop', '192.0.2.1']) - self.cli_set(base_path + ['wan', 'interface-health', iface2, 'success-count', '1']) - - self.cli_set(base_path + ['wan', 'rule', '10', 'inbound-interface', iface3]) - self.cli_set(base_path + ['wan', 'rule', '10', 'source', 'address', '198.51.100.0/24']) - - - # commit changes - self.cli_commit() - - time.sleep(5) - # Check default routes in tables 201, 202 - # Expected values - original = 'default via 203.0.113.1 dev eth201' - tmp = cmd('sudo ip route show table 201') - self.assertEqual(tmp, original) - - original = 'default via 192.0.2.1 dev eth202' - tmp = cmd('sudo ip route show table 202') - self.assertEqual(tmp, original) - - # Delete veth interfaces and netns - for iface in [iface1, iface2, iface3]: - call(f'sudo ip link del dev {iface}') - - delete_netns(ns1) - delete_netns(ns2) - delete_netns(ns3) - - def test_check_chains(self): - - ns1 = 'nsA' - ns2 = 'nsB' - ns3 = 'nsC' - iface1 = 'veth1' - iface2 = 'veth2' - iface3 = 'veth3' - container_iface1 = 'ceth0' - container_iface2 = 'ceth1' - container_iface3 = 'ceth2' - mangle_isp1 = """table ip mangle { - chain ISP_veth1 { - counter ct mark set 0xc9 - counter meta mark set 0xc9 - counter accept - } -}""" - mangle_isp2 = """table ip mangle { - chain ISP_veth2 { - counter ct mark set 0xca - counter meta mark set 0xca - counter accept - } -}""" - mangle_prerouting = """table ip mangle { - chain PREROUTING { - type filter hook prerouting priority mangle; policy accept; - counter jump WANLOADBALANCE_PRE - } -}""" - mangle_wanloadbalance_pre = """table ip mangle { - chain WANLOADBALANCE_PRE { - iifname "veth3" ip saddr 198.51.100.0/24 ct state new meta random & 2147483647 < 1073741824 counter jump ISP_veth1 - iifname "veth3" ip saddr 198.51.100.0/24 ct state new counter jump ISP_veth2 - iifname "veth3" ip saddr 198.51.100.0/24 counter meta mark set ct mark - } -}""" - nat_wanloadbalance = """table ip nat { - chain WANLOADBALANCE { - ct mark 0xc9 counter snat to 203.0.113.10 - ct mark 0xca counter snat to 192.0.2.10 - } -}""" - nat_vyos_pre_snat_hook = """table ip nat { - chain VYOS_PRE_SNAT_HOOK { - type nat hook postrouting priority srcnat - 1; policy accept; - counter jump WANLOADBALANCE - } -}""" - - # Create network namespeces - create_netns(ns1) - create_netns(ns2) - create_netns(ns3) - create_veth_pair(iface1, container_iface1) - create_veth_pair(iface2, container_iface2) - create_veth_pair(iface3, container_iface3) - move_interface_to_netns(container_iface1, ns1) - move_interface_to_netns(container_iface2, ns2) - move_interface_to_netns(container_iface3, ns3) - call(f'sudo ip address add 203.0.113.10/24 dev {iface1}') - call(f'sudo ip address add 192.0.2.10/24 dev {iface2}') - call(f'sudo ip address add 198.51.100.10/24 dev {iface3}') - - for iface in [iface1, iface2, iface3]: - call(f'sudo ip link set dev {iface} up') - - cmd_in_netns(ns1, f'ip link set {container_iface1} name eth0') - cmd_in_netns(ns2, f'ip link set {container_iface2} name eth0') - cmd_in_netns(ns3, f'ip link set {container_iface3} name eth0') - cmd_in_netns(ns1, 'ip address add 203.0.113.1/24 dev eth0') - cmd_in_netns(ns2, 'ip address add 192.0.2.1/24 dev eth0') - cmd_in_netns(ns3, 'ip address add 198.51.100.1/24 dev eth0') - cmd_in_netns(ns1, 'ip link set dev eth0 up') - cmd_in_netns(ns2, 'ip link set dev eth0 up') - cmd_in_netns(ns3, 'ip link set dev eth0 up') - - # Set load-balancing configuration - self.cli_set(base_path + ['wan', 'interface-health', iface1, 'failure-count', '2']) - self.cli_set(base_path + ['wan', 'interface-health', iface1, 'nexthop', '203.0.113.1']) - self.cli_set(base_path + ['wan', 'interface-health', iface1, 'success-count', '1']) - self.cli_set(base_path + ['wan', 'interface-health', iface2, 'failure-count', '2']) - self.cli_set(base_path + ['wan', 'interface-health', iface2, 'nexthop', '192.0.2.1']) - self.cli_set(base_path + ['wan', 'interface-health', iface2, 'success-count', '1']) - self.cli_set(base_path + ['wan', 'rule', '10', 'inbound-interface', iface3]) - self.cli_set(base_path + ['wan', 'rule', '10', 'source', 'address', '198.51.100.0/24']) - self.cli_set(base_path + ['wan', 'rule', '10', 'interface', iface1]) - self.cli_set(base_path + ['wan', 'rule', '10', 'interface', iface2]) - - # commit changes - self.cli_commit() - - time.sleep(5) - - # Check mangle chains - tmp = cmd(f'sudo nft -s list chain mangle ISP_{iface1}') - self.assertEqual(tmp, mangle_isp1) - - tmp = cmd(f'sudo nft -s list chain mangle ISP_{iface2}') - self.assertEqual(tmp, mangle_isp2) - - tmp = cmd(f'sudo nft -s list chain mangle PREROUTING') - self.assertEqual(tmp, mangle_prerouting) - - tmp = cmd(f'sudo nft -s list chain mangle WANLOADBALANCE_PRE') - self.assertEqual(tmp, mangle_wanloadbalance_pre) - - # Check nat chains - tmp = cmd(f'sudo nft -s list chain nat WANLOADBALANCE') - self.assertEqual(tmp, nat_wanloadbalance) - - tmp = cmd(f'sudo nft -s list chain nat VYOS_PRE_SNAT_HOOK') - self.assertEqual(tmp, nat_vyos_pre_snat_hook) - - # Delete veth interfaces and netns - for iface in [iface1, iface2, iface3]: - call(f'sudo ip link del dev {iface}') - - delete_netns(ns1) - delete_netns(ns2) - delete_netns(ns3) - - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_segment-routing.py b/smoketest/scripts/cli/test_protocols_segment-routing.py new file mode 100755 index 000000000..403c05924 --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_segment-routing.py @@ -0,0 +1,112 @@ +#!/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 . + +import os +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Section +from vyos.utils.process import cmd +from vyos.utils.process import process_named_running +from vyos.utils.system import sysctl_read + +base_path = ['protocols', 'segment-routing'] +PROCESS_NAME = 'zebra' + +class TestProtocolsSegmentRouting(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + # call base-classes classmethod + super(TestProtocolsSegmentRouting, cls).setUpClass() + # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same + cls.daemon_pid = process_named_running(PROCESS_NAME) + # 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): + self.cli_delete(base_path) + self.cli_commit() + + # check process health and continuity + self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) + + def test_srv6(self): + interfaces = Section.interfaces('ethernet', vlan=False) + locators = { + 'foo' : { 'prefix' : '2001:a::/64' }, + 'foo' : { 'prefix' : '2001:b::/64', 'usid' : {} }, + } + + for locator, locator_config in locators.items(): + self.cli_set(base_path + ['srv6', 'locator', locator, 'prefix', locator_config['prefix']]) + if 'usid' in locator_config: + self.cli_set(base_path + ['srv6', 'locator', locator, 'behavior-usid']) + + # verify() - SRv6 should be enabled on at least one interface! + with self.assertRaises(ConfigSessionError): + self.cli_commit() + for interface in interfaces: + self.cli_set(base_path + ['interface', interface, 'srv6']) + + self.cli_commit() + + for interface in interfaces: + self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_enabled'), '1') + self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_require_hmac'), '0') # default + + frrconfig = self.getFRRconfig(f'segment-routing', daemon='zebra') + self.assertIn(f'segment-routing', frrconfig) + self.assertIn(f' srv6', frrconfig) + self.assertIn(f' locators', frrconfig) + for locator, locator_config in locators.items(): + self.assertIn(f' locator {locator}', frrconfig) + self.assertIn(f' prefix {locator_config["prefix"]} block-len 40 node-len 24 func-bits 16', frrconfig) + + def test_srv6_sysctl(self): + interfaces = Section.interfaces('ethernet', vlan=False) + + # HMAC accept + for interface in interfaces: + self.cli_set(base_path + ['interface', interface, 'srv6']) + self.cli_set(base_path + ['interface', interface, 'srv6', 'hmac', 'ignore']) + self.cli_commit() + + for interface in interfaces: + self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_enabled'), '1') + self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_require_hmac'), '-1') # ignore + + # HMAC drop + for interface in interfaces: + self.cli_set(base_path + ['interface', interface, 'srv6']) + self.cli_set(base_path + ['interface', interface, 'srv6', 'hmac', 'drop']) + self.cli_commit() + + for interface in interfaces: + self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_enabled'), '1') + self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_require_hmac'), '1') # drop + + # Disable SRv6 on first interface + first_if = interfaces[-1] + self.cli_delete(base_path + ['interface', first_if]) + self.cli_commit() + + self.assertEqual(sysctl_read(f'net.ipv6.conf.{first_if}.seg6_enabled'), '0') + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_segment_routing.py b/smoketest/scripts/cli/test_protocols_segment_routing.py deleted file mode 100755 index 403c05924..000000000 --- a/smoketest/scripts/cli/test_protocols_segment_routing.py +++ /dev/null @@ -1,112 +0,0 @@ -#!/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 . - -import os -import unittest - -from base_vyostest_shim import VyOSUnitTestSHIM - -from vyos.configsession import ConfigSessionError -from vyos.ifconfig import Section -from vyos.utils.process import cmd -from vyos.utils.process import process_named_running -from vyos.utils.system import sysctl_read - -base_path = ['protocols', 'segment-routing'] -PROCESS_NAME = 'zebra' - -class TestProtocolsSegmentRouting(VyOSUnitTestSHIM.TestCase): - @classmethod - def setUpClass(cls): - # call base-classes classmethod - super(TestProtocolsSegmentRouting, cls).setUpClass() - # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same - cls.daemon_pid = process_named_running(PROCESS_NAME) - # 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): - self.cli_delete(base_path) - self.cli_commit() - - # check process health and continuity - self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) - - def test_srv6(self): - interfaces = Section.interfaces('ethernet', vlan=False) - locators = { - 'foo' : { 'prefix' : '2001:a::/64' }, - 'foo' : { 'prefix' : '2001:b::/64', 'usid' : {} }, - } - - for locator, locator_config in locators.items(): - self.cli_set(base_path + ['srv6', 'locator', locator, 'prefix', locator_config['prefix']]) - if 'usid' in locator_config: - self.cli_set(base_path + ['srv6', 'locator', locator, 'behavior-usid']) - - # verify() - SRv6 should be enabled on at least one interface! - with self.assertRaises(ConfigSessionError): - self.cli_commit() - for interface in interfaces: - self.cli_set(base_path + ['interface', interface, 'srv6']) - - self.cli_commit() - - for interface in interfaces: - self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_enabled'), '1') - self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_require_hmac'), '0') # default - - frrconfig = self.getFRRconfig(f'segment-routing', daemon='zebra') - self.assertIn(f'segment-routing', frrconfig) - self.assertIn(f' srv6', frrconfig) - self.assertIn(f' locators', frrconfig) - for locator, locator_config in locators.items(): - self.assertIn(f' locator {locator}', frrconfig) - self.assertIn(f' prefix {locator_config["prefix"]} block-len 40 node-len 24 func-bits 16', frrconfig) - - def test_srv6_sysctl(self): - interfaces = Section.interfaces('ethernet', vlan=False) - - # HMAC accept - for interface in interfaces: - self.cli_set(base_path + ['interface', interface, 'srv6']) - self.cli_set(base_path + ['interface', interface, 'srv6', 'hmac', 'ignore']) - self.cli_commit() - - for interface in interfaces: - self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_enabled'), '1') - self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_require_hmac'), '-1') # ignore - - # HMAC drop - for interface in interfaces: - self.cli_set(base_path + ['interface', interface, 'srv6']) - self.cli_set(base_path + ['interface', interface, 'srv6', 'hmac', 'drop']) - self.cli_commit() - - for interface in interfaces: - self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_enabled'), '1') - self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_require_hmac'), '1') # drop - - # Disable SRv6 on first interface - first_if = interfaces[-1] - self.cli_delete(base_path + ['interface', first_if]) - self.cli_commit() - - self.assertEqual(sysctl_read(f'net.ipv6.conf.{first_if}.seg6_enabled'), '0') - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_bcast-relay.py b/smoketest/scripts/cli/test_service_bcast-relay.py deleted file mode 100755 index 87901869e..000000000 --- a/smoketest/scripts/cli/test_service_bcast-relay.py +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2019-2020 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 . - -import unittest - -from base_vyostest_shim import VyOSUnitTestSHIM - -from psutil import process_iter -from vyos.configsession import ConfigSessionError - -base_path = ['service', 'broadcast-relay'] - -class TestServiceBroadcastRelay(VyOSUnitTestSHIM.TestCase): - _address1 = '192.0.2.1/24' - _address2 = '192.0.2.1/24' - - def setUp(self): - self.cli_set(['interfaces', 'dummy', 'dum1001', 'address', self._address1]) - self.cli_set(['interfaces', 'dummy', 'dum1002', 'address', self._address2]) - - def tearDown(self): - self.cli_delete(['interfaces', 'dummy', 'dum1001']) - self.cli_delete(['interfaces', 'dummy', 'dum1002']) - self.cli_delete(base_path) - self.cli_commit() - - def test_broadcast_relay_service(self): - ids = range(1, 5) - for id in ids: - base = base_path + ['id', str(id)] - self.cli_set(base + ['description', 'vyos']) - self.cli_set(base + ['port', str(10000 + id)]) - - # check validate() - two interfaces must be present - with self.assertRaises(ConfigSessionError): - self.cli_commit() - - self.cli_set(base + ['interface', 'dum1001']) - self.cli_set(base + ['interface', 'dum1002']) - self.cli_set(base + ['address', self._address1.split('/')[0]]) - - self.cli_commit() - - for id in ids: - # check if process is running - running = False - for p in process_iter(): - if "udp-broadcast-relay" in p.name(): - if p.cmdline()[3] == str(id): - running = True - break - self.assertTrue(running) - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_broadcast-relay.py b/smoketest/scripts/cli/test_service_broadcast-relay.py new file mode 100755 index 000000000..87901869e --- /dev/null +++ b/smoketest/scripts/cli/test_service_broadcast-relay.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2020 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 . + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from psutil import process_iter +from vyos.configsession import ConfigSessionError + +base_path = ['service', 'broadcast-relay'] + +class TestServiceBroadcastRelay(VyOSUnitTestSHIM.TestCase): + _address1 = '192.0.2.1/24' + _address2 = '192.0.2.1/24' + + def setUp(self): + self.cli_set(['interfaces', 'dummy', 'dum1001', 'address', self._address1]) + self.cli_set(['interfaces', 'dummy', 'dum1002', 'address', self._address2]) + + def tearDown(self): + self.cli_delete(['interfaces', 'dummy', 'dum1001']) + self.cli_delete(['interfaces', 'dummy', 'dum1002']) + self.cli_delete(base_path) + self.cli_commit() + + def test_broadcast_relay_service(self): + ids = range(1, 5) + for id in ids: + base = base_path + ['id', str(id)] + self.cli_set(base + ['description', 'vyos']) + self.cli_set(base + ['port', str(10000 + id)]) + + # check validate() - two interfaces must be present + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base + ['interface', 'dum1001']) + self.cli_set(base + ['interface', 'dum1002']) + self.cli_set(base + ['address', self._address1.split('/')[0]]) + + self.cli_commit() + + for id in ids: + # check if process is running + running = False + for p in process_iter(): + if "udp-broadcast-relay" in p.name(): + if p.cmdline()[3] == str(id): + running = True + break + self.assertTrue(running) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_ids.py b/smoketest/scripts/cli/test_service_ids.py deleted file mode 100755 index 91b056eea..000000000 --- a/smoketest/scripts/cli/test_service_ids.py +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2022 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 . - -import os -import unittest - -from base_vyostest_shim import VyOSUnitTestSHIM - -from vyos.configsession import ConfigSessionError -from vyos.utils.process import process_named_running -from vyos.utils.file import read_file - -PROCESS_NAME = 'fastnetmon' -FASTNETMON_CONF = '/run/fastnetmon/fastnetmon.conf' -NETWORKS_CONF = '/run/fastnetmon/networks_list' -EXCLUDED_NETWORKS_CONF = '/run/fastnetmon/excluded_networks_list' -base_path = ['service', 'ids', 'ddos-protection'] - -class TestServiceIDS(VyOSUnitTestSHIM.TestCase): - @classmethod - def setUpClass(cls): - super(TestServiceIDS, 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): - # Check for running process - self.assertTrue(process_named_running(PROCESS_NAME)) - - # delete test config - self.cli_delete(base_path) - self.cli_commit() - - self.assertFalse(os.path.exists(FASTNETMON_CONF)) - self.assertFalse(process_named_running(PROCESS_NAME)) - - def test_fastnetmon(self): - networks = ['10.0.0.0/24', '10.5.5.0/24', '2001:db8:10::/64', '2001:db8:20::/64'] - excluded_networks = ['10.0.0.1/32', '2001:db8:10::1/128'] - interfaces = ['eth0', 'eth1'] - fps = '3500' - mbps = '300' - pps = '60000' - - self.cli_set(base_path + ['mode', 'mirror']) - # Required network! - with self.assertRaises(ConfigSessionError): - self.cli_commit() - for tmp in networks: - self.cli_set(base_path + ['network', tmp]) - - # optional excluded-network! - with self.assertRaises(ConfigSessionError): - self.cli_commit() - for tmp in excluded_networks: - self.cli_set(base_path + ['excluded-network', tmp]) - - # Required interface(s)! - with self.assertRaises(ConfigSessionError): - self.cli_commit() - for tmp in interfaces: - self.cli_set(base_path + ['listen-interface', tmp]) - - self.cli_set(base_path + ['direction', 'in']) - self.cli_set(base_path + ['threshold', 'general', 'fps', fps]) - self.cli_set(base_path + ['threshold', 'general', 'pps', pps]) - self.cli_set(base_path + ['threshold', 'general', 'mbps', mbps]) - - # commit changes - self.cli_commit() - - # Check configured port - config = read_file(FASTNETMON_CONF) - self.assertIn(f'mirror_afpacket = on', config) - self.assertIn(f'process_incoming_traffic = on', config) - self.assertIn(f'process_outgoing_traffic = off', config) - self.assertIn(f'ban_for_flows = on', config) - self.assertIn(f'threshold_flows = {fps}', config) - self.assertIn(f'ban_for_bandwidth = on', config) - self.assertIn(f'threshold_mbps = {mbps}', config) - self.assertIn(f'ban_for_pps = on', config) - self.assertIn(f'threshold_pps = {pps}', config) - # default - self.assertIn(f'enable_ban = on', config) - self.assertIn(f'enable_ban_ipv6 = on', config) - self.assertIn(f'ban_time = 1900', config) - - tmp = ','.join(interfaces) - self.assertIn(f'interfaces = {tmp}', config) - - - network_config = read_file(NETWORKS_CONF) - for tmp in networks: - self.assertIn(f'{tmp}', network_config) - - excluded_network_config = read_file(EXCLUDED_NETWORKS_CONF) - for tmp in excluded_networks: - self.assertIn(f'{tmp}', excluded_network_config) - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_ids_ddos-protection.py b/smoketest/scripts/cli/test_service_ids_ddos-protection.py new file mode 100755 index 000000000..91b056eea --- /dev/null +++ b/smoketest/scripts/cli/test_service_ids_ddos-protection.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 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 . + +import os +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file + +PROCESS_NAME = 'fastnetmon' +FASTNETMON_CONF = '/run/fastnetmon/fastnetmon.conf' +NETWORKS_CONF = '/run/fastnetmon/networks_list' +EXCLUDED_NETWORKS_CONF = '/run/fastnetmon/excluded_networks_list' +base_path = ['service', 'ids', 'ddos-protection'] + +class TestServiceIDS(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestServiceIDS, 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): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + # delete test config + self.cli_delete(base_path) + self.cli_commit() + + self.assertFalse(os.path.exists(FASTNETMON_CONF)) + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_fastnetmon(self): + networks = ['10.0.0.0/24', '10.5.5.0/24', '2001:db8:10::/64', '2001:db8:20::/64'] + excluded_networks = ['10.0.0.1/32', '2001:db8:10::1/128'] + interfaces = ['eth0', 'eth1'] + fps = '3500' + mbps = '300' + pps = '60000' + + self.cli_set(base_path + ['mode', 'mirror']) + # Required network! + with self.assertRaises(ConfigSessionError): + self.cli_commit() + for tmp in networks: + self.cli_set(base_path + ['network', tmp]) + + # optional excluded-network! + with self.assertRaises(ConfigSessionError): + self.cli_commit() + for tmp in excluded_networks: + self.cli_set(base_path + ['excluded-network', tmp]) + + # Required interface(s)! + with self.assertRaises(ConfigSessionError): + self.cli_commit() + for tmp in interfaces: + self.cli_set(base_path + ['listen-interface', tmp]) + + self.cli_set(base_path + ['direction', 'in']) + self.cli_set(base_path + ['threshold', 'general', 'fps', fps]) + self.cli_set(base_path + ['threshold', 'general', 'pps', pps]) + self.cli_set(base_path + ['threshold', 'general', 'mbps', mbps]) + + # commit changes + self.cli_commit() + + # Check configured port + config = read_file(FASTNETMON_CONF) + self.assertIn(f'mirror_afpacket = on', config) + self.assertIn(f'process_incoming_traffic = on', config) + self.assertIn(f'process_outgoing_traffic = off', config) + self.assertIn(f'ban_for_flows = on', config) + self.assertIn(f'threshold_flows = {fps}', config) + self.assertIn(f'ban_for_bandwidth = on', config) + self.assertIn(f'threshold_mbps = {mbps}', config) + self.assertIn(f'ban_for_pps = on', config) + self.assertIn(f'threshold_pps = {pps}', config) + # default + self.assertIn(f'enable_ban = on', config) + self.assertIn(f'enable_ban_ipv6 = on', config) + self.assertIn(f'ban_time = 1900', config) + + tmp = ','.join(interfaces) + self.assertIn(f'interfaces = {tmp}', config) + + + network_config = read_file(NETWORKS_CONF) + for tmp in networks: + self.assertIn(f'{tmp}', network_config) + + excluded_network_config = read_file(EXCLUDED_NETWORKS_CONF) + for tmp in excluded_networks: + self.assertIn(f'{tmp}', excluded_network_config) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_mdns-repeater.py b/smoketest/scripts/cli/test_service_mdns-repeater.py deleted file mode 100755 index f2fb3b509..000000000 --- a/smoketest/scripts/cli/test_service_mdns-repeater.py +++ /dev/null @@ -1,134 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2020-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 . - -import unittest - -from base_vyostest_shim import VyOSUnitTestSHIM - -from configparser import ConfigParser -from vyos.configsession import ConfigSessionError -from vyos.utils.process import process_named_running - -base_path = ['service', 'mdns', 'repeater'] -intf_base = ['interfaces', 'dummy'] -config_file = '/run/avahi-daemon/avahi-daemon.conf' - - -class TestServiceMDNSrepeater(VyOSUnitTestSHIM.TestCase): - def setUp(self): - # Start with a clean CLI instance - self.cli_delete(base_path) - - # Service required a configured IP address on the interface - self.cli_set(intf_base + ['dum10', 'address', '192.0.2.1/30']) - self.cli_set(intf_base + ['dum10', 'ipv6', 'address', 'no-default-link-local']) - self.cli_set(intf_base + ['dum20', 'address', '192.0.2.5/30']) - self.cli_set(intf_base + ['dum20', 'address', '2001:db8:0:2::5/64']) - self.cli_set(intf_base + ['dum30', 'address', '192.0.2.9/30']) - self.cli_set(intf_base + ['dum30', 'address', '2001:db8:0:2::9/64']) - self.cli_set(intf_base + ['dum40', 'address', '2001:db8:0:2::11/64']) - self.cli_commit() - - def tearDown(self): - # Check for running process - self.assertTrue(process_named_running('avahi-daemon')) - - self.cli_delete(base_path) - self.cli_delete(intf_base + ['dum10']) - self.cli_delete(intf_base + ['dum20']) - self.cli_delete(intf_base + ['dum30']) - self.cli_delete(intf_base + ['dum40']) - self.cli_commit() - - # Check that there is no longer a running process - self.assertFalse(process_named_running('avahi-daemon')) - - def test_service_dual_stack(self): - # mDNS browsing domains in addition to the default one (local) - domains = ['dom1.home.arpa', 'dom2.home.arpa'] - - # mDNS services to be repeated - services = ['_ipp._tcp', '_smb._tcp', '_ssh._tcp'] - - self.cli_set(base_path + ['ip-version', 'both']) - self.cli_set(base_path + ['interface', 'dum20']) - self.cli_set(base_path + ['interface', 'dum30']) - - for domain in domains: - self.cli_set(base_path + ['browse-domain', domain]) - - for service in services: - self.cli_set(base_path + ['allow-service', service]) - - self.cli_commit() - - # Validate configuration values - conf = ConfigParser(delimiters='=') - conf.read(config_file) - - self.assertEqual(conf['server']['use-ipv4'], 'yes') - self.assertEqual(conf['server']['use-ipv6'], 'yes') - self.assertEqual(conf['server']['allow-interfaces'], 'dum20, dum30') - self.assertEqual(conf['server']['browse-domains'], ', '.join(domains)) - self.assertEqual(conf['reflector']['enable-reflector'], 'yes') - self.assertEqual(conf['reflector']['reflect-filters'], ', '.join(services)) - - def test_service_ipv4(self): - # partcipating interfaces should have IPv4 addresses - self.cli_set(base_path + ['ip-version', 'ipv4']) - self.cli_set(base_path + ['interface', 'dum10']) - self.cli_set(base_path + ['interface', 'dum40']) - - # exception is raised if partcipating interfaces do not have IPv4 address - with self.assertRaises(ConfigSessionError): - self.cli_commit() - self.cli_delete(base_path + ['interface', 'dum40']) - self.cli_set(base_path + ['interface', 'dum20']) - self.cli_commit() - - # Validate configuration values - conf = ConfigParser(delimiters='=') - conf.read(config_file) - - self.assertEqual(conf['server']['use-ipv4'], 'yes') - self.assertEqual(conf['server']['use-ipv6'], 'no') - self.assertEqual(conf['server']['allow-interfaces'], 'dum10, dum20') - self.assertEqual(conf['reflector']['enable-reflector'], 'yes') - - def test_service_ipv6(self): - # partcipating interfaces should have IPv6 addresses - self.cli_set(base_path + ['ip-version', 'ipv6']) - self.cli_set(base_path + ['interface', 'dum10']) - self.cli_set(base_path + ['interface', 'dum30']) - - # exception is raised if partcipating interfaces do not have IPv4 address - with self.assertRaises(ConfigSessionError): - self.cli_commit() - self.cli_delete(base_path + ['interface', 'dum10']) - self.cli_set(base_path + ['interface', 'dum40']) - self.cli_commit() - - # Validate configuration values - conf = ConfigParser(delimiters='=') - conf.read(config_file) - - self.assertEqual(conf['server']['use-ipv4'], 'no') - self.assertEqual(conf['server']['use-ipv6'], 'yes') - self.assertEqual(conf['server']['allow-interfaces'], 'dum30, dum40') - self.assertEqual(conf['reflector']['enable-reflector'], 'yes') - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_mdns_repeater.py b/smoketest/scripts/cli/test_service_mdns_repeater.py new file mode 100755 index 000000000..f2fb3b509 --- /dev/null +++ b/smoketest/scripts/cli/test_service_mdns_repeater.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-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 . + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from configparser import ConfigParser +from vyos.configsession import ConfigSessionError +from vyos.utils.process import process_named_running + +base_path = ['service', 'mdns', 'repeater'] +intf_base = ['interfaces', 'dummy'] +config_file = '/run/avahi-daemon/avahi-daemon.conf' + + +class TestServiceMDNSrepeater(VyOSUnitTestSHIM.TestCase): + def setUp(self): + # Start with a clean CLI instance + self.cli_delete(base_path) + + # Service required a configured IP address on the interface + self.cli_set(intf_base + ['dum10', 'address', '192.0.2.1/30']) + self.cli_set(intf_base + ['dum10', 'ipv6', 'address', 'no-default-link-local']) + self.cli_set(intf_base + ['dum20', 'address', '192.0.2.5/30']) + self.cli_set(intf_base + ['dum20', 'address', '2001:db8:0:2::5/64']) + self.cli_set(intf_base + ['dum30', 'address', '192.0.2.9/30']) + self.cli_set(intf_base + ['dum30', 'address', '2001:db8:0:2::9/64']) + self.cli_set(intf_base + ['dum40', 'address', '2001:db8:0:2::11/64']) + self.cli_commit() + + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running('avahi-daemon')) + + self.cli_delete(base_path) + self.cli_delete(intf_base + ['dum10']) + self.cli_delete(intf_base + ['dum20']) + self.cli_delete(intf_base + ['dum30']) + self.cli_delete(intf_base + ['dum40']) + self.cli_commit() + + # Check that there is no longer a running process + self.assertFalse(process_named_running('avahi-daemon')) + + def test_service_dual_stack(self): + # mDNS browsing domains in addition to the default one (local) + domains = ['dom1.home.arpa', 'dom2.home.arpa'] + + # mDNS services to be repeated + services = ['_ipp._tcp', '_smb._tcp', '_ssh._tcp'] + + self.cli_set(base_path + ['ip-version', 'both']) + self.cli_set(base_path + ['interface', 'dum20']) + self.cli_set(base_path + ['interface', 'dum30']) + + for domain in domains: + self.cli_set(base_path + ['browse-domain', domain]) + + for service in services: + self.cli_set(base_path + ['allow-service', service]) + + self.cli_commit() + + # Validate configuration values + conf = ConfigParser(delimiters='=') + conf.read(config_file) + + self.assertEqual(conf['server']['use-ipv4'], 'yes') + self.assertEqual(conf['server']['use-ipv6'], 'yes') + self.assertEqual(conf['server']['allow-interfaces'], 'dum20, dum30') + self.assertEqual(conf['server']['browse-domains'], ', '.join(domains)) + self.assertEqual(conf['reflector']['enable-reflector'], 'yes') + self.assertEqual(conf['reflector']['reflect-filters'], ', '.join(services)) + + def test_service_ipv4(self): + # partcipating interfaces should have IPv4 addresses + self.cli_set(base_path + ['ip-version', 'ipv4']) + self.cli_set(base_path + ['interface', 'dum10']) + self.cli_set(base_path + ['interface', 'dum40']) + + # exception is raised if partcipating interfaces do not have IPv4 address + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', 'dum40']) + self.cli_set(base_path + ['interface', 'dum20']) + self.cli_commit() + + # Validate configuration values + conf = ConfigParser(delimiters='=') + conf.read(config_file) + + self.assertEqual(conf['server']['use-ipv4'], 'yes') + self.assertEqual(conf['server']['use-ipv6'], 'no') + self.assertEqual(conf['server']['allow-interfaces'], 'dum10, dum20') + self.assertEqual(conf['reflector']['enable-reflector'], 'yes') + + def test_service_ipv6(self): + # partcipating interfaces should have IPv6 addresses + self.cli_set(base_path + ['ip-version', 'ipv6']) + self.cli_set(base_path + ['interface', 'dum10']) + self.cli_set(base_path + ['interface', 'dum30']) + + # exception is raised if partcipating interfaces do not have IPv4 address + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', 'dum10']) + self.cli_set(base_path + ['interface', 'dum40']) + self.cli_commit() + + # Validate configuration values + conf = ConfigParser(delimiters='=') + conf.read(config_file) + + self.assertEqual(conf['server']['use-ipv4'], 'no') + self.assertEqual(conf['server']['use-ipv6'], 'yes') + self.assertEqual(conf['server']['allow-interfaces'], 'dum30, dum40') + self.assertEqual(conf['reflector']['enable-reflector'], 'yes') + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_salt-minion.py b/smoketest/scripts/cli/test_service_salt-minion.py new file mode 100755 index 000000000..48a588b72 --- /dev/null +++ b/smoketest/scripts/cli/test_service_salt-minion.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022-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 . + +import unittest + +from socket import gethostname +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file +from vyos.utils.process import cmd + +PROCESS_NAME = 'salt-minion' +SALT_CONF = '/etc/salt/minion' +base_path = ['service', 'salt-minion'] + +interface = 'dum4456' + +class TestServiceSALT(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestServiceSALT, 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) + + cls.cli_set(cls, ['interfaces', 'dummy', interface, 'address', '100.64.0.1/16']) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, ['interfaces', 'dummy', interface]) + super(TestServiceSALT, cls).tearDownClass() + + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + # delete testing SALT config + self.cli_delete(base_path) + self.cli_commit() + + # For an unknown reason on QEMU systems (e.g. where smoketests are executed + # from the CI) salt-minion process is not killed by systemd. Apparently + # no issue on VMWare. + if cmd('systemd-detect-virt') != 'kvm': + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_default(self): + servers = ['192.0.2.1', '192.0.2.2'] + + for server in servers: + self.cli_set(base_path + ['master', server]) + + self.cli_commit() + + # commiconf = read_file() Check configured port + conf = read_file(SALT_CONF) + self.assertIn(f' - {server}', conf) + + # defaults + hostname = gethostname() + self.assertIn(f'hash_type: sha256', conf) + self.assertIn(f'id: {hostname}', conf) + self.assertIn(f'mine_interval: 60', conf) + + def test_options(self): + server = '192.0.2.3' + hash = 'sha1' + id = 'foo' + interval = '120' + + self.cli_set(base_path + ['master', server]) + self.cli_set(base_path + ['hash', hash]) + self.cli_set(base_path + ['id', id]) + self.cli_set(base_path + ['interval', interval]) + self.cli_set(base_path + ['source-interface', interface]) + + self.cli_commit() + + # commiconf = read_file() Check configured port + conf = read_file(SALT_CONF) + self.assertIn(f'- {server}', conf) + + # defaults + self.assertIn(f'hash_type: {hash}', conf) + self.assertIn(f'id: {id}', conf) + self.assertIn(f'mine_interval: {interval}', conf) + self.assertIn(f'source_interface_name: {interface}', conf) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_salt.py b/smoketest/scripts/cli/test_service_salt.py deleted file mode 100755 index 48a588b72..000000000 --- a/smoketest/scripts/cli/test_service_salt.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2022-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 . - -import unittest - -from socket import gethostname -from base_vyostest_shim import VyOSUnitTestSHIM - -from vyos.utils.process import process_named_running -from vyos.utils.file import read_file -from vyos.utils.process import cmd - -PROCESS_NAME = 'salt-minion' -SALT_CONF = '/etc/salt/minion' -base_path = ['service', 'salt-minion'] - -interface = 'dum4456' - -class TestServiceSALT(VyOSUnitTestSHIM.TestCase): - @classmethod - def setUpClass(cls): - super(TestServiceSALT, 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) - - cls.cli_set(cls, ['interfaces', 'dummy', interface, 'address', '100.64.0.1/16']) - - @classmethod - def tearDownClass(cls): - cls.cli_delete(cls, ['interfaces', 'dummy', interface]) - super(TestServiceSALT, cls).tearDownClass() - - def tearDown(self): - # Check for running process - self.assertTrue(process_named_running(PROCESS_NAME)) - - # delete testing SALT config - self.cli_delete(base_path) - self.cli_commit() - - # For an unknown reason on QEMU systems (e.g. where smoketests are executed - # from the CI) salt-minion process is not killed by systemd. Apparently - # no issue on VMWare. - if cmd('systemd-detect-virt') != 'kvm': - self.assertFalse(process_named_running(PROCESS_NAME)) - - def test_default(self): - servers = ['192.0.2.1', '192.0.2.2'] - - for server in servers: - self.cli_set(base_path + ['master', server]) - - self.cli_commit() - - # commiconf = read_file() Check configured port - conf = read_file(SALT_CONF) - self.assertIn(f' - {server}', conf) - - # defaults - hostname = gethostname() - self.assertIn(f'hash_type: sha256', conf) - self.assertIn(f'id: {hostname}', conf) - self.assertIn(f'mine_interval: 60', conf) - - def test_options(self): - server = '192.0.2.3' - hash = 'sha1' - id = 'foo' - interval = '120' - - self.cli_set(base_path + ['master', server]) - self.cli_set(base_path + ['hash', hash]) - self.cli_set(base_path + ['id', id]) - self.cli_set(base_path + ['interval', interval]) - self.cli_set(base_path + ['source-interface', interface]) - - self.cli_commit() - - # commiconf = read_file() Check configured port - conf = read_file(SALT_CONF) - self.assertIn(f'- {server}', conf) - - # defaults - self.assertIn(f'hash_type: {hash}', conf) - self.assertIn(f'id: {id}', conf) - self.assertIn(f'mine_interval: {interval}', conf) - self.assertIn(f'source_interface_name: {interface}', conf) - -if __name__ == '__main__': - unittest.main(verbosity=2) -- cgit v1.2.3