diff options
Diffstat (limited to 'smoketest/scripts/cli')
58 files changed, 4986 insertions, 1715 deletions
diff --git a/smoketest/scripts/cli/base_accel_ppp_test.py b/smoketest/scripts/cli/base_accel_ppp_test.py index 705c932b4..b2acb03cc 100644 --- a/smoketest/scripts/cli/base_accel_ppp_test.py +++ b/smoketest/scripts/cli/base_accel_ppp_test.py @@ -12,10 +12,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import os import re import unittest +from base_vyostest_shim import VyOSUnitTestSHIM from configparser import ConfigParser from vyos.configsession import ConfigSession @@ -26,26 +26,22 @@ from vyos.util import get_half_cpus from vyos.util import process_named_running class BasicAccelPPPTest: - class BaseTest(unittest.TestCase): - + class TestCase(VyOSUnitTestSHIM.TestCase): def setUp(self): - self.session = ConfigSession(os.getpid()) self._gateway = '192.0.2.1' - # ensure we can also run this test on a live system - so lets clean # out the current configuration :) - self.session.delete(self._base_path) + self.cli_delete(self._base_path) def tearDown(self): - self.session.delete(self._base_path) - self.session.commit() - del self.session + self.cli_delete(self._base_path) + self.cli_commit() def set(self, path): - self.session.set(self._base_path + path) + self.cli_set(self._base_path + path) def delete(self, path): - self.session.delete(self._base_path + path) + self.cli_delete(self._base_path + path) def basic_config(self): # PPPoE local auth mode requires local users to be configured! @@ -65,7 +61,7 @@ class BasicAccelPPPTest: self.set(['name-server', ns]) # commit changes - self.session.commit() + self.cli_commit() # Validate configuration values conf = ConfigParser(allow_no_value=True, delimiters='=') @@ -95,11 +91,11 @@ class BasicAccelPPPTest: # upload rate-limit requires also download rate-limit with self.assertRaises(ConfigSessionError): - self.session.commit() + self.cli_commit() self.set(['authentication', 'local-users', 'username', user, 'rate-limit', 'download', download]) # commit changes - self.session.commit() + self.cli_commit() # Validate configuration values conf = ConfigParser(allow_no_value=True, delimiters='=') @@ -123,7 +119,7 @@ class BasicAccelPPPTest: # Check local-users default value(s) self.delete(['authentication', 'local-users', 'username', user, 'static-ip']) # commit changes - self.session.commit() + self.cli_commit() # check local users tmp = cmd(f'sudo cat {self._chap_secrets}') @@ -162,7 +158,7 @@ class BasicAccelPPPTest: self.set(['authentication', 'radius', 'source-address', source_address]) # commit changes - self.session.commit() + self.cli_commit() # Validate configuration values conf = ConfigParser(allow_no_value=True, delimiters='=') @@ -200,7 +196,7 @@ class BasicAccelPPPTest: self.set(['authentication', 'radius', 'server', radius_server, 'disable-accounting']) # commit changes - self.session.commit() + self.cli_commit() conf.read(self._config_file) diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py index 8ee5395d0..f897088ef 100644 --- a/smoketest/scripts/cli/base_interfaces_test.py +++ b/smoketest/scripts/cli/base_interfaces_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2020 VyOS maintainers and contributors +# Copyright (C) 2019-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 @@ -12,16 +12,17 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import re import os import unittest -import json from binascii import hexlify -from netifaces import ifaddresses from netifaces import AF_INET from netifaces import AF_INET6 +from netifaces import ifaddresses +from netifaces import interfaces + +from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSession from vyos.ifconfig import Interface @@ -30,6 +31,7 @@ from vyos.util import read_file from vyos.util import cmd from vyos.util import dict_search from vyos.util import process_named_running +from vyos.util import get_interface_config from vyos.validate import is_intf_addr_assigned from vyos.validate import is_ipv6_link_local @@ -51,24 +53,15 @@ def is_mirrored_to(interface, mirror_if, qdisc): ret_val = True return ret_val - -dhcp6c_config_file = '/run/dhcp6c/dhcp6c.{}.conf' -def get_dhcp6c_config_value(interface, key): - tmp = read_file(dhcp6c_config_file.format(interface)) - tmp = re.findall(r'\n?{}\s+(.*)'.format(key), tmp) - - out = [] - for item in tmp: - out.append(item.replace(';','')) - return out - class BasicInterfaceTest: - class BaseTest(unittest.TestCase): + class TestCase(VyOSUnitTestSHIM.TestCase): _test_ip = False _test_mtu = False _test_vlan = False _test_qinq = False _test_ipv6 = False + _test_ipv6_pd = False + _test_ipv6_dhcpc6 = False _test_mirror = False _base_path = [] @@ -84,37 +77,35 @@ class BasicInterfaceTest: _mtu = '1280' def setUp(self): - self.session = ConfigSession(os.getpid()) - # Setup mirror interfaces for SPAN (Switch Port Analyzer) for span in self._mirror_interfaces: section = Section.section(span) - self.session.set(['interfaces', section, span]) + self.cli_set(['interfaces', section, span]) def tearDown(self): - # Ethernet is handled in its derived class - if 'ethernet' not in self._base_path: - self.session.delete(self._base_path) - # Tear down mirror interfaces for SPAN (Switch Port Analyzer) for span in self._mirror_interfaces: section = Section.section(span) - self.session.delete(['interfaces', section, span]) + self.cli_delete(['interfaces', section, span]) - self.session.commit() - del self.session + 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()) def test_span_mirror(self): if not self._mirror_interfaces: - return None + self.skipTest('not supported') # Check the two-way mirror rules of ingress and egress for mirror in self._mirror_interfaces: for interface in self._interfaces: - self.session.set(self._base_path + [interface, 'mirror', 'ingress', mirror]) - self.session.set(self._base_path + [interface, 'mirror', 'egress', mirror]) + self.cli_set(self._base_path + [interface, 'mirror', 'ingress', mirror]) + self.cli_set(self._base_path + [interface, 'mirror', 'egress', mirror]) - self.session.commit() + self.cli_commit() # Verify config for mirror in self._mirror_interfaces: @@ -122,45 +113,69 @@ class BasicInterfaceTest: self.assertTrue(is_mirrored_to(interface, mirror, 'ffff')) self.assertTrue(is_mirrored_to(interface, mirror, '1')) + def test_interface_disable(self): + # Check if description can be added to interface and + # can be read back + for intf in self._interfaces: + self.cli_set(self._base_path + [intf, 'disable']) + for option in self._options.get(intf, []): + self.cli_set(self._base_path + [intf] + option.split()) + + self.cli_commit() + + # Validate interface description + for intf in self._interfaces: + self.assertEqual(Interface(intf).get_admin_state(), 'down') def test_interface_description(self): # Check if description can be added to interface and # can be read back for intf in self._interfaces: test_string=f'Description-Test-{intf}' - self.session.set(self._base_path + [intf, 'description', test_string]) + self.cli_set(self._base_path + [intf, 'description', test_string]) for option in self._options.get(intf, []): - self.session.set(self._base_path + [intf] + option.split()) + self.cli_set(self._base_path + [intf] + option.split()) - self.session.commit() + self.cli_commit() # Validate interface description for intf in self._interfaces: test_string=f'Description-Test-{intf}' tmp = read_file(f'/sys/class/net/{intf}/ifalias') - self.assertTrue(tmp, test_string) + self.assertEqual(tmp, test_string) + self.assertEqual(Interface(intf).get_alias(), test_string) + self.cli_delete(self._base_path + [intf, 'description']) + + self.cli_commit() + + # Validate remove interface description "empty" + for intf in self._interfaces: + tmp = read_file(f'/sys/class/net/{intf}/ifalias') + self.assertEqual(tmp, str()) + self.assertEqual(Interface(intf).get_alias(), str()) def test_add_single_ip_address(self): addr = '192.0.2.0/31' for intf in self._interfaces: - self.session.set(self._base_path + [intf, 'address', addr]) + self.cli_set(self._base_path + [intf, 'address', addr]) for option in self._options.get(intf, []): - self.session.set(self._base_path + [intf] + option.split()) + self.cli_set(self._base_path + [intf] + option.split()) - self.session.commit() + self.cli_commit() for intf in self._interfaces: self.assertTrue(is_intf_addr_assigned(intf, addr)) + self.assertEqual(Interface(intf).get_admin_state(), 'up') def test_add_multiple_ip_addresses(self): # Add address for intf in self._interfaces: for addr in self._test_addr: - self.session.set(self._base_path + [intf, 'address', addr]) + self.cli_set(self._base_path + [intf, 'address', addr]) for option in self._options.get(intf, []): - self.session.set(self._base_path + [intf] + option.split()) + self.cli_set(self._base_path + [intf] + option.split()) - self.session.commit() + self.cli_commit() # Validate address for intf in self._interfaces: @@ -175,15 +190,15 @@ class BasicInterfaceTest: def test_ipv6_link_local_address(self): # Common function for IPv6 link-local address assignemnts if not self._test_ipv6: - return None + self.skipTest('not supported') for interface in self._interfaces: base = self._base_path + [interface] for option in self._options.get(interface, []): - self.session.set(base + option.split()) + self.cli_set(base + option.split()) # after commit we must have an IPv6 link-local address - self.session.commit() + self.cli_commit() for interface in self._interfaces: for addr in ifaddresses(interface)[AF_INET6]: @@ -192,26 +207,26 @@ class BasicInterfaceTest: # disable IPv6 link-local address assignment for interface in self._interfaces: base = self._base_path + [interface] - self.session.set(base + ['ipv6', 'address', 'no-default-link-local']) + self.cli_set(base + ['ipv6', 'address', 'no-default-link-local']) # after commit we must have no IPv6 link-local address - self.session.commit() + self.cli_commit() for interface in self._interfaces: self.assertTrue(AF_INET6 not in ifaddresses(interface)) def test_interface_mtu(self): if not self._test_mtu: - return None + self.skipTest('not supported') for intf in self._interfaces: base = self._base_path + [intf] - self.session.set(base + ['mtu', self._mtu]) + self.cli_set(base + ['mtu', self._mtu]) for option in self._options.get(intf, []): - self.session.set(base + option.split()) + self.cli_set(base + option.split()) # commit interface changes - self.session.commit() + self.cli_commit() # verify changed MTU for intf in self._interfaces: @@ -222,21 +237,21 @@ class BasicInterfaceTest: # Testcase if MTU can be changed to 1200 on non IPv6 # enabled interfaces if not self._test_mtu: - return None + self.skipTest('not supported') old_mtu = self._mtu self._mtu = '1200' for intf in self._interfaces: base = self._base_path + [intf] - self.session.set(base + ['mtu', self._mtu]) - self.session.set(base + ['ipv6', 'address', 'no-default-link-local']) + self.cli_set(base + ['mtu', self._mtu]) + self.cli_set(base + ['ipv6', 'address', 'no-default-link-local']) for option in self._options.get(intf, []): - self.session.set(base + option.split()) + self.cli_set(base + option.split()) # commit interface changes - self.session.commit() + self.cli_commit() # verify changed MTU for intf in self._interfaces: @@ -245,22 +260,26 @@ class BasicInterfaceTest: self._mtu = old_mtu - def test_8021q_vlan_interfaces(self): + def test_vif_8021q_interfaces(self): + # XXX: This testcase is not allowed to run as first testcase, reason + # is the Wireless test will first load the wifi kernel hwsim module + # which creates a wlan0 and wlan1 interface which will fail the + # tearDown() test in the end that no interface is allowed to survive! if not self._test_vlan: - return None + self.skipTest('not supported') for interface in self._interfaces: base = self._base_path + [interface] for option in self._options.get(interface, []): - self.session.set(base + option.split()) + self.cli_set(base + option.split()) for vlan in self._vlan_range: base = self._base_path + [interface, 'vif', vlan] - self.session.set(base + ['mtu', self._mtu]) + self.cli_set(base + ['mtu', self._mtu]) for address in self._test_addr: - self.session.set(base + ['address', address]) + self.cli_set(base + ['address', address]) - self.session.commit() + self.cli_commit() for intf in self._interfaces: for vlan in self._vlan_range: @@ -270,29 +289,70 @@ class BasicInterfaceTest: tmp = read_file(f'/sys/class/net/{vif}/mtu') self.assertEqual(tmp, self._mtu) + self.assertEqual(Interface(vif).get_admin_state(), 'up') + + def test_vif_8021q_lower_up_down(self): + # Testcase for https://phabricator.vyos.net/T3349 + if not self._test_vlan: + self.skipTest('not supported') + + for interface in self._interfaces: + base = self._base_path + [interface] + for option in self._options.get(interface, []): + self.cli_set(base + option.split()) + + # disable the lower interface + self.cli_set(base + ['disable']) + + for vlan in self._vlan_range: + vlan_base = self._base_path + [interface, 'vif', vlan] + # disable the vlan interface + self.cli_set(vlan_base + ['disable']) + self.cli_commit() - def test_8021ad_qinq_vlan_interfaces(self): + # re-enable all lower interfaces + for interface in self._interfaces: + base = self._base_path + [interface] + self.cli_delete(base + ['disable']) + + self.cli_commit() + + # verify that the lower interfaces are admin up and the vlan + # interfaces are all admin down + for interface in self._interfaces: + self.assertEqual(Interface(interface).get_admin_state(), 'up') + + for vlan in self._vlan_range: + ifname = f'{interface}.{vlan}' + self.assertEqual(Interface(ifname).get_admin_state(), 'down') + + + def test_vif_s_8021ad_vlan_interfaces(self): + # XXX: This testcase is not allowed to run as first testcase, reason + # is the Wireless test will first load the wifi kernel hwsim module + # which creates a wlan0 and wlan1 interface which will fail the + # tearDown() test in the end that no interface is allowed to survive! if not self._test_qinq: - return None + self.skipTest('not supported') for interface in self._interfaces: base = self._base_path + [interface] for option in self._options.get(interface, []): - self.session.set(base + option.split()) + self.cli_set(base + option.split()) for vif_s in self._qinq_range: for vif_c in self._vlan_range: base = self._base_path + [interface, 'vif-s', vif_s, 'vif-c', vif_c] - self.session.set(base + ['mtu', self._mtu]) + self.cli_set(base + ['mtu', self._mtu]) for address in self._test_addr: - self.session.set(base + ['address', address]) + self.cli_set(base + ['address', address]) - self.session.commit() + self.cli_commit() for interface in self._interfaces: for vif_s in self._qinq_range: - tmp = json.loads(cmd(f'ip -d -j link show dev {interface}.{vif_s}'))[0] + tmp = get_interface_config(f'{interface}.{vif_s}') self.assertEqual(dict_search('linkinfo.info_data.protocol', tmp), '802.1ad') for vif_c in self._vlan_range: @@ -305,26 +365,26 @@ class BasicInterfaceTest: def test_interface_ip_options(self): if not self._test_ip: - return None + self.skipTest('not supported') for interface in self._interfaces: arp_tmo = '300' path = self._base_path + [interface] for option in self._options.get(interface, []): - self.session.set(path + option.split()) + self.cli_set(path + option.split()) # Options - self.session.set(path + ['ip', 'arp-cache-timeout', arp_tmo]) - self.session.set(path + ['ip', 'disable-arp-filter']) - self.session.set(path + ['ip', 'disable-forwarding']) - self.session.set(path + ['ip', 'enable-arp-accept']) - self.session.set(path + ['ip', 'enable-arp-announce']) - self.session.set(path + ['ip', 'enable-arp-ignore']) - self.session.set(path + ['ip', 'enable-proxy-arp']) - self.session.set(path + ['ip', 'proxy-arp-pvlan']) - self.session.set(path + ['ip', 'source-validation', 'loose']) - - self.session.commit() + self.cli_set(path + ['ip', 'arp-cache-timeout', arp_tmo]) + self.cli_set(path + ['ip', 'disable-arp-filter']) + self.cli_set(path + ['ip', 'disable-forwarding']) + self.cli_set(path + ['ip', 'enable-arp-accept']) + self.cli_set(path + ['ip', 'enable-arp-announce']) + self.cli_set(path + ['ip', 'enable-arp-ignore']) + self.cli_set(path + ['ip', 'enable-proxy-arp']) + self.cli_set(path + ['ip', 'proxy-arp-pvlan']) + self.cli_set(path + ['ip', 'source-validation', 'loose']) + + self.cli_commit() for interface in self._interfaces: tmp = read_file(f'/proc/sys/net/ipv4/neigh/{interface}/base_reachable_time_ms') @@ -356,19 +416,19 @@ class BasicInterfaceTest: def test_interface_ipv6_options(self): if not self._test_ipv6: - return None + self.skipTest('not supported') for interface in self._interfaces: dad_transmits = '10' path = self._base_path + [interface] for option in self._options.get(interface, []): - self.session.set(path + option.split()) + self.cli_set(path + option.split()) # Options - self.session.set(path + ['ipv6', 'disable-forwarding']) - self.session.set(path + ['ipv6', 'dup-addr-detect-transmits', dad_transmits]) + self.cli_set(path + ['ipv6', 'disable-forwarding']) + self.cli_set(path + ['ipv6', 'dup-addr-detect-transmits', dad_transmits]) - self.session.commit() + self.cli_commit() for interface in self._interfaces: tmp = read_file(f'/proc/sys/net/ipv6/conf/{interface}/forwarding') @@ -377,40 +437,156 @@ class BasicInterfaceTest: tmp = read_file(f'/proc/sys/net/ipv6/conf/{interface}/dad_transmits') self.assertEqual(dad_transmits, tmp) + def test_dhcpv6_clinet_options(self): + if not self._test_ipv6_dhcpc6: + self.skipTest('not supported') - def test_ipv6_dhcpv6_prefix_delegation(self): - if not self._test_ipv6: - return None + duid_base = 10 + for interface in self._interfaces: + duid = '00:01:00:01:27:71:db:f0:00:50:00:00:00:{}'.format(duid_base) + path = self._base_path + [interface] + for option in self._options.get(interface, []): + self.cli_set(path + option.split()) + + # Enable DHCPv6 client + self.cli_set(path + ['address', 'dhcpv6']) + self.cli_set(path + ['dhcpv6-options', 'rapid-commit']) + self.cli_set(path + ['dhcpv6-options', 'parameters-only']) + self.cli_set(path + ['dhcpv6-options', 'duid', duid]) + duid_base += 1 + + self.cli_commit() + + duid_base = 10 + for interface in self._interfaces: + duid = '00:01:00:01:27:71:db:f0:00:50:00:00:00:{}'.format(duid_base) + dhcpc6_config = read_file(f'/run/dhcp6c/dhcp6c.{interface}.conf') + self.assertIn(f'interface {interface} ' + '{', dhcpc6_config) + self.assertIn(f' request domain-name-servers;', dhcpc6_config) + self.assertIn(f' request domain-name;', dhcpc6_config) + self.assertIn(f' information-only;', dhcpc6_config) + self.assertIn(f' send ia-na 0;', dhcpc6_config) + self.assertIn(f' send rapid-commit;', dhcpc6_config) + self.assertIn(f' send client-id {duid};', dhcpc6_config) + self.assertIn('};', dhcpc6_config) + duid_base += 1 + + # Check for running process + self.assertTrue(process_named_running('dhcp6c')) + + def test_dhcpv6pd_auto_sla_id(self): + if not self._test_ipv6_pd: + self.skipTest('not supported') + + prefix_len = '56' + sla_len = str(64 - int(prefix_len)) + + delegatees = ['dum2340', 'dum2341', 'dum2342', 'dum2343', 'dum2344'] - address = '1' - sla_id = '0' - sla_len = '8' for interface in self._interfaces: path = self._base_path + [interface] for option in self._options.get(interface, []): - self.session.set(path + option.split()) + self.cli_set(path + option.split()) + address = '1' # prefix delegation stuff pd_base = path + ['dhcpv6-options', 'pd', '0'] - self.session.set(pd_base + ['length', '56']) - self.session.set(pd_base + ['interface', interface, 'address', address]) - self.session.set(pd_base + ['interface', interface, 'sla-id', sla_id]) + self.cli_set(pd_base + ['length', prefix_len]) + + for delegatee in delegatees: + section = Section.section(delegatee) + self.cli_set(['interfaces', section, delegatee]) + self.cli_set(pd_base + ['interface', delegatee, 'address', address]) + # increment interface address + address = str(int(address) + 1) - self.session.commit() + self.cli_commit() for interface in self._interfaces: + dhcpc6_config = read_file(f'/run/dhcp6c/dhcp6c.{interface}.conf') + # verify DHCPv6 prefix delegation - # will return: ['delegation', '::/56 infinity;'] - tmp = get_dhcp6c_config_value(interface, 'prefix')[1].split()[0] # mind the whitespace - self.assertEqual(tmp, '::/56') - tmp = get_dhcp6c_config_value(interface, 'prefix-interface')[0].split()[0] - self.assertEqual(tmp, interface) - tmp = get_dhcp6c_config_value(interface, 'ifid')[0] - self.assertEqual(tmp, address) - tmp = get_dhcp6c_config_value(interface, 'sla-id')[0] - self.assertEqual(tmp, sla_id) - tmp = get_dhcp6c_config_value(interface, 'sla-len')[0] - self.assertEqual(tmp, sla_len) + self.assertIn(f'prefix ::/{prefix_len} infinity;', dhcpc6_config) + + address = '1' + sla_id = '0' + for delegatee in delegatees: + self.assertIn(f'prefix-interface {delegatee}' + r' {', dhcpc6_config) + self.assertIn(f'ifid {address};', dhcpc6_config) + self.assertIn(f'sla-id {sla_id};', dhcpc6_config) + self.assertIn(f'sla-len {sla_len};', dhcpc6_config) + + # increment sla-id + sla_id = str(int(sla_id) + 1) + # increment interface address + address = str(int(address) + 1) # Check for running process self.assertTrue(process_named_running('dhcp6c')) + + for delegatee in delegatees: + # we can already cleanup the test delegatee interface here + # as until commit() is called, nothing happens + section = Section.section(delegatee) + self.cli_delete(['interfaces', section, delegatee]) + + def test_dhcpv6pd_manual_sla_id(self): + if not self._test_ipv6_pd: + self.skipTest('not supported') + + prefix_len = '56' + sla_len = str(64 - int(prefix_len)) + + delegatees = ['dum3340', 'dum3341', 'dum3342', 'dum3343', 'dum3344'] + + for interface in self._interfaces: + path = self._base_path + [interface] + for option in self._options.get(interface, []): + self.cli_set(path + option.split()) + + # prefix delegation stuff + address = '1' + sla_id = '1' + pd_base = path + ['dhcpv6-options', 'pd', '0'] + self.cli_set(pd_base + ['length', prefix_len]) + + for delegatee in delegatees: + section = Section.section(delegatee) + self.cli_set(['interfaces', section, delegatee]) + self.cli_set(pd_base + ['interface', delegatee, 'address', address]) + self.cli_set(pd_base + ['interface', delegatee, 'sla-id', sla_id]) + + # increment interface address + address = str(int(address) + 1) + sla_id = str(int(sla_id) + 1) + + self.cli_commit() + + # Verify dhcpc6 client configuration + for interface in self._interfaces: + address = '1' + sla_id = '1' + dhcpc6_config = read_file(f'/run/dhcp6c/dhcp6c.{interface}.conf') + + # verify DHCPv6 prefix delegation + self.assertIn(f'prefix ::/{prefix_len} infinity;', dhcpc6_config) + + for delegatee in delegatees: + self.assertIn(f'prefix-interface {delegatee}' + r' {', dhcpc6_config) + self.assertIn(f'ifid {address};', dhcpc6_config) + self.assertIn(f'sla-id {sla_id};', dhcpc6_config) + self.assertIn(f'sla-len {sla_len};', dhcpc6_config) + + # increment sla-id + sla_id = str(int(sla_id) + 1) + # increment interface address + address = str(int(address) + 1) + + # Check for running process + self.assertTrue(process_named_running('dhcp6c')) + + for delegatee in delegatees: + # we can already cleanup the test delegatee interface here + # as until commit() is called, nothing happens + section = Section.section(delegatee) + self.cli_delete(['interfaces', section, delegatee]) diff --git a/smoketest/scripts/cli/base_vyostest_shim.py b/smoketest/scripts/cli/base_vyostest_shim.py new file mode 100644 index 000000000..18e49f47f --- /dev/null +++ b/smoketest/scripts/cli/base_vyostest_shim.py @@ -0,0 +1,90 @@ +# 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 <http://www.gnu.org/licenses/>. + +import os +import unittest + +from time import sleep + +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError +from vyos import ConfigError +from vyos.util import cmd + +save_config = '/tmp/vyos-smoketest-save' + +# This class acts as shim between individual Smoketests developed for VyOS and +# the Python UnitTest framework. Before every test is loaded, we dump the current +# system configuration and reload it after the test - despite the test results. +# +# Using this approach we can not render a live system useless while running any +# kind of smoketest. In addition it adds debug capabilities like printing the +# command used to execute the test. +class VyOSUnitTestSHIM: + class TestCase(unittest.TestCase): + # if enabled in derived class, print out each and every set/del command + # on the CLI. This is usefull to grap all the commands required to + # trigger the certain failure condition. + # Use "self.debug = True" in derived classes setUp() method + debug = False + + @classmethod + def setUpClass(cls): + cls._session = ConfigSession(os.getpid()) + cls._session.save_config(save_config) + pass + + @classmethod + def tearDownClass(cls): + # discard any pending changes which might caused a messed up config + cls._session.discard() + # ... and restore the initial state + cls._session.migrate_and_load_config(save_config) + + try: + cls._session.commit() + except (ConfigError, ConfigSessionError): + cls._session.discard() + cls.fail(cls) + + def cli_set(self, config): + if self.debug: + print('set ' + ' '.join(config)) + self._session.set(config) + + def cli_delete(self, config): + if self.debug: + print('del ' + ' '.join(config)) + self._session.delete(config) + + def cli_commit(self): + self._session.commit() + + def getFRRconfig(self, string, end='$', endsection='^!'): + """ Retrieve current "running configuration" from FRR """ + command = f'vtysh -c "show run" | sed -n "/^{string}{end}/,/{endsection}/p"' + + count = 0 + tmp = '' + while count < 10 and tmp == '': + # Let FRR settle after a config change first before harassing it again + sleep(1) + tmp = cmd(command) + count += 1 + + if self.debug or tmp == '': + import pprint + print(f'\n\ncommand "{command}" returned:\n') + pprint.pprint(tmp) + return tmp diff --git a/smoketest/scripts/cli/test_interfaces_bonding.py b/smoketest/scripts/cli/test_interfaces_bonding.py index a35682b7c..03cdafb8d 100755 --- a/smoketest/scripts/cli/test_interfaces_bonding.py +++ b/smoketest/scripts/cli/test_interfaces_bonding.py @@ -24,32 +24,36 @@ from vyos.ifconfig.interface import Interface from vyos.configsession import ConfigSessionError from vyos.util import read_file -class BondingInterfaceTest(BasicInterfaceTest.BaseTest): - def setUp(self): - self._test_mtu = True - self._test_vlan = True - self._test_qinq = True - self._test_ipv6 = True - self._base_path = ['interfaces', 'bonding'] - self._interfaces = ['bond0'] - self._mirror_interfaces = ['dum21354'] - self._members = [] +class BondingInterfaceTest(BasicInterfaceTest.TestCase): + @classmethod + def setUpClass(cls): + cls._test_ip = True + cls._test_ipv6 = True + cls._test_ipv6_pd = True + cls._test_ipv6_dhcpc6 = True + cls._test_mtu = True + cls._test_vlan = True + cls._test_qinq = True + cls._base_path = ['interfaces', 'bonding'] + cls._interfaces = ['bond0'] + cls._mirror_interfaces = ['dum21354'] + cls._members = [] # we need to filter out VLAN interfaces identified by a dot (.) # in their name - just in case! if 'TEST_ETH' in os.environ: - self._members = os.environ['TEST_ETH'].split() + cls._members = os.environ['TEST_ETH'].split() else: - for tmp in Section.interfaces("ethernet"): + for tmp in Section.interfaces('ethernet'): if not '.' in tmp: - self._members.append(tmp) + cls._members.append(tmp) - self._options['bond0'] = [] - for member in self._members: - self._options['bond0'].append(f'member interface {member}') - - super().setUp() + cls._options['bond0'] = [] + for member in cls._members: + cls._options['bond0'].append(f'member interface {member}') + # call base-classes classmethod + super(cls, cls).setUpClass() def test_add_single_ip_address(self): super().test_add_single_ip_address() @@ -58,8 +62,8 @@ class BondingInterfaceTest(BasicInterfaceTest.BaseTest): slaves = read_file(f'/sys/class/net/{interface}/bonding/slaves').split() self.assertListEqual(slaves, self._members) - def test_8021q_vlan_interfaces(self): - super().test_8021q_vlan_interfaces() + def test_vif_8021q_interfaces(self): + super().test_vif_8021q_interfaces() for interface in self._interfaces: slaves = read_file(f'/sys/class/net/{interface}/bonding/slaves').split() @@ -73,16 +77,16 @@ class BondingInterfaceTest(BasicInterfaceTest.BaseTest): # configure member interfaces for interface in self._interfaces: for option in self._options.get(interface, []): - self.session.set(self._base_path + [interface] + option.split()) + self.cli_set(self._base_path + [interface] + option.split()) - self.session.commit() + self.cli_commit() # remove single bond member port for interface in self._interfaces: remove_member = self._members[0] - self.session.delete(self._base_path + [interface, 'member', 'interface', remove_member]) + self.cli_delete(self._base_path + [interface, 'member', 'interface', remove_member]) - self.session.commit() + self.cli_commit() # removed member port must be admin-up for interface in self._interfaces: diff --git a/smoketest/scripts/cli/test_interfaces_bridge.py b/smoketest/scripts/cli/test_interfaces_bridge.py index 7444701c1..21f20c781 100755 --- a/smoketest/scripts/cli/test_interfaces_bridge.py +++ b/smoketest/scripts/cli/test_interfaces_bridge.py @@ -25,72 +25,126 @@ from netifaces import interfaces from vyos.ifconfig import Section from vyos.util import cmd from vyos.util import read_file - -class BridgeInterfaceTest(BasicInterfaceTest.BaseTest): - def setUp(self): - self._test_ipv6 = True - self._test_vlan = True - self._test_qinq = True - self._base_path = ['interfaces', 'bridge'] - self._mirror_interfaces = ['dum21354'] - self._members = [] +from vyos.util import get_interface_config +from vyos.validate import is_intf_addr_assigned + +class BridgeInterfaceTest(BasicInterfaceTest.TestCase): + @classmethod + def setUpClass(cls): + cls._test_ip = True + cls._test_ipv6 = True + cls._test_ipv6_pd = True + cls._test_ipv6_dhcpc6 = True + cls._test_vlan = True + cls._base_path = ['interfaces', 'bridge'] + cls._mirror_interfaces = ['dum21354'] + cls._members = [] # we need to filter out VLAN interfaces identified by a dot (.) # in their name - just in case! if 'TEST_ETH' in os.environ: - self._members = os.environ['TEST_ETH'].split() + cls._members = os.environ['TEST_ETH'].split() else: - for tmp in Section.interfaces("ethernet"): + for tmp in Section.interfaces('ethernet'): if not '.' in tmp: - self._members.append(tmp) + cls._members.append(tmp) + + cls._options['br0'] = [] + for member in cls._members: + cls._options['br0'].append(f'member interface {member}') + cls._interfaces = list(cls._options) + + # call base-classes classmethod + super(cls, cls).setUpClass() + + def tearDown(self): + for intf in self._interfaces: + self.cli_delete(self._base_path + [intf]) + + super().tearDown() + + def test_isolated_interfaces(self): + # Add member interfaces to bridge and set STP cost/priority + for interface in self._interfaces: + base = self._base_path + [interface] + self.cli_set(base + ['stp']) - self._options['br0'] = [] - for member in self._members: - self._options['br0'].append(f'member interface {member}') - self._interfaces = list(self._options) + # assign members to bridge interface + for member in self._members: + base_member = base + ['member', 'interface', member] + self.cli_set(base_member + ['isolated']) + + # commit config + self.cli_commit() + + for interface in self._interfaces: + tmp = get_interface_config(interface) + # STP must be enabled as configured above + self.assertEqual(1, tmp['linkinfo']['info_data']['stp_state']) + + # validate member interface configuration + for member in self._members: + tmp = get_interface_config(member) + # Isolated must be enabled as configured above + self.assertTrue(tmp['linkinfo']['info_slave_data']['isolated']) - super().setUp() def test_add_remove_bridge_member(self): # Add member interfaces to bridge and set STP cost/priority for interface in self._interfaces: base = self._base_path + [interface] - self.session.set(base + ['stp']) - self.session.set(base + ['address', '192.0.2.1/24']) + self.cli_set(base + ['stp']) + self.cli_set(base + ['address', '192.0.2.1/24']) cost = 1000 priority = 10 # assign members to bridge interface for member in self._members: base_member = base + ['member', 'interface', member] - self.session.set(base_member + ['cost', str(cost)]) - self.session.set(base_member + ['priority', str(priority)]) + self.cli_set(base_member + ['cost', str(cost)]) + self.cli_set(base_member + ['priority', str(priority)]) cost += 1 priority += 1 # commit config - self.session.commit() + self.cli_commit() - # check member interfaces are added on the bridge - bridge_members = [] - for tmp in glob(f'/sys/class/net/{interface}/lower_*'): - bridge_members.append(os.path.basename(tmp).replace('lower_', '')) + # Add member interfaces to bridge and set STP cost/priority + for interface in self._interfaces: + cost = 1000 + priority = 10 + for member in self._members: + tmp = get_interface_config(member) + self.assertEqual(interface, tmp['master']) + self.assertFalse( tmp['linkinfo']['info_slave_data']['isolated']) + self.assertEqual(cost, tmp['linkinfo']['info_slave_data']['cost']) + self.assertEqual(priority, tmp['linkinfo']['info_slave_data']['priority']) - for member in self._members: - self.assertIn(member, bridge_members) + cost += 1 + priority += 1 - # delete all members + + def test_vif_8021q_interfaces(self): for interface in self._interfaces: - self.session.delete(self._base_path + [interface, 'member']) + base = self._base_path + [interface] + self.cli_set(base + ['enable-vlan']) + super().test_vif_8021q_interfaces() - self.session.commit() + def test_vif_8021q_lower_up_down(self): + for interface in self._interfaces: + base = self._base_path + [interface] + self.cli_set(base + ['enable-vlan']) + super().test_vif_8021q_interfaces() def test_bridge_vlan_filter(self): + vif_vlan = 2 # Add member interface to bridge and set VLAN filter for interface in self._interfaces: base = self._base_path + [interface] - self.session.set(base + ['vif', '1', 'address', '192.0.2.1/24']) - self.session.set(base + ['vif', '2', 'address', '192.0.3.1/24']) + self.cli_set(base + ['enable-vlan']) + self.cli_set(base + ['address', '192.0.2.1/24']) + self.cli_set(base + ['vif', str(vif_vlan), 'address', '192.0.3.1/24']) + self.cli_set(base + ['vif', str(vif_vlan), 'mtu', self._mtu]) vlan_id = 101 allowed_vlan = 2 @@ -98,13 +152,13 @@ class BridgeInterfaceTest(BasicInterfaceTest.BaseTest): # assign members to bridge interface for member in self._members: base_member = base + ['member', 'interface', member] - self.session.set(base_member + ['allowed-vlan', str(allowed_vlan)]) - self.session.set(base_member + ['allowed-vlan', allowed_vlan_range]) - self.session.set(base_member + ['native-vlan', str(vlan_id)]) + self.cli_set(base_member + ['allowed-vlan', str(allowed_vlan)]) + self.cli_set(base_member + ['allowed-vlan', allowed_vlan_range]) + self.cli_set(base_member + ['native-vlan', str(vlan_id)]) vlan_id += 1 # commit config - self.session.commit() + self.cli_commit() # Detect the vlan filter function for interface in self._interfaces: @@ -160,7 +214,7 @@ class BridgeInterfaceTest(BasicInterfaceTest.BaseTest): # delete all members for interface in self._interfaces: - self.session.delete(self._base_path + [interface, 'member']) + self.cli_delete(self._base_path + [interface, 'member']) def test_bridge_vlan_members(self): @@ -169,10 +223,10 @@ class BridgeInterfaceTest(BasicInterfaceTest.BaseTest): for interface in self._interfaces: for member in self._members: for vif in vifs: - self.session.set(['interfaces', 'ethernet', member, 'vif', vif]) - self.session.set(['interfaces', 'bridge', interface, 'member', 'interface', f'{member}.{vif}']) + self.cli_set(['interfaces', 'ethernet', member, 'vif', vif]) + self.cli_set(['interfaces', 'bridge', interface, 'member', 'interface', f'{member}.{vif}']) - self.session.commit() + self.cli_commit() # Verify config for interface in self._interfaces: @@ -181,10 +235,12 @@ class BridgeInterfaceTest(BasicInterfaceTest.BaseTest): # member interface must be assigned to the bridge self.assertTrue(os.path.exists(f'/sys/class/net/{interface}/lower_{member}.{vif}')) - # remove VLAN interfaces - for vif in vifs: - self.session.delete(['interfaces', 'ethernet', member, 'vif', vif]) + # delete all members + for interface in self._interfaces: + for member in self._members: + for vif in vifs: + self.cli_delete(['interfaces', 'ethernet', member, 'vif', vif]) + self.cli_delete(['interfaces', 'bridge', interface, 'member', 'interface', f'{member}.{vif}']) if __name__ == '__main__': - unittest.main(verbosity=2, failfast=True) - + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_dummy.py b/smoketest/scripts/cli/test_interfaces_dummy.py index c482a6f0b..dedc6fe05 100755 --- a/smoketest/scripts/cli/test_interfaces_dummy.py +++ b/smoketest/scripts/cli/test_interfaces_dummy.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -18,11 +18,13 @@ import unittest from base_interfaces_test import BasicInterfaceTest -class DummyInterfaceTest(BasicInterfaceTest.BaseTest): - def setUp(self): - self._base_path = ['interfaces', 'dummy'] - self._interfaces = ['dum0', 'dum1', 'dum2'] - super().setUp() +class DummyInterfaceTest(BasicInterfaceTest.TestCase): + @classmethod + def setUpClass(cls): + cls._base_path = ['interfaces', 'dummy'] + cls._interfaces = ['dum435', 'dum8677', 'dum0931', 'dum089'] + # call base-classes classmethod + super(cls, cls).setUpClass() if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_ethernet.py b/smoketest/scripts/cli/test_interfaces_ethernet.py index 3c4796283..cb0c8a426 100755 --- a/smoketest/scripts/cli/test_interfaces_ethernet.py +++ b/smoketest/scripts/cli/test_interfaces_ethernet.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -19,6 +19,7 @@ import re import unittest from base_interfaces_test import BasicInterfaceTest +from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section from vyos.util import cmd from vyos.util import process_named_running @@ -33,37 +34,35 @@ def get_wpa_supplicant_value(interface, key): tmp = re.findall(r'\n?{}=(.*)'.format(key), tmp) return tmp[0] -class EthernetInterfaceTest(BasicInterfaceTest.BaseTest): - def setUp(self): - self._test_ip = True - self._test_mtu = True - self._test_vlan = True - self._test_qinq = True - self._test_ipv6 = True - self._base_path = ['interfaces', 'ethernet'] - self._mirror_interfaces = ['dum21354'] +class EthernetInterfaceTest(BasicInterfaceTest.TestCase): + @classmethod + def setUpClass(cls): + cls._test_ip = True + cls._test_ipv6 = True + cls._test_ipv6_pd = True + cls._test_ipv6_dhcpc6 = True + cls._test_mtu = True + cls._test_vlan = True + cls._test_qinq = True + cls._base_path = ['interfaces', 'ethernet'] + cls._mirror_interfaces = ['dum21354'] # we need to filter out VLAN interfaces identified by a dot (.) # in their name - just in case! if 'TEST_ETH' in os.environ: tmp = os.environ['TEST_ETH'].split() - self._interfaces = tmp + cls._interfaces = tmp else: - for tmp in Section.interfaces("ethernet"): + for tmp in Section.interfaces('ethernet'): if not '.' in tmp: - self._interfaces.append(tmp) + cls._interfaces.append(tmp) - self._macs = {} - for interface in self._interfaces: - try: - mac = self.session.show_config(self._base_path + - [interface, 'hw-id']).split()[1] - except: - # during initial system startup there is no hw-id node - mac = read_file(f'/sys/class/net/{interface}/address') - self._macs[interface] = mac + cls._macs = {} + for interface in cls._interfaces: + cls._macs[interface] = read_file(f'/sys/class/net/{interface}/address') - super().setUp() + # call base-classes classmethod + super(cls, cls).setUpClass() def tearDown(self): @@ -71,31 +70,34 @@ class EthernetInterfaceTest(BasicInterfaceTest.BaseTest): # when using a dedicated interface to test via TEST_ETH environment # variable only this one will be cleared in the end - usable to test # ethernet interfaces via SSH - self.session.delete(self._base_path + [interface]) - self.session.set(self._base_path + [interface, 'duplex', 'auto']) - self.session.set(self._base_path + [interface, 'speed', 'auto']) - self.session.set(self._base_path + [interface, 'hw-id', self._macs[interface]]) + self.cli_delete(self._base_path + [interface]) + self.cli_set(self._base_path + [interface, 'duplex', 'auto']) + self.cli_set(self._base_path + [interface, 'speed', 'auto']) + self.cli_set(self._base_path + [interface, 'hw-id', self._macs[interface]]) - super().tearDown() + # Tear down mirror interfaces for SPAN (Switch Port Analyzer) + for span in self._mirror_interfaces: + section = Section.section(span) + self.cli_delete(['interfaces', section, span]) + self.cli_commit() def test_dhcp_disable_interface(self): # When interface is configured as admin down, it must be admin down # even when dhcpc starts on the given interface for interface in self._interfaces: - self.session.set(self._base_path + [interface, 'disable']) + self.cli_set(self._base_path + [interface, 'disable']) # Also enable DHCP (ISC DHCP always places interface in admin up # state so we check that we do not start DHCP client. # https://phabricator.vyos.net/T2767 - self.session.set(self._base_path + [interface, 'address', 'dhcp']) + self.cli_set(self._base_path + [interface, 'address', 'dhcp']) - self.session.commit() + self.cli_commit() # Validate interface state for interface in self._interfaces: - with open(f'/sys/class/net/{interface}/flags', 'r') as f: - flags = f.read() + flags = read_file(f'/sys/class/net/{interface}/flags') self.assertEqual(int(flags, 16) & 1, 0) def test_offloading_rps(self): @@ -111,9 +113,9 @@ class EthernetInterfaceTest(BasicInterfaceTest.BaseTest): rps_cpus &= ~1 for interface in self._interfaces: - self.session.set(self._base_path + [interface, 'offload', 'rps']) + self.cli_set(self._base_path + [interface, 'offload', 'rps']) - self.session.commit() + self.cli_commit() for interface in self._interfaces: cpus = read_file('/sys/class/net/eth1/queues/rx-0/rps_cpus') @@ -123,15 +125,37 @@ class EthernetInterfaceTest(BasicInterfaceTest.BaseTest): self.assertEqual(f'{cpus:x}', f'{rps_cpus:x}') + def test_non_existing_interface(self): + unknonw_interface = self._base_path + ['eth667'] + self.cli_set(unknonw_interface) + + # check validate() - interface does not exist + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + # we need to remove this wrong interface from the configuration + # manually, else tearDown() will have problem in commit() + self.cli_delete(unknonw_interface) + + def test_speed_duplex_verify(self): + for interface in self._interfaces: + self.cli_set(self._base_path + [interface, 'speed', '1000']) + + # check validate() - if either speed or duplex is not auto, the + # other one must be manually configured, too + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'speed', 'auto']) + self.cli_commit() def test_eapol_support(self): for interface in self._interfaces: # Enable EAPoL - self.session.set(self._base_path + [interface, 'eapol', 'ca-cert-file', ca_cert]) - self.session.set(self._base_path + [interface, 'eapol', 'cert-file', ssl_cert]) - self.session.set(self._base_path + [interface, 'eapol', 'key-file', ssl_key]) + self.cli_set(self._base_path + [interface, 'eapol', 'ca-cert-file', ca_cert]) + self.cli_set(self._base_path + [interface, 'eapol', 'cert-file', ssl_cert]) + self.cli_set(self._base_path + [interface, 'eapol', 'key-file', ssl_key]) - self.session.commit() + self.cli_commit() # Check for running process self.assertTrue(process_named_running('wpa_supplicant')) @@ -169,12 +193,12 @@ if __name__ == '__main__': # Generate mandatory SSL certificate tmp = f'openssl req -newkey rsa:4096 -new -nodes -x509 -days 3650 '\ f'-keyout {ssl_key} -out {ssl_cert} -subj {subject}' - print(cmd(tmp)) + cmd(tmp) if not os.path.isfile(ca_cert): # Generate "CA" tmp = f'openssl req -new -x509 -key {ssl_key} -out {ca_cert} -subj {subject}' - print(cmd(tmp)) + cmd(tmp) for file in [ca_cert, ssl_cert, ssl_key]: cmd(f'sudo chown radius_priv_user:vyattacfg {file}') diff --git a/smoketest/scripts/cli/test_interfaces_geneve.py b/smoketest/scripts/cli/test_interfaces_geneve.py index 98f55210f..129ee71e5 100755 --- a/smoketest/scripts/cli/test_interfaces_geneve.py +++ b/smoketest/scripts/cli/test_interfaces_geneve.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -17,17 +17,65 @@ import unittest from vyos.configsession import ConfigSession +from vyos.ifconfig import Interface +from vyos.util import get_interface_config + from base_interfaces_test import BasicInterfaceTest -class GeneveInterfaceTest(BasicInterfaceTest.BaseTest): - def setUp(self): - self._base_path = ['interfaces', 'geneve'] - self._options = { +class GeneveInterfaceTest(BasicInterfaceTest.TestCase): + @classmethod + def setUpClass(cls): + cls._test_ip = True + cls._test_ipv6 = True + cls._base_path = ['interfaces', 'geneve'] + cls._options = { 'gnv0': ['vni 10', 'remote 127.0.1.1'], 'gnv1': ['vni 20', 'remote 127.0.1.2'], + 'gnv1': ['vni 30', 'remote 2001:db8::1', 'parameters ipv6 flowlabel 0x1000'], } - self._interfaces = list(self._options) - super().setUp() + cls._interfaces = list(cls._options) + # call base-classes classmethod + super(cls, cls).setUpClass() + + def test_geneve_parameters(self): + tos = '40' + ttl = 20 + for intf in self._interfaces: + for option in self._options.get(intf, []): + self.cli_set(self._base_path + [intf] + option.split()) + + self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'dont-fragment']) + self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'tos', tos]) + self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'ttl', str(ttl)]) + ttl += 10 + + self.cli_commit() + + ttl = 20 + for interface in self._interfaces: + options = get_interface_config(interface) + + vni = options['linkinfo']['info_data']['id'] + self.assertIn(f'vni {vni}', self._options[interface]) + + if any('remote' in s for s in self._options[interface]): + key = 'remote' + if 'remote6' in options['linkinfo']['info_data']: + key = 'remote6' + + remote = options['linkinfo']['info_data'][key] + self.assertIn(f'remote {remote}', self._options[interface]) + + if any('flowlabel' in s for s in self._options[interface]): + label = options['linkinfo']['info_data']['label'] + self.assertIn(f'parameters ipv6 flowlabel {label}', self._options[interface]) + + self.assertEqual('geneve', options['linkinfo']['info_kind']) + self.assertEqual('set', options['linkinfo']['info_data']['df']) + self.assertEqual(f'0x{tos}', options['linkinfo']['info_data']['tos']) + self.assertEqual(ttl, options['linkinfo']['info_data']['ttl']) + self.assertEqual(Interface(interface).get_admin_state(), 'up') + ttl += 10 if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_l2tpv3.py b/smoketest/scripts/cli/test_interfaces_l2tpv3.py index c756bfdd5..24cb9464e 100755 --- a/smoketest/scripts/cli/test_interfaces_l2tpv3.py +++ b/smoketest/scripts/cli/test_interfaces_l2tpv3.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -20,21 +20,25 @@ import unittest from base_interfaces_test import BasicInterfaceTest from vyos.util import cmd -class GeneveInterfaceTest(BasicInterfaceTest.BaseTest): - def setUp(self): - self._base_path = ['interfaces', 'l2tpv3'] - self._options = { - 'l2tpeth10': ['local-ip 127.0.0.1', 'remote-ip 127.10.10.10', +class GeneveInterfaceTest(BasicInterfaceTest.TestCase): + @classmethod + def setUpClass(cls): + cls._test_ip = True + cls._test_ipv6 = True + cls._base_path = ['interfaces', 'l2tpv3'] + cls._options = { + 'l2tpeth10': ['source-address 127.0.0.1', 'remote 127.10.10.10', 'tunnel-id 100', 'peer-tunnel-id 10', 'session-id 100', 'peer-session-id 10', 'source-port 1010', 'destination-port 10101'], - 'l2tpeth20': ['local-ip 127.0.0.1', 'peer-session-id 20', - 'peer-tunnel-id 200', 'remote-ip 127.20.20.20', + 'l2tpeth20': ['source-address 127.0.0.1', 'peer-session-id 20', + 'peer-tunnel-id 200', 'remote 127.20.20.20', 'session-id 20', 'tunnel-id 200', 'source-port 2020', 'destination-port 20202'], } - self._interfaces = list(self._options) - super().setUp() + cls._interfaces = list(cls._options) + # call base-classes classmethod + super(cls, cls).setUpClass() def test_add_single_ip_address(self): super().test_add_single_ip_address() diff --git a/smoketest/scripts/cli/test_interfaces_loopback.py b/smoketest/scripts/cli/test_interfaces_loopback.py index 79225a1bd..85b5ca6d6 100755 --- a/smoketest/scripts/cli/test_interfaces_loopback.py +++ b/smoketest/scripts/cli/test_interfaces_loopback.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -17,25 +17,40 @@ import unittest from base_interfaces_test import BasicInterfaceTest +from netifaces import interfaces + from vyos.validate import is_intf_addr_assigned -class LoopbackInterfaceTest(BasicInterfaceTest.BaseTest): - def setUp(self): - super().setUp() - # these addresses are never allowed to be removed from the system - self._loopback_addresses = ['127.0.0.1', '::1'] - self._base_path = ['interfaces', 'loopback'] - self._interfaces = ['lo'] +loopbacks = ['127.0.0.1', '::1'] + +class LoopbackInterfaceTest(BasicInterfaceTest.TestCase): + @classmethod + def setUpClass(cls): + cls._base_path = ['interfaces', 'loopback'] + cls._interfaces = ['lo'] + # call base-classes classmethod + super(cls, cls).setUpClass() + + def tearDown(self): + self.cli_delete(self._base_path) + self.cli_commit() + + # loopback interface must persist! + for intf in self._interfaces: + self.assertIn(intf, interfaces()) def test_add_single_ip_address(self): super().test_add_single_ip_address() - for addr in self._loopback_addresses: + for addr in loopbacks: self.assertTrue(is_intf_addr_assigned('lo', addr)) def test_add_multiple_ip_addresses(self): super().test_add_multiple_ip_addresses() - for addr in self._loopback_addresses: + for addr in loopbacks: self.assertTrue(is_intf_addr_assigned('lo', addr)) + def test_interface_disable(self): + self.skipTest('not supported') + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_macsec.py b/smoketest/scripts/cli/test_interfaces_macsec.py index d9635951f..e4280a5b7 100755 --- a/smoketest/scripts/cli/test_interfaces_macsec.py +++ b/smoketest/scripts/cli/test_interfaces_macsec.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -14,6 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. +import os import re import unittest @@ -22,7 +23,9 @@ from netifaces import interfaces from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section +from vyos.util import cmd from vyos.util import read_file +from vyos.util import get_interface_config from vyos.util import process_named_running def get_config_value(interface, key): @@ -30,18 +33,26 @@ def get_config_value(interface, key): tmp = re.findall(r'\n?{}=(.*)'.format(key), tmp) return tmp[0] -class MACsecInterfaceTest(BasicInterfaceTest.BaseTest): - def setUp(self): - super().setUp() - self._base_path = ['interfaces', 'macsec'] - self._options = { 'macsec0': ['source-interface eth0', 'security cipher gcm-aes-128'] } +def get_cipher(interface): + tmp = get_interface_config(interface) + return tmp['linkinfo']['info_data']['cipher_suite'].lower() - # if we have a physical eth1 interface, add a second macsec instance - if 'eth1' in Section.interfaces("ethernet"): - macsec = { 'macsec1': [f'source-interface eth1', 'security cipher gcm-aes-128'] } - self._options.update(macsec) +class MACsecInterfaceTest(BasicInterfaceTest.TestCase): + @classmethod + def setUpClass(cls): + cls._test_ip = True + cls._test_ipv6 = True + cls._base_path = ['interfaces', 'macsec'] + cls._options = { 'macsec0': ['source-interface eth0', 'security cipher gcm-aes-128'] } - self._interfaces = list(self._options) + # if we have a physical eth1 interface, add a second macsec instance + if 'eth1' in Section.interfaces('ethernet'): + macsec = { 'macsec1': [f'source-interface eth1', 'security cipher gcm-aes-128'] } + cls._options.update(macsec) + + cls._interfaces = list(cls._options) + # call base-classes classmethod + super(cls, cls).setUpClass() def test_macsec_encryption(self): # MACsec can be operating in authentication and encryption mode - both @@ -57,31 +68,31 @@ class MACsecInterfaceTest(BasicInterfaceTest.BaseTest): if option.split()[0] == 'source-interface': src_interface = option.split()[1] - self.session.set(self._base_path + [interface] + option.split()) + self.cli_set(self._base_path + [interface] + option.split()) # Encrypt link - self.session.set(self._base_path + [interface, 'security', 'encrypt']) + self.cli_set(self._base_path + [interface, 'security', 'encrypt']) # check validate() - Physical source interface MTU must be higher then our MTU - self.session.set(self._base_path + [interface, 'mtu', '1500']) + self.cli_set(self._base_path + [interface, 'mtu', '1500']) with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.delete(self._base_path + [interface, 'mtu']) + self.cli_commit() + self.cli_delete(self._base_path + [interface, 'mtu']) # check validate() - MACsec security keys mandartory when encryption is enabled with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(self._base_path + [interface, 'security', 'mka', 'cak', mak_cak]) + self.cli_commit() + self.cli_set(self._base_path + [interface, 'security', 'mka', 'cak', mak_cak]) # check validate() - MACsec security keys mandartory when encryption is enabled with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(self._base_path + [interface, 'security', 'mka', 'ckn', mak_ckn]) + self.cli_commit() + self.cli_set(self._base_path + [interface, 'security', 'mka', 'ckn', mak_ckn]) - self.session.set(self._base_path + [interface, 'security', 'replay-window', replay_window]) + self.cli_set(self._base_path + [interface, 'security', 'replay-window', replay_window]) # final commit of settings - self.session.commit() + self.cli_commit() tmp = get_config_value(src_interface, 'macsec_integ_only') self.assertTrue("0" in tmp) @@ -105,23 +116,46 @@ class MACsecInterfaceTest(BasicInterfaceTest.BaseTest): # Check for running process self.assertTrue(process_named_running('wpa_supplicant')) - def test_macsec_mandatory_options(self): + def test_macsec_gcm_aes_128(self): interface = 'macsec1' - self.session.set(self._base_path + [interface]) + cipher = 'gcm-aes-128' + self.cli_set(self._base_path + [interface]) + + # check validate() - source interface is mandatory + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'source-interface', 'eth0']) + + # check validate() - cipher is mandatory + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'security', 'cipher', cipher]) + + # final commit and verify + self.cli_commit() + self.assertIn(interface, interfaces()) + self.assertIn(interface, interfaces()) + self.assertEqual(cipher, get_cipher(interface)) + + def test_macsec_gcm_aes_256(self): + interface = 'macsec4' + cipher = 'gcm-aes-256' + self.cli_set(self._base_path + [interface]) # check validate() - source interface is mandatory with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(self._base_path + [interface, 'source-interface', 'eth0']) + self.cli_commit() + self.cli_set(self._base_path + [interface, 'source-interface', 'eth0']) # check validate() - cipher is mandatory with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(self._base_path + [interface, 'security', 'cipher', 'gcm-aes-128']) + self.cli_commit() + self.cli_set(self._base_path + [interface, 'security', 'cipher', cipher]) # final commit and verify - self.session.commit() + self.cli_commit() self.assertIn(interface, interfaces()) + self.assertEqual(cipher, get_cipher(interface)) def test_macsec_source_interface(self): # Ensure source-interface can bot be part of any other bond or bridge @@ -131,24 +165,24 @@ class MACsecInterfaceTest(BasicInterfaceTest.BaseTest): for interface, option_value in self._options.items(): for option in option_value: - self.session.set(self._base_path + [interface] + option.split()) + self.cli_set(self._base_path + [interface] + option.split()) if option.split()[0] == 'source-interface': src_interface = option.split()[1] - self.session.set(base_bridge + ['member', 'interface', src_interface]) + self.cli_set(base_bridge + ['member', 'interface', src_interface]) # check validate() - Source interface must not already be a member of a bridge with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.delete(base_bridge) + self.cli_commit() + self.cli_delete(base_bridge) - self.session.set(base_bond + ['member', 'interface', src_interface]) + self.cli_set(base_bond + ['member', 'interface', src_interface]) # check validate() - Source interface must not already be a member of a bridge with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.delete(base_bond) + self.cli_commit() + self.cli_delete(base_bond) # final commit and verify - self.session.commit() + self.cli_commit() self.assertIn(interface, interfaces()) if __name__ == '__main__': diff --git a/smoketest/scripts/cli/test_interfaces_openvpn.py b/smoketest/scripts/cli/test_interfaces_openvpn.py index 00db3f667..655ee770d 100755 --- a/smoketest/scripts/cli/test_interfaces_openvpn.py +++ b/smoketest/scripts/cli/test_interfaces_openvpn.py @@ -21,6 +21,8 @@ from glob import glob from ipaddress import IPv4Network from netifaces import interfaces +from base_vyostest_shim import VyOSUnitTestSHIM + from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError from vyos.util import cmd @@ -58,85 +60,83 @@ def get_vrf(interface): tmp = tmp.replace('upper_', '') return tmp -class TestInterfacesOpenVPN(unittest.TestCase): +class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase): def setUp(self): - self.session = ConfigSession(os.getpid()) - self.session.set(['interfaces', 'dummy', dummy_if, 'address', '192.0.2.1/32']) - self.session.set(['vrf', 'name', vrf_name, 'table', '12345']) + self.cli_set(['interfaces', 'dummy', dummy_if, 'address', '192.0.2.1/32']) + self.cli_set(['vrf', 'name', vrf_name, 'table', '12345']) def tearDown(self): - self.session.delete(base_path) - self.session.delete(['interfaces', 'dummy', dummy_if]) - self.session.delete(['vrf']) - self.session.commit() - del self.session + self.cli_delete(base_path) + self.cli_delete(['interfaces', 'dummy', dummy_if]) + self.cli_delete(['vrf']) + self.cli_commit() def test_openvpn_client_verify(self): # Create OpenVPN client interface and test verify() steps. interface = 'vtun2000' path = base_path + [interface] - self.session.set(path + ['mode', 'client']) + self.cli_set(path + ['mode', 'client']) # check validate() - cannot specify both "encryption disable-ncp" and # "encryption ncp-ciphers" at the same time - self.session.set(path + ['encryption', 'disable-ncp']) - self.session.set(path + ['encryption', 'ncp-ciphers', 'aes192gcm']) + self.cli_set(path + ['encryption', 'disable-ncp']) + self.cli_set(path + ['encryption', 'ncp-ciphers', 'aes192gcm']) with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.delete(path + ['encryption', 'ncp-ciphers']) + self.cli_commit() + self.cli_delete(path + ['encryption', 'ncp-ciphers']) # check validate() - cannot specify local-port in client mode - self.session.set(path + ['local-port', '5000']) + self.cli_set(path + ['local-port', '5000']) with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.delete(path + ['local-port']) + self.cli_commit() + self.cli_delete(path + ['local-port']) # check validate() - cannot specify local-host in client mode - self.session.set(path + ['local-host', '127.0.0.1']) + self.cli_set(path + ['local-host', '127.0.0.1']) with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.delete(path + ['local-host']) + self.cli_commit() + self.cli_delete(path + ['local-host']) # check validate() - cannot specify protocol tcp-passive in client mode - self.session.set(path + ['protocol', 'tcp-passive']) + self.cli_set(path + ['protocol', 'tcp-passive']) with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.delete(path + ['protocol']) + self.cli_commit() + self.cli_delete(path + ['protocol']) # check validate() - remote-host must be set in client mode with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(path + ['remote-host', '192.0.9.9']) + self.cli_commit() + self.cli_set(path + ['remote-host', '192.0.9.9']) # check validate() - cannot specify "tls dh-file" in client mode - self.session.set(path + ['tls', 'dh-file', dh_pem]) + self.cli_set(path + ['tls', 'dh-file', dh_pem]) with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.delete(path + ['tls']) + self.cli_commit() + self.cli_delete(path + ['tls']) # check validate() - must specify one of "shared-secret-key-file" and "tls" with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(path + ['shared-secret-key-file', s2s_key]) + self.cli_commit() + self.cli_set(path + ['shared-secret-key-file', s2s_key]) # check validate() - must specify one of "shared-secret-key-file" and "tls" with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.delete(path + ['shared-secret-key-file', s2s_key]) + self.cli_commit() + self.cli_delete(path + ['shared-secret-key-file', s2s_key]) - self.session.set(path + ['tls', 'ca-cert-file', ca_cert]) - self.session.set(path + ['tls', 'cert-file', ssl_cert]) - self.session.set(path + ['tls', 'key-file', ssl_key]) + self.cli_set(path + ['tls', 'ca-cert-file', ca_cert]) + self.cli_set(path + ['tls', 'cert-file', ssl_cert]) + self.cli_set(path + ['tls', 'key-file', ssl_key]) # check validate() - can not have auth username without a password - self.session.set(path + ['authentication', 'username', 'vyos']) + self.cli_set(path + ['authentication', 'username', 'vyos']) with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(path + ['authentication', 'password', 'vyos']) + self.cli_commit() + self.cli_set(path + ['authentication', 'password', 'vyos']) # client commit must pass - self.session.commit() + self.cli_commit() self.assertTrue(process_named_running(PROCESS_NAME)) self.assertIn(interface, interfaces()) @@ -152,22 +152,22 @@ class TestInterfacesOpenVPN(unittest.TestCase): path = base_path + [interface] auth_hash = 'sha1' - self.session.set(path + ['device-type', 'tun']) - self.session.set(path + ['encryption', 'cipher', 'aes256']) - self.session.set(path + ['hash', auth_hash]) - self.session.set(path + ['mode', 'client']) - self.session.set(path + ['persistent-tunnel']) - self.session.set(path + ['protocol', protocol]) - self.session.set(path + ['remote-host', remote_host]) - self.session.set(path + ['remote-port', remote_port]) - self.session.set(path + ['tls', 'ca-cert-file', ca_cert]) - self.session.set(path + ['tls', 'cert-file', ssl_cert]) - self.session.set(path + ['tls', 'key-file', ssl_key]) - self.session.set(path + ['vrf', vrf_name]) - self.session.set(path + ['authentication', 'username', interface+'user']) - self.session.set(path + ['authentication', 'password', interface+'secretpw']) - - self.session.commit() + self.cli_set(path + ['device-type', 'tun']) + self.cli_set(path + ['encryption', 'cipher', 'aes256']) + self.cli_set(path + ['hash', auth_hash]) + self.cli_set(path + ['mode', 'client']) + self.cli_set(path + ['persistent-tunnel']) + self.cli_set(path + ['protocol', protocol]) + self.cli_set(path + ['remote-host', remote_host]) + self.cli_set(path + ['remote-port', remote_port]) + self.cli_set(path + ['tls', 'ca-cert-file', ca_cert]) + self.cli_set(path + ['tls', 'cert-file', ssl_cert]) + self.cli_set(path + ['tls', 'key-file', ssl_key]) + self.cli_set(path + ['vrf', vrf_name]) + self.cli_set(path + ['authentication', 'username', interface+'user']) + self.cli_set(path + ['authentication', 'password', interface+'secretpw']) + + self.cli_commit() for ii in num_range: interface = f'vtun{ii}' @@ -200,8 +200,8 @@ class TestInterfacesOpenVPN(unittest.TestCase): self.assertIn(f'{interface}secretpw', pw) # check that no interface remained after deleting them - self.session.delete(base_path) - self.session.commit() + self.cli_delete(base_path) + self.cli_commit() for ii in num_range: interface = f'vtun{ii}' @@ -213,104 +213,104 @@ class TestInterfacesOpenVPN(unittest.TestCase): path = base_path + [interface] # check validate() - must speciy operating mode - self.session.set(path) + self.cli_set(path) with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(path + ['mode', 'server']) + self.cli_commit() + self.cli_set(path + ['mode', 'server']) # check validate() - cannot specify protocol tcp-active in server mode - self.session.set(path + ['protocol', 'tcp-active']) + self.cli_set(path + ['protocol', 'tcp-active']) with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.delete(path + ['protocol']) + self.cli_commit() + self.cli_delete(path + ['protocol']) # check validate() - cannot specify local-port in client mode - self.session.set(path + ['remote-port', '5000']) + self.cli_set(path + ['remote-port', '5000']) with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.delete(path + ['remote-port']) + self.cli_commit() + self.cli_delete(path + ['remote-port']) # check validate() - cannot specify local-host in client mode - self.session.set(path + ['remote-host', '127.0.0.1']) + self.cli_set(path + ['remote-host', '127.0.0.1']) with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.delete(path + ['remote-host']) + self.cli_commit() + self.cli_delete(path + ['remote-host']) # check validate() - must specify "tls dh-file" when not using EC keys # in server mode with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(path + ['tls', 'dh-file', dh_pem]) + self.cli_commit() + self.cli_set(path + ['tls', 'dh-file', dh_pem]) # check validate() - must specify "server subnet" or add interface to # bridge in server mode with self.assertRaises(ConfigSessionError): - self.session.commit() + self.cli_commit() # check validate() - server client-ip-pool is too large # [100.64.0.4 -> 100.127.255.251 = 4194295], maximum is 65536 addresses. - self.session.set(path + ['server', 'subnet', '100.64.0.0/10']) + self.cli_set(path + ['server', 'subnet', '100.64.0.0/10']) with self.assertRaises(ConfigSessionError): - self.session.commit() + self.cli_commit() # check validate() - cannot specify more than 1 IPv4 and 1 IPv6 server subnet - self.session.set(path + ['server', 'subnet', '100.64.0.0/20']) + self.cli_set(path + ['server', 'subnet', '100.64.0.0/20']) with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.delete(path + ['server', 'subnet', '100.64.0.0/10']) + self.cli_commit() + self.cli_delete(path + ['server', 'subnet', '100.64.0.0/10']) # check validate() - must specify "tls ca-cert-file" with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(path + ['tls', 'ca-cert-file', ca_cert]) + self.cli_commit() + self.cli_set(path + ['tls', 'ca-cert-file', ca_cert]) # check validate() - must specify "tls cert-file" with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(path + ['tls', 'cert-file', ssl_cert]) + self.cli_commit() + self.cli_set(path + ['tls', 'cert-file', ssl_cert]) # check validate() - must specify "tls key-file" with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(path + ['tls', 'key-file', ssl_key]) + self.cli_commit() + self.cli_set(path + ['tls', 'key-file', ssl_key]) # check validate() - cannot specify "tls role" in client-server mode' - self.session.set(path + ['tls', 'role', 'active']) + self.cli_set(path + ['tls', 'role', 'active']) with self.assertRaises(ConfigSessionError): - self.session.commit() + self.cli_commit() # check validate() - cannot specify "tls role" in client-server mode' - self.session.set(path + ['tls', 'auth-file', auth_key]) + self.cli_set(path + ['tls', 'auth-file', auth_key]) with self.assertRaises(ConfigSessionError): - self.session.commit() + self.cli_commit() # check validate() - cannot specify "tcp-passive" when "tls role" is "active" - self.session.set(path + ['protocol', 'tcp-passive']) + self.cli_set(path + ['protocol', 'tcp-passive']) with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.delete(path + ['protocol']) + self.cli_commit() + self.cli_delete(path + ['protocol']) # check validate() - cannot specify "tls dh-file" when "tls role" is "active" - self.session.set(path + ['tls', 'dh-file', dh_pem]) + self.cli_set(path + ['tls', 'dh-file', dh_pem]) with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.delete(path + ['tls', 'dh-file']) + self.cli_commit() + self.cli_delete(path + ['tls', 'dh-file']) # Now test the other path with tls role passive - self.session.set(path + ['tls', 'role', 'passive']) + self.cli_set(path + ['tls', 'role', 'passive']) # check validate() - cannot specify "tcp-active" when "tls role" is "passive" - self.session.set(path + ['protocol', 'tcp-active']) + self.cli_set(path + ['protocol', 'tcp-active']) with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.delete(path + ['protocol']) + self.cli_commit() + self.cli_delete(path + ['protocol']) # check validate() - must specify "tls dh-file" when "tls role" is "passive" with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(path + ['tls', 'dh-file', dh_pem]) + self.cli_commit() + self.cli_set(path + ['tls', 'dh-file', dh_pem]) - self.session.commit() + self.cli_commit() self.assertTrue(process_named_running(PROCESS_NAME)) self.assertIn(interface, interfaces()) @@ -330,29 +330,29 @@ class TestInterfacesOpenVPN(unittest.TestCase): path = base_path + [interface] port = str(2000 + ii) - self.session.set(path + ['device-type', 'tun']) - self.session.set(path + ['encryption', 'cipher', 'aes192']) - self.session.set(path + ['hash', auth_hash]) - self.session.set(path + ['mode', 'server']) - self.session.set(path + ['local-port', port]) - self.session.set(path + ['server', 'subnet', subnet]) - self.session.set(path + ['server', 'topology', 'subnet']) - self.session.set(path + ['keep-alive', 'failure-count', '5']) - self.session.set(path + ['keep-alive', 'interval', '5']) + self.cli_set(path + ['device-type', 'tun']) + self.cli_set(path + ['encryption', 'cipher', 'aes192']) + self.cli_set(path + ['hash', auth_hash]) + self.cli_set(path + ['mode', 'server']) + self.cli_set(path + ['local-port', port]) + self.cli_set(path + ['server', 'subnet', subnet]) + self.cli_set(path + ['server', 'topology', 'subnet']) + self.cli_set(path + ['keep-alive', 'failure-count', '5']) + self.cli_set(path + ['keep-alive', 'interval', '5']) # clients - self.session.set(path + ['server', 'client', 'client1', 'ip', client_ip]) + self.cli_set(path + ['server', 'client', 'client1', 'ip', client_ip]) for route in client1_routes: - self.session.set(path + ['server', 'client', 'client1', 'subnet', route]) + self.cli_set(path + ['server', 'client', 'client1', 'subnet', route]) - self.session.set(path + ['replace-default-route']) - self.session.set(path + ['tls', 'ca-cert-file', ca_cert]) - self.session.set(path + ['tls', 'cert-file', ssl_cert]) - self.session.set(path + ['tls', 'key-file', ssl_key]) - self.session.set(path + ['tls', 'dh-file', dh_pem]) - self.session.set(path + ['vrf', vrf_name]) + self.cli_set(path + ['replace-default-route']) + self.cli_set(path + ['tls', 'ca-cert-file', ca_cert]) + self.cli_set(path + ['tls', 'cert-file', ssl_cert]) + self.cli_set(path + ['tls', 'key-file', ssl_key]) + self.cli_set(path + ['tls', 'dh-file', dh_pem]) + self.cli_set(path + ['vrf', vrf_name]) - self.session.commit() + self.cli_commit() for ii in num_range: interface = f'vtun{ii}' @@ -404,8 +404,8 @@ class TestInterfacesOpenVPN(unittest.TestCase): self.assertIn(interface, interfaces()) # check that no interface remained after deleting them - self.session.delete(base_path) - self.session.commit() + self.cli_delete(base_path) + self.cli_commit() for ii in num_range: interface = f'vtun{ii}' @@ -423,23 +423,23 @@ class TestInterfacesOpenVPN(unittest.TestCase): path = base_path + [interface] port = str(2000 + ii) - self.session.set(path + ['device-type', 'tun']) - self.session.set(path + ['encryption', 'cipher', 'aes192']) - self.session.set(path + ['hash', auth_hash]) - self.session.set(path + ['mode', 'server']) - self.session.set(path + ['local-port', port]) - self.session.set(path + ['server', 'subnet', subnet]) - self.session.set(path + ['server', 'topology', 'net30']) - self.session.set(path + ['replace-default-route']) - self.session.set(path + ['keep-alive', 'failure-count', '10']) - self.session.set(path + ['keep-alive', 'interval', '5']) - self.session.set(path + ['tls', 'ca-cert-file', ca_cert]) - self.session.set(path + ['tls', 'cert-file', ssl_cert]) - self.session.set(path + ['tls', 'key-file', ssl_key]) - self.session.set(path + ['tls', 'dh-file', dh_pem]) - self.session.set(path + ['vrf', vrf_name]) - - self.session.commit() + self.cli_set(path + ['device-type', 'tun']) + self.cli_set(path + ['encryption', 'cipher', 'aes192']) + self.cli_set(path + ['hash', auth_hash]) + self.cli_set(path + ['mode', 'server']) + self.cli_set(path + ['local-port', port]) + self.cli_set(path + ['server', 'subnet', subnet]) + self.cli_set(path + ['server', 'topology', 'net30']) + self.cli_set(path + ['replace-default-route']) + self.cli_set(path + ['keep-alive', 'failure-count', '10']) + self.cli_set(path + ['keep-alive', 'interval', '5']) + self.cli_set(path + ['tls', 'ca-cert-file', ca_cert]) + self.cli_set(path + ['tls', 'cert-file', ssl_cert]) + self.cli_set(path + ['tls', 'key-file', ssl_key]) + self.cli_set(path + ['tls', 'dh-file', dh_pem]) + self.cli_set(path + ['vrf', vrf_name]) + + self.cli_commit() for ii in num_range: interface = f'vtun{ii}' @@ -479,8 +479,8 @@ class TestInterfacesOpenVPN(unittest.TestCase): self.assertIn(interface, interfaces()) # check that no interface remained after deleting them - self.session.delete(base_path) - self.session.commit() + self.cli_delete(base_path) + self.cli_commit() for ii in num_range: interface = f'vtun{ii}' @@ -493,57 +493,57 @@ class TestInterfacesOpenVPN(unittest.TestCase): interface = 'vtun5000' path = base_path + [interface] - self.session.set(path + ['mode', 'site-to-site']) + self.cli_set(path + ['mode', 'site-to-site']) # check validate() - encryption ncp-ciphers cannot be specified in site-to-site mode - self.session.set(path + ['encryption', 'ncp-ciphers', 'aes192gcm']) + self.cli_set(path + ['encryption', 'ncp-ciphers', 'aes192gcm']) with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.delete(path + ['encryption']) + self.cli_commit() + self.cli_delete(path + ['encryption']) # check validate() - must specify "local-address" or add interface to bridge with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(path + ['local-address', '10.0.0.1']) - self.session.set(path + ['local-address', '2001:db8:1::1']) + self.cli_commit() + self.cli_set(path + ['local-address', '10.0.0.1']) + self.cli_set(path + ['local-address', '2001:db8:1::1']) # check validate() - cannot specify more than 1 IPv4 local-address - self.session.set(path + ['local-address', '10.0.0.2']) + self.cli_set(path + ['local-address', '10.0.0.2']) with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.delete(path + ['local-address', '10.0.0.2']) + self.cli_commit() + self.cli_delete(path + ['local-address', '10.0.0.2']) # check validate() - cannot specify more than 1 IPv6 local-address - self.session.set(path + ['local-address', '2001:db8:1::2']) + self.cli_set(path + ['local-address', '2001:db8:1::2']) with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.delete(path + ['local-address', '2001:db8:1::2']) + self.cli_commit() + self.cli_delete(path + ['local-address', '2001:db8:1::2']) # check validate() - IPv4 "local-address" requires IPv4 "remote-address" # or IPv4 "local-address subnet" with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(path + ['remote-address', '192.168.0.1']) - self.session.set(path + ['remote-address', '2001:db8:ffff::1']) + self.cli_commit() + self.cli_set(path + ['remote-address', '192.168.0.1']) + self.cli_set(path + ['remote-address', '2001:db8:ffff::1']) # check validate() - Cannot specify more than 1 IPv4 "remote-address" - self.session.set(path + ['remote-address', '192.168.0.2']) + self.cli_set(path + ['remote-address', '192.168.0.2']) with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.delete(path + ['remote-address', '192.168.0.2']) + self.cli_commit() + self.cli_delete(path + ['remote-address', '192.168.0.2']) # check validate() - Cannot specify more than 1 IPv6 "remote-address" - self.session.set(path + ['remote-address', '2001:db8:ffff::2']) + self.cli_set(path + ['remote-address', '2001:db8:ffff::2']) with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.delete(path + ['remote-address', '2001:db8:ffff::2']) + self.cli_commit() + self.cli_delete(path + ['remote-address', '2001:db8:ffff::2']) # check validate() - Must specify one of "shared-secret-key-file" and "tls" with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(path + ['shared-secret-key-file', s2s_key]) + self.cli_commit() + self.cli_set(path + ['shared-secret-key-file', s2s_key]) - self.session.commit() + self.cli_commit() def test_openvpn_site2site_interfaces_tun(self): # Create two OpenVPN site-to-site interfaces @@ -561,23 +561,23 @@ class TestInterfacesOpenVPN(unittest.TestCase): path = base_path + [interface] port = str(3000 + ii) - self.session.set(path + ['local-address', local_address]) + self.cli_set(path + ['local-address', local_address]) # even numbers use tun type, odd numbers use tap type if ii % 2 == 0: - self.session.set(path + ['device-type', 'tun']) + self.cli_set(path + ['device-type', 'tun']) else: - self.session.set(path + ['device-type', 'tap']) - self.session.set(path + ['local-address', local_address, 'subnet-mask', local_address_subnet]) + self.cli_set(path + ['device-type', 'tap']) + self.cli_set(path + ['local-address', local_address, 'subnet-mask', local_address_subnet]) - self.session.set(path + ['mode', 'site-to-site']) - self.session.set(path + ['local-port', port]) - self.session.set(path + ['remote-port', port]) - self.session.set(path + ['shared-secret-key-file', s2s_key]) - self.session.set(path + ['remote-address', remote_address]) - self.session.set(path + ['vrf', vrf_name]) + self.cli_set(path + ['mode', 'site-to-site']) + self.cli_set(path + ['local-port', port]) + self.cli_set(path + ['remote-port', port]) + self.cli_set(path + ['shared-secret-key-file', s2s_key]) + self.cli_set(path + ['remote-address', remote_address]) + self.cli_set(path + ['vrf', vrf_name]) - self.session.commit() + self.cli_commit() for ii in num_range: interface = f'vtun{ii}' @@ -608,8 +608,8 @@ class TestInterfacesOpenVPN(unittest.TestCase): # check that no interface remained after deleting them - self.session.delete(base_path) - self.session.commit() + self.cli_delete(base_path) + self.cli_commit() for ii in num_range: interface = f'vtun{ii}' @@ -625,27 +625,27 @@ if __name__ == '__main__': # Generate mandatory SSL certificate tmp = f'openssl req -newkey rsa:4096 -new -nodes -x509 -days 3650 '\ f'-keyout {ssl_key} -out {ssl_cert} -subj {subject}' - print(cmd(tmp)) + cmd(tmp) if not os.path.isfile(ca_cert): # Generate "CA" tmp = f'openssl req -new -x509 -key {ssl_key} -out {ca_cert} -subj {subject}' - print(cmd(tmp)) + cmd(tmp) if not os.path.isfile(dh_pem): # Generate "DH" key tmp = f'openssl dhparam -out {dh_pem} 2048' - print(cmd(tmp)) + cmd(tmp) if not os.path.isfile(s2s_key): # Generate site-2-site key tmp = f'openvpn --genkey --secret {s2s_key}' - print(cmd(tmp)) + cmd(tmp) if not os.path.isfile(auth_key): # Generate TLS auth key tmp = f'openvpn --genkey --secret {auth_key}' - print(cmd(tmp)) + cmd(tmp) for file in [ca_cert, ssl_cert, ssl_key, dh_pem, s2s_key, auth_key]: cmd(f'sudo chown openvpn:openvpn {file}') diff --git a/smoketest/scripts/cli/test_interfaces_pppoe.py b/smoketest/scripts/cli/test_interfaces_pppoe.py index 6bfe35d86..b8682fe71 100755 --- a/smoketest/scripts/cli/test_interfaces_pppoe.py +++ b/smoketest/scripts/cli/test_interfaces_pppoe.py @@ -15,11 +15,13 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import re -import os import unittest from psutil import process_iter -from vyos.configsession import ConfigSession, ConfigSessionError +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError from vyos.util import read_file config_file = '/etc/ppp/peers/{}' @@ -42,16 +44,14 @@ def get_dhcp6c_config_value(interface, key): out.append(item.replace(';','')) return out -class PPPoEInterfaceTest(unittest.TestCase): +class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase): def setUp(self): - self.session = ConfigSession(os.getpid()) self._interfaces = ['pppoe10', 'pppoe20', 'pppoe30'] self._source_interface = 'eth0' def tearDown(self): - self.session.delete(base_path) - self.session.commit() - del self.session + self.cli_delete(base_path) + self.cli_commit() def test_pppoe_client(self): # Check if PPPoE dialer can be configured and runs @@ -60,19 +60,19 @@ class PPPoEInterfaceTest(unittest.TestCase): passwd = 'VyOS-passwd-' + interface mtu = '1400' - self.session.set(base_path + [interface, 'authentication', 'user', user]) - self.session.set(base_path + [interface, 'authentication', 'password', passwd]) - self.session.set(base_path + [interface, 'default-route', 'auto']) - self.session.set(base_path + [interface, 'mtu', mtu]) - self.session.set(base_path + [interface, 'no-peer-dns']) + self.cli_set(base_path + [interface, 'authentication', 'user', user]) + self.cli_set(base_path + [interface, 'authentication', 'password', passwd]) + self.cli_set(base_path + [interface, 'default-route', 'auto']) + self.cli_set(base_path + [interface, 'mtu', mtu]) + self.cli_set(base_path + [interface, 'no-peer-dns']) # check validate() - a source-interface is required with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(base_path + [interface, 'source-interface', self._source_interface]) + self.cli_commit() + self.cli_set(base_path + [interface, 'source-interface', self._source_interface]) # commit changes - self.session.commit() + self.cli_commit() # verify configuration file(s) for interface in self._interfaces: @@ -97,27 +97,48 @@ class PPPoEInterfaceTest(unittest.TestCase): self.assertTrue(running) + + def test_pppoe_clent_disabled_interface(self): + # Check if PPPoE Client can be disabled + for interface in self._interfaces: + self.cli_set(base_path + [interface, 'authentication', 'user', 'vyos']) + self.cli_set(base_path + [interface, 'authentication', 'password', 'vyos']) + self.cli_set(base_path + [interface, 'source-interface', self._source_interface]) + self.cli_set(base_path + [interface, 'disable']) + + self.cli_commit() + + # Validate PPPoE client process + running = False + for interface in self._interfaces: + for proc in process_iter(): + if interface in proc.cmdline(): + running = True + + self.assertFalse(running) + + def test_pppoe_dhcpv6pd(self): # Check if PPPoE dialer can be configured with DHCPv6-PD address = '1' sla_id = '0' sla_len = '8' for interface in self._interfaces: - self.session.set(base_path + [interface, 'authentication', 'user', 'vyos']) - self.session.set(base_path + [interface, 'authentication', 'password', 'vyos']) - self.session.set(base_path + [interface, 'default-route', 'none']) - self.session.set(base_path + [interface, 'no-peer-dns']) - self.session.set(base_path + [interface, 'source-interface', self._source_interface]) - self.session.set(base_path + [interface, 'ipv6', 'address', 'autoconf']) + self.cli_set(base_path + [interface, 'authentication', 'user', 'vyos']) + self.cli_set(base_path + [interface, 'authentication', 'password', 'vyos']) + self.cli_set(base_path + [interface, 'default-route', 'none']) + self.cli_set(base_path + [interface, 'no-peer-dns']) + self.cli_set(base_path + [interface, 'source-interface', self._source_interface]) + self.cli_set(base_path + [interface, 'ipv6', 'address', 'autoconf']) # prefix delegation stuff dhcpv6_pd_base = base_path + [interface, 'dhcpv6-options', 'pd', '0'] - self.session.set(dhcpv6_pd_base + ['length', '56']) - self.session.set(dhcpv6_pd_base + ['interface', self._source_interface, 'address', address]) - self.session.set(dhcpv6_pd_base + ['interface', self._source_interface, 'sla-id', sla_id]) + self.cli_set(dhcpv6_pd_base + ['length', '56']) + self.cli_set(dhcpv6_pd_base + ['interface', self._source_interface, 'address', address]) + self.cli_set(dhcpv6_pd_base + ['interface', self._source_interface, 'sla-id', sla_id]) # commit changes - self.session.commit() + self.cli_commit() # verify "normal" PPPoE value - 1492 is default MTU tmp = get_config_value(interface, 'mtu')[1] diff --git a/smoketest/scripts/cli/test_interfaces_pseudo_ethernet.py b/smoketest/scripts/cli/test_interfaces_pseudo_ethernet.py index 85e5e70bd..ff343bb87 100755 --- a/smoketest/scripts/cli/test_interfaces_pseudo_ethernet.py +++ b/smoketest/scripts/cli/test_interfaces_pseudo_ethernet.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -18,20 +18,24 @@ import unittest from base_interfaces_test import BasicInterfaceTest -class PEthInterfaceTest(BasicInterfaceTest.BaseTest): - def setUp(self): - self._test_ip = True - self._test_ipv6 = True - self._test_mtu = True - self._test_vlan = True - self._test_qinq = True - self._base_path = ['interfaces', 'pseudo-ethernet'] - self._options = { +class PEthInterfaceTest(BasicInterfaceTest.TestCase): + @classmethod + def setUpClass(cls): + cls._test_ip = True + cls._test_ipv6 = True + cls._test_ipv6_pd = True + cls._test_ipv6_dhcpc6 = True + cls._test_mtu = True + cls._test_vlan = True + cls._test_qinq = True + cls._base_path = ['interfaces', 'pseudo-ethernet'] + cls._options = { 'peth0': ['source-interface eth1'], 'peth1': ['source-interface eth1'], } - self._interfaces = list(self._options) - super().setUp() + cls._interfaces = list(cls._options) + # call base-classes classmethod + super(cls, cls).setUpClass() if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_tunnel.py b/smoketest/scripts/cli/test_interfaces_tunnel.py index ca68cb8ba..6af31ddff 100755 --- a/smoketest/scripts/cli/test_interfaces_tunnel.py +++ b/smoketest/scripts/cli/test_interfaces_tunnel.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -15,365 +15,222 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import unittest -import json - -from vyos.configsession import ConfigSession -from vyos.configsession import ConfigSessionError -from vyos.util import cmd from base_interfaces_test import BasicInterfaceTest +from vyos.configsession import ConfigSessionError +from vyos.util import get_interface_config +from vyos.template import inc_ip + remote_ip4 = '192.0.2.100' remote_ip6 = '2001:db8::ffff' source_if = 'dum2222' mtu = 1476 -def tunnel_conf(interface): - tmp = cmd(f'ip -d -j link show {interface}') - # {'address': '2.2.2.2', - # 'broadcast': '192.0.2.10', - # 'flags': ['POINTOPOINT', 'NOARP', 'UP', 'LOWER_UP'], - # 'group': 'default', - # 'gso_max_segs': 65535, - # 'gso_max_size': 65536, - # 'ifindex': 10, - # 'ifname': 'tun10', - # 'inet6_addr_gen_mode': 'none', - # 'link': None, - # 'link_pointtopoint': True, - # 'link_type': 'gre', - # 'linkinfo': {'info_data': {'local': '2.2.2.2', - # 'pmtudisc': True, - # 'remote': '192.0.2.10', - # 'tos': '0x1', - # 'ttl': 255}, - # 'info_kind': 'gre'}, - # 'linkmode': 'DEFAULT', - # 'max_mtu': 65511, - # 'min_mtu': 68, - # 'mtu': 1476, - # 'num_rx_queues': 1, - # 'num_tx_queues': 1, - # 'operstate': 'UNKNOWN', - # 'promiscuity': 0, - # 'qdisc': 'noqueue', - # 'txqlen': 1000} - return json.loads(tmp)[0] - -class TunnelInterfaceTest(BasicInterfaceTest.BaseTest): - def setUp(self): - self._test_mtu = True - self._base_path = ['interfaces', 'tunnel'] - self.local_v4 = '192.0.2.1' - self.local_v6 = '2001:db8::1' - - self._options = { - 'tun10': ['encapsulation ipip', 'remote-ip 192.0.2.10', 'local-ip ' + self.local_v4], - 'tun20': ['encapsulation gre', 'remote-ip 192.0.2.20', 'local-ip ' + self.local_v4], +class TunnelInterfaceTest(BasicInterfaceTest.TestCase): + @classmethod + def setUpClass(cls): + cls._test_ip = True + cls._test_ipv6 = True + cls._test_mtu = True + cls._base_path = ['interfaces', 'tunnel'] + cls.local_v4 = '192.0.2.1' + cls.local_v6 = '2001:db8::1' + cls._options = { + 'tun10': ['encapsulation ipip', 'remote 192.0.2.10', 'source-address ' + cls.local_v4], + 'tun20': ['encapsulation gre', 'remote 192.0.2.20', 'source-address ' + cls.local_v4], } + cls._interfaces = list(cls._options) + # call base-classes classmethod + super(cls, cls).setUpClass() - self._interfaces = list(self._options) + def setUp(self): super().setUp() - - self.session.set(['interfaces', 'dummy', source_if, 'address', self.local_v4 + '/32']) - self.session.set(['interfaces', 'dummy', source_if, 'address', self.local_v6 + '/128']) + self.cli_set(['interfaces', 'dummy', source_if, 'address', self.local_v4 + '/32']) + self.cli_set(['interfaces', 'dummy', source_if, 'address', self.local_v6 + '/128']) def tearDown(self): - self.session.delete(['interfaces', 'dummy', source_if]) + self.cli_delete(['interfaces', 'dummy', source_if]) super().tearDown() - def test_ipip(self): - interface = 'tun100' - encapsulation = 'ipip' - local_if_addr = '10.10.10.1/24' - - self.session.set(self._base_path + [interface, 'address', local_if_addr]) - - # Must provide an "encapsulation" for tunnel tun10 - with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(self._base_path + [interface, 'encapsulation', encapsulation]) - - # Must configure either local-ip or dhcp-interface for tunnel ipip tun100 - with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(self._base_path + [interface, 'local-ip', self.local_v4]) - - # missing required option remote for ipip - with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(self._base_path + [interface, 'remote-ip', remote_ip4]) - - # Configure Tunnel Source interface - self.session.set(self._base_path + [interface, 'source-interface', source_if]) - - self.session.commit() - - conf = tunnel_conf(interface) - self.assertEqual(interface, conf['ifname']) - self.assertEqual(encapsulation, conf['link_type']) - self.assertEqual(mtu, conf['mtu']) - self.assertEqual(source_if, conf['link']) - - self.assertEqual(self.local_v4, conf['linkinfo']['info_data']['local']) - self.assertEqual(remote_ip4, conf['linkinfo']['info_data']['remote']) - - def test_ipip6(self): - interface = 'tun110' - encapsulation = 'ipip6' - local_if_addr = '10.10.10.1/24' - - self.session.set(self._base_path + [interface, 'address', local_if_addr]) - - # Must provide an "encapsulation" for tunnel tun10 - with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(self._base_path + [interface, 'encapsulation', encapsulation]) - - # Must configure either local-ip or dhcp-interface for tunnel ipip tun100 - with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(self._base_path + [interface, 'local-ip', self.local_v6]) - - # missing required option remote for ipip - with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(self._base_path + [interface, 'remote-ip', remote_ip6]) - - # Configure Tunnel Source interface - self.session.set(self._base_path + [interface, 'source-interface', source_if]) - - self.session.commit() - - conf = tunnel_conf(interface) - self.assertEqual(interface, conf['ifname']) - self.assertEqual('tunnel6', conf['link_type']) - self.assertEqual(mtu, conf['mtu']) - self.assertEqual(source_if, conf['link']) - - self.assertEqual(self.local_v6, conf['linkinfo']['info_data']['local']) - self.assertEqual(remote_ip6, conf['linkinfo']['info_data']['remote']) - - def test_tunnel_verify_ipv4_local_remote_addr(self): + def test_ipv4_encapsulations(self): # When running tests ensure that for certain encapsulation types the # local and remote IP address is actually an IPv4 address interface = f'tun1000' local_if_addr = f'10.10.200.1/24' - for encapsulation in ['ipip', 'sit', 'gre']: - self.session.set(self._base_path + [interface, 'address', local_if_addr]) - self.session.set(self._base_path + [interface, 'encapsulation', encapsulation]) - self.session.set(self._base_path + [interface, 'local-ip', self.local_v6]) - self.session.set(self._base_path + [interface, 'remote-ip', remote_ip6]) + for encapsulation in ['ipip', 'sit', 'gre', 'gretap']: + self.cli_set(self._base_path + [interface, 'address', local_if_addr]) + self.cli_set(self._base_path + [interface, 'encapsulation', encapsulation]) + self.cli_set(self._base_path + [interface, 'source-address', self.local_v6]) + self.cli_set(self._base_path + [interface, 'remote', remote_ip6]) - # Encapsulation mode requires IPv4 local-ip + # Encapsulation mode requires IPv4 source-address with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(self._base_path + [interface, 'local-ip', self.local_v4]) + self.cli_commit() + self.cli_set(self._base_path + [interface, 'source-address', self.local_v4]) - # Encapsulation mode requires IPv4 local-ip + # Encapsulation mode requires IPv4 remote with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(self._base_path + [interface, 'remote-ip', remote_ip4]) + self.cli_commit() + self.cli_set(self._base_path + [interface, 'remote', remote_ip4]) + self.cli_set(self._base_path + [interface, 'source-interface', source_if]) + + # Source interface can not be used with sit and gretap + if encapsulation in ['sit', 'gretap']: + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(self._base_path + [interface, 'source-interface']) # Check if commit is ok - self.session.commit() + self.cli_commit() + + conf = get_interface_config(interface) + if encapsulation not in ['sit', 'gretap']: + self.assertEqual(source_if, conf['link']) + + self.assertEqual(interface, conf['ifname']) + self.assertEqual(mtu, conf['mtu']) + self.assertEqual(encapsulation, conf['linkinfo']['info_kind']) + self.assertEqual(self.local_v4, conf['linkinfo']['info_data']['local']) + self.assertEqual(remote_ip4, conf['linkinfo']['info_data']['remote']) + self.assertTrue(conf['linkinfo']['info_data']['pmtudisc']) # cleanup this instance - self.session.delete(self._base_path + [interface]) - self.session.commit() + self.cli_delete(self._base_path + [interface]) + self.cli_commit() - def test_tunnel_verify_ipv6_local_remote_addr(self): + def test_ipv6_encapsulations(self): # When running tests ensure that for certain encapsulation types the # local and remote IP address is actually an IPv6 address interface = f'tun1010' local_if_addr = f'10.10.200.1/24' - for encapsulation in ['ipip6', 'ip6ip6', 'ip6gre']: - self.session.set(self._base_path + [interface, 'address', local_if_addr]) - self.session.set(self._base_path + [interface, 'encapsulation', encapsulation]) - self.session.set(self._base_path + [interface, 'local-ip', self.local_v4]) - self.session.set(self._base_path + [interface, 'remote-ip', remote_ip4]) + for encapsulation in ['ipip6', 'ip6ip6', 'ip6gre', 'ip6gretap']: + self.cli_set(self._base_path + [interface, 'address', local_if_addr]) + self.cli_set(self._base_path + [interface, 'encapsulation', encapsulation]) + self.cli_set(self._base_path + [interface, 'source-address', self.local_v4]) + self.cli_set(self._base_path + [interface, 'remote', remote_ip4]) - # Encapsulation mode requires IPv6 local-ip + # Encapsulation mode requires IPv6 source-address with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(self._base_path + [interface, 'local-ip', self.local_v6]) + self.cli_commit() + self.cli_set(self._base_path + [interface, 'source-address', self.local_v6]) - # Encapsulation mode requires IPv6 local-ip + # Encapsulation mode requires IPv6 remote with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(self._base_path + [interface, 'remote-ip', remote_ip6]) + self.cli_commit() + self.cli_set(self._base_path + [interface, 'remote', remote_ip6]) - # Check if commit is ok - self.session.commit() + # Configure Tunnel Source interface + self.cli_set(self._base_path + [interface, 'source-interface', source_if]) + # Source interface can not be used with ip6gretap + if encapsulation in ['ip6gretap']: + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(self._base_path + [interface, 'source-interface']) - # cleanup this instance - self.session.delete(self._base_path + [interface]) - self.session.commit() + # Check if commit is ok + self.cli_commit() - def test_tunnel_verify_local_dhcp(self): - # We can not use local-ip and dhcp-interface at the same time + conf = get_interface_config(interface) + if encapsulation not in ['ip6gretap']: + self.assertEqual(source_if, conf['link']) - interface = f'tun1020' - local_if_addr = f'10.0.0.1/24' + self.assertEqual(interface, conf['ifname']) + self.assertEqual(mtu, conf['mtu']) - self.session.set(self._base_path + [interface, 'address', local_if_addr]) - self.session.set(self._base_path + [interface, 'encapsulation', 'gre']) - self.session.set(self._base_path + [interface, 'local-ip', self.local_v4]) - self.session.set(self._base_path + [interface, 'remote-ip', remote_ip4]) - self.session.set(self._base_path + [interface, 'dhcp-interface', 'eth0']) + # Not applicable for ip6gre + if 'proto' in conf['linkinfo']['info_data']: + self.assertEqual(encapsulation, conf['linkinfo']['info_data']['proto']) - # local-ip and dhcp-interface can not be used at the same time - with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.delete(self._base_path + [interface, 'dhcp-interface']) + # remap encapsulation protocol(s) only for ipip6, ip6ip6 + if encapsulation in ['ipip6', 'ip6ip6']: + encapsulation = 'ip6tnl' - # Check if commit is ok - self.session.commit() + self.assertEqual(encapsulation, conf['linkinfo']['info_kind']) + self.assertEqual(self.local_v6, conf['linkinfo']['info_data']['local']) + self.assertEqual(remote_ip6, conf['linkinfo']['info_data']['remote']) - def test_tunnel_ip6ip6(self): - interface = 'tun120' - encapsulation = 'ip6ip6' - local_if_addr = '2001:db8:f00::1/24' + # cleanup this instance + self.cli_delete(self._base_path + [interface]) + self.cli_commit() - self.session.set(self._base_path + [interface, 'address', local_if_addr]) + def test_tunnel_verify_local_dhcp(self): + # We can not use source-address and dhcp-interface at the same time - # Must provide an "encapsulation" for tunnel tun10 - with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(self._base_path + [interface, 'encapsulation', encapsulation]) + interface = f'tun1020' + local_if_addr = f'10.0.0.1/24' - # Must configure either local-ip or dhcp-interface for tunnel ipip tun100 - with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(self._base_path + [interface, 'local-ip', self.local_v6]) + self.cli_set(self._base_path + [interface, 'address', local_if_addr]) + self.cli_set(self._base_path + [interface, 'encapsulation', 'gre']) + self.cli_set(self._base_path + [interface, 'source-address', self.local_v4]) + self.cli_set(self._base_path + [interface, 'remote', remote_ip4]) + self.cli_set(self._base_path + [interface, 'dhcp-interface', 'eth0']) - # missing required option remote for ipip + # source-address and dhcp-interface can not be used at the same time with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(self._base_path + [interface, 'remote-ip', remote_ip6]) - - # Configure Tunnel Source interface - self.session.set(self._base_path + [interface, 'source-interface', source_if]) + self.cli_commit() + self.cli_delete(self._base_path + [interface, 'dhcp-interface']) - self.session.commit() - - conf = tunnel_conf(interface) - self.assertEqual(interface, conf['ifname']) - self.assertEqual('tunnel6', conf['link_type']) - self.assertEqual(mtu, conf['mtu']) - self.assertEqual(source_if, conf['link']) - - self.assertEqual(self.local_v6, conf['linkinfo']['info_data']['local']) - self.assertEqual(remote_ip6, conf['linkinfo']['info_data']['remote']) + # Check if commit is ok + self.cli_commit() - def test_tunnel_gre_ipv4(self): - interface = 'tun200' + def test_tunnel_parameters_gre(self): + interface = f'tun1030' + gre_key = '10' encapsulation = 'gre' - local_if_addr = '172.16.1.1/24' + tos = '20' - self.session.set(self._base_path + [interface, 'address', local_if_addr]) + self.cli_set(self._base_path + [interface, 'encapsulation', encapsulation]) + self.cli_set(self._base_path + [interface, 'source-address', self.local_v4]) + self.cli_set(self._base_path + [interface, 'remote', remote_ip4]) - # Must provide an "encapsulation" for tunnel tun10 - with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(self._base_path + [interface, 'encapsulation', encapsulation]) - - # Must configure either local-ip or dhcp-interface - with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(self._base_path + [interface, 'local-ip', self.local_v4]) - - # No assertion is raised for GRE remote-ip when missing - self.session.set(self._base_path + [interface, 'remote-ip', remote_ip4]) - - # Configure Tunnel Source interface - self.session.set(self._base_path + [interface, 'source-interface', source_if]) - - self.session.commit() + self.cli_set(self._base_path + [interface, 'parameters', 'ip', 'no-pmtu-discovery']) + self.cli_set(self._base_path + [interface, 'parameters', 'ip', 'key', gre_key]) + self.cli_set(self._base_path + [interface, 'parameters', 'ip', 'tos', tos]) - conf = tunnel_conf(interface) - self.assertEqual(interface, conf['ifname']) - self.assertEqual(encapsulation, conf['link_type']) - self.assertEqual(mtu, conf['mtu']) - self.assertEqual(source_if, conf['link']) + # Check if commit is ok + self.cli_commit() + conf = get_interface_config(interface) + self.assertEqual(mtu, conf['mtu']) + self.assertEqual(interface, conf['ifname']) + self.assertEqual(encapsulation, conf['linkinfo']['info_kind']) self.assertEqual(self.local_v4, conf['linkinfo']['info_data']['local']) - self.assertEqual(remote_ip4, conf['linkinfo']['info_data']['remote']) - - - def test_gre_ipv6(self): - interface = 'tun210' - encapsulation = 'ip6gre' - local_if_addr = '2001:db8:f01::1/24' + self.assertEqual(remote_ip4, conf['linkinfo']['info_data']['remote']) + self.assertEqual(0, conf['linkinfo']['info_data']['ttl']) + self.assertFalse( conf['linkinfo']['info_data']['pmtudisc']) - self.session.set(self._base_path + [interface, 'address', local_if_addr]) + def test_gretap_parameters_change(self): + interface = f'tun1040' + gre_key = '10' + encapsulation = 'gretap' + tos = '20' - # Must provide an "encapsulation" for tunnel tun10 - with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(self._base_path + [interface, 'encapsulation', encapsulation]) - - # Must configure either local-ip or dhcp-interface - with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(self._base_path + [interface, 'local-ip', self.local_v6]) + self.cli_set(self._base_path + [interface, 'encapsulation', encapsulation]) + self.cli_set(self._base_path + [interface, 'source-address', self.local_v4]) + self.cli_set(self._base_path + [interface, 'remote', remote_ip4]) - # No assertion is raised for GRE remote-ip when missing - self.session.set(self._base_path + [interface, 'remote-ip', remote_ip6]) - - # Configure Tunnel Source interface - self.session.set(self._base_path + [interface, 'source-interface', source_if]) - - self.session.commit() - - conf = tunnel_conf(interface) - self.assertEqual(interface, conf['ifname']) - self.assertEqual(encapsulation, conf['link_type']) - self.assertEqual(mtu, conf['mtu']) - self.assertEqual(source_if, conf['link']) - - self.assertEqual(self.local_v6, conf['linkinfo']['info_data']['local']) - self.assertEqual(remote_ip6, conf['linkinfo']['info_data']['remote']) - - - def test_tunnel_sit(self): - interface = 'tun300' - encapsulation = 'sit' - local_if_addr = '172.16.2.1/24' - - self.session.set(self._base_path + [interface, 'address', local_if_addr]) - - # Must provide an "encapsulation" for tunnel tun10 - with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(self._base_path + [interface, 'encapsulation', encapsulation]) - - # Must configure either local-ip or dhcp-interface - with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(self._base_path + [interface, 'local-ip', self.local_v4]) - - # No assertion is raised for GRE remote-ip when missing - self.session.set(self._base_path + [interface, 'remote-ip', remote_ip4]) - - # Source interface can not be used with sit - self.session.set(self._base_path + [interface, 'source-interface', source_if]) - with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.delete(self._base_path + [interface, 'source-interface']) - - self.session.commit() - - conf = tunnel_conf(interface) - self.assertEqual(interface, conf['ifname']) - self.assertEqual(encapsulation, conf['link_type']) - self.assertEqual(mtu, conf['mtu']) + # Check if commit is ok + self.cli_commit() + conf = get_interface_config(interface) + self.assertEqual(mtu, conf['mtu']) + self.assertEqual(interface, conf['ifname']) + self.assertEqual(encapsulation, conf['linkinfo']['info_kind']) self.assertEqual(self.local_v4, conf['linkinfo']['info_data']['local']) - self.assertEqual(remote_ip4, conf['linkinfo']['info_data']['remote']) + self.assertEqual(remote_ip4, conf['linkinfo']['info_data']['remote']) + self.assertEqual(0, conf['linkinfo']['info_data']['ttl']) + + # Change remote ip address (inc host by 2 + new_remote = inc_ip(remote_ip4, 2) + self.cli_set(self._base_path + [interface, 'remote', new_remote]) + # Check if commit is ok + self.cli_commit() + conf = get_interface_config(interface) + self.assertEqual(new_remote, conf['linkinfo']['info_data']['remote']) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_vxlan.py b/smoketest/scripts/cli/test_interfaces_vxlan.py index a9b0fc5a1..7b420cd51 100755 --- a/smoketest/scripts/cli/test_interfaces_vxlan.py +++ b/smoketest/scripts/cli/test_interfaces_vxlan.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -16,19 +16,75 @@ import unittest -from vyos.configsession import ConfigSession, ConfigSessionError +from vyos.configsession import ConfigSession +from vyos.ifconfig import Interface +from vyos.util import get_interface_config + from base_interfaces_test import BasicInterfaceTest -class VXLANInterfaceTest(BasicInterfaceTest.BaseTest): - def setUp(self): - self._test_mtu = True - self._base_path = ['interfaces', 'vxlan'] - self._options = { - 'vxlan0': ['vni 10', 'remote 127.0.0.2'], - 'vxlan1': ['vni 20', 'group 239.1.1.1', 'source-interface eth0'], +class VXLANInterfaceTest(BasicInterfaceTest.TestCase): + @classmethod + def setUpClass(cls): + cls._test_ip = True + cls._test_ipv6 = True + cls._test_mtu = True + cls._base_path = ['interfaces', 'vxlan'] + cls._options = { + 'vxlan10': ['vni 10', 'remote 127.0.0.2'], + 'vxlan20': ['vni 20', 'group 239.1.1.1', 'source-interface eth0'], + 'vxlan30': ['vni 30', 'remote 2001:db8:2000::1', 'source-address 2001:db8:1000::1', 'parameters ipv6 flowlabel 0x1000'], } - self._interfaces = list(self._options) - super().setUp() + cls._interfaces = list(cls._options) + # call base-classes classmethod + super(cls, cls).setUpClass() + + def test_vxlan_parameters(self): + tos = '40' + ttl = 20 + for intf in self._interfaces: + for option in self._options.get(intf, []): + self.cli_set(self._base_path + [intf] + option.split()) + + self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'dont-fragment']) + self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'tos', tos]) + self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'ttl', str(ttl)]) + ttl += 10 + + self.cli_commit() + + ttl = 20 + for interface in self._interfaces: + options = get_interface_config(interface) + + vni = options['linkinfo']['info_data']['id'] + self.assertIn(f'vni {vni}', self._options[interface]) + + if any('link' in s for s in self._options[interface]): + link = options['linkinfo']['info_data']['link'] + self.assertIn(f'source-interface {link}', self._options[interface]) + + if any('local6' in s for s in self._options[interface]): + remote = options['linkinfo']['info_data']['local6'] + self.assertIn(f'source-address {local6}', self._options[interface]) + + if any('remote6' in s for s in self._options[interface]): + remote = options['linkinfo']['info_data']['remote6'] + self.assertIn(f'remote {remote}', self._options[interface]) + + if any('group' in s for s in self._options[interface]): + group = options['linkinfo']['info_data']['group'] + self.assertIn(f'group {group}', self._options[interface]) + + if any('flowlabel' in s for s in self._options[interface]): + label = options['linkinfo']['info_data']['label'] + self.assertIn(f'parameters ipv6 flowlabel {label}', self._options[interface]) + + self.assertEqual('vxlan', options['linkinfo']['info_kind']) + self.assertEqual('set', options['linkinfo']['info_data']['df']) + self.assertEqual(f'0x{tos}', options['linkinfo']['info_data']['tos']) + self.assertEqual(ttl, options['linkinfo']['info_data']['ttl']) + self.assertEqual(Interface(interface).get_admin_state(), 'up') + ttl += 10 if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_wireguard.py b/smoketest/scripts/cli/test_interfaces_wireguard.py index d9a51b146..d31ec0332 100755 --- a/smoketest/scripts/cli/test_interfaces_wireguard.py +++ b/smoketest/scripts/cli/test_interfaces_wireguard.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -17,8 +17,10 @@ import os import unittest -from vyos.configsession import ConfigSession, ConfigSessionError -from base_interfaces_test import BasicInterfaceTest +from base_vyostest_shim import VyOSUnitTestSHIM +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError + # Generate WireGuard default keypair if not os.path.isdir('/config/auth/wireguard/default'): @@ -26,17 +28,15 @@ if not os.path.isdir('/config/auth/wireguard/default'): base_path = ['interfaces', 'wireguard'] -class WireGuardInterfaceTest(unittest.TestCase): +class WireGuardInterfaceTest(VyOSUnitTestSHIM.TestCase): def setUp(self): - self.session = ConfigSession(os.getpid()) self._test_addr = ['192.0.2.1/26', '192.0.2.255/31', '192.0.2.64/32', '2001:db8:1::ffff/64', '2001:db8:101::1/112'] self._interfaces = ['wg0', 'wg1'] def tearDown(self): - self.session.delete(base_path) - self.session.commit() - del self.session + self.cli_delete(base_path) + self.cli_commit() def test_wireguard_peer(self): # Create WireGuard interfaces with associated peers @@ -46,19 +46,19 @@ class WireGuardInterfaceTest(unittest.TestCase): pubkey = 'n6ZZL7ph/QJUJSUUTyu19c77my1dRCDHkMzFQUO9Z3A=' for addr in self._test_addr: - self.session.set(base_path + [intf, 'address', addr]) + self.cli_set(base_path + [intf, 'address', addr]) - self.session.set(base_path + [intf, 'peer', peer, 'address', '127.0.0.1']) - self.session.set(base_path + [intf, 'peer', peer, 'port', '1337']) + self.cli_set(base_path + [intf, 'peer', peer, 'address', '127.0.0.1']) + self.cli_set(base_path + [intf, 'peer', peer, 'port', '1337']) # Allow different prefixes to traverse the tunnel allowed_ips = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'] for ip in allowed_ips: - self.session.set(base_path + [intf, 'peer', peer, 'allowed-ips', ip]) + self.cli_set(base_path + [intf, 'peer', peer, 'allowed-ips', ip]) - self.session.set(base_path + [intf, 'peer', peer, 'preshared-key', psk]) - self.session.set(base_path + [intf, 'peer', peer, 'pubkey', pubkey]) - self.session.commit() + self.cli_set(base_path + [intf, 'peer', peer, 'preshared-key', psk]) + self.cli_set(base_path + [intf, 'peer', peer, 'pubkey', pubkey]) + self.cli_commit() self.assertTrue(os.path.isdir(f'/sys/class/net/{intf}')) @@ -71,26 +71,26 @@ class WireGuardInterfaceTest(unittest.TestCase): pubkey_1 = 'n1CUsmR0M2LUUsyicBd6blZICwUqqWWHbu4ifZ2/9gk=' pubkey_2 = 'ebFx/1G0ti8tvuZd94sEIosAZZIznX+dBAKG/8DFm0I=' - self.session.set(base_path + [interface, 'address', '172.16.0.1/24']) + self.cli_set(base_path + [interface, 'address', '172.16.0.1/24']) - self.session.set(base_path + [interface, 'peer', 'PEER01', 'pubkey', pubkey_1]) - self.session.set(base_path + [interface, 'peer', 'PEER01', 'port', port]) - self.session.set(base_path + [interface, 'peer', 'PEER01', 'allowed-ips', '10.205.212.10/32']) - self.session.set(base_path + [interface, 'peer', 'PEER01', 'address', '192.0.2.1']) + self.cli_set(base_path + [interface, 'peer', 'PEER01', 'pubkey', pubkey_1]) + self.cli_set(base_path + [interface, 'peer', 'PEER01', 'port', port]) + self.cli_set(base_path + [interface, 'peer', 'PEER01', 'allowed-ips', '10.205.212.10/32']) + self.cli_set(base_path + [interface, 'peer', 'PEER01', 'address', '192.0.2.1']) - self.session.set(base_path + [interface, 'peer', 'PEER02', 'pubkey', pubkey_2]) - self.session.set(base_path + [interface, 'peer', 'PEER02', 'port', port]) - self.session.set(base_path + [interface, 'peer', 'PEER02', 'allowed-ips', '10.205.212.11/32']) - self.session.set(base_path + [interface, 'peer', 'PEER02', 'address', '192.0.2.2']) + self.cli_set(base_path + [interface, 'peer', 'PEER02', 'pubkey', pubkey_2]) + self.cli_set(base_path + [interface, 'peer', 'PEER02', 'port', port]) + self.cli_set(base_path + [interface, 'peer', 'PEER02', 'allowed-ips', '10.205.212.11/32']) + self.cli_set(base_path + [interface, 'peer', 'PEER02', 'address', '192.0.2.2']) # Commit peers - self.session.commit() + self.cli_commit() self.assertTrue(os.path.isdir(f'/sys/class/net/{interface}')) # Delete second peer - self.session.delete(base_path + [interface, 'peer', 'PEER01']) - self.session.commit() + self.cli_delete(base_path + [interface, 'peer', 'PEER01']) + self.cli_commit() if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_wireless.py b/smoketest/scripts/cli/test_interfaces_wireless.py index ffaa7d523..4f539a23c 100755 --- a/smoketest/scripts/cli/test_interfaces_wireless.py +++ b/smoketest/scripts/cli/test_interfaces_wireless.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -31,10 +31,12 @@ def get_config_value(interface, key): tmp = re.findall(f'{key}=+(.*)', tmp) return tmp[0] -class WirelessInterfaceTest(BasicInterfaceTest.BaseTest): - def setUp(self): - self._base_path = ['interfaces', 'wireless'] - self._options = { +class WirelessInterfaceTest(BasicInterfaceTest.TestCase): + @classmethod + def setUpClass(cls): + cls._test_ip = True + cls._base_path = ['interfaces', 'wireless'] + cls._options = { 'wlan0': ['physical-device phy0', 'ssid VyOS-WIFI-0', 'type station', 'address 192.0.2.1/30'], 'wlan1': ['physical-device phy0', 'ssid VyOS-WIFI-1', 'country-code se', @@ -44,8 +46,9 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest): 'wlan11': ['physical-device phy1', 'ssid VyOS-WIFI-3', 'country-code se', 'type access-point', 'address 192.0.2.13/30', 'channel 0'], } - self._interfaces = list(self._options) - super().setUp() + cls._interfaces = list(cls._options) + # call base-classes classmethod + super(cls, cls).setUpClass() def test_wireless_add_single_ip_address(self): # derived method to check if member interfaces are enslaved properly @@ -66,12 +69,12 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest): interface = 'wlan0' ssid = 'ssid' - self.session.set(self._base_path + [interface, 'ssid', ssid]) - self.session.set(self._base_path + [interface, 'country-code', 'se']) - self.session.set(self._base_path + [interface, 'type', 'access-point']) + self.cli_set(self._base_path + [interface, 'ssid', ssid]) + self.cli_set(self._base_path + [interface, 'country-code', 'se']) + self.cli_set(self._base_path + [interface, 'type', 'access-point']) # auto-powersave is special - self.session.set(self._base_path + [interface, 'capabilities', 'ht', 'auto-powersave']) + self.cli_set(self._base_path + [interface, 'capabilities', 'ht', 'auto-powersave']) ht_opt = { # VyOS CLI option hostapd - ht_capab setting @@ -87,7 +90,7 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest): 'smps static' : '[SMPS-STATIC]', } for key in ht_opt: - self.session.set(self._base_path + [interface, 'capabilities', 'ht'] + key.split()) + self.cli_set(self._base_path + [interface, 'capabilities', 'ht'] + key.split()) vht_opt = { # VyOS CLI option hostapd - ht_capab setting @@ -104,9 +107,9 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest): 'short-gi 160' : '[SHORT-GI-160]', } for key in vht_opt: - self.session.set(self._base_path + [interface, 'capabilities', 'vht'] + key.split()) + self.cli_set(self._base_path + [interface, 'capabilities', 'vht'] + key.split()) - self.session.commit() + self.cli_commit() # # Validate Config @@ -147,29 +150,29 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest): mode = 'n' country = 'de' - self.session.set(self._base_path + [interface, 'physical-device', phy]) - self.session.set(self._base_path + [interface, 'type', 'access-point']) - self.session.set(self._base_path + [interface, 'mode', mode]) + self.cli_set(self._base_path + [interface, 'physical-device', phy]) + self.cli_set(self._base_path + [interface, 'type', 'access-point']) + self.cli_set(self._base_path + [interface, 'mode', mode]) # SSID must be set with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(self._base_path + [interface, 'ssid', ssid]) + self.cli_commit() + self.cli_set(self._base_path + [interface, 'ssid', ssid]) # Channel must be set with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(self._base_path + [interface, 'channel', channel]) + self.cli_commit() + self.cli_set(self._base_path + [interface, 'channel', channel]) # Country-Code must be set with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(self._base_path + [interface, 'country-code', country]) + self.cli_commit() + self.cli_set(self._base_path + [interface, 'country-code', country]) - self.session.set(self._base_path + [interface, 'security', 'wpa', 'mode', 'wpa2']) - self.session.set(self._base_path + [interface, 'security', 'wpa', 'passphrase', wpa_key]) + self.cli_set(self._base_path + [interface, 'security', 'wpa', 'mode', 'wpa2']) + self.cli_set(self._base_path + [interface, 'security', 'wpa', 'passphrase', wpa_key]) - self.session.commit() + self.cli_commit() # # Validate Config @@ -210,13 +213,13 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest): # We need a bridge where we can hook our access-point interface to bridge_path = ['interfaces', 'bridge', bridge] - self.session.set(bridge_path + ['member', 'interface', interface]) + self.cli_set(bridge_path + ['member', 'interface', interface]) - self.session.set(self._base_path + [interface, 'ssid', ssid]) - self.session.set(self._base_path + [interface, 'country-code', 'se']) - self.session.set(self._base_path + [interface, 'type', 'access-point']) + self.cli_set(self._base_path + [interface, 'ssid', ssid]) + self.cli_set(self._base_path + [interface, 'country-code', 'se']) + self.cli_set(self._base_path + [interface, 'type', 'access-point']) - self.session.commit() + self.cli_commit() # Check for running process self.assertTrue(process_named_running('hostapd')) @@ -227,9 +230,9 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest): self.assertIn(interface, bridge_members) - self.session.delete(bridge_path) - self.session.delete(self._base_path) - self.session.commit() + self.cli_delete(bridge_path) + self.cli_delete(self._base_path) + self.cli_commit() if __name__ == '__main__': check_kmod('mac80211_hwsim') diff --git a/smoketest/scripts/cli/test_interfaces_wirelessmodem.py b/smoketest/scripts/cli/test_interfaces_wirelessmodem.py index 45cd069f4..c36835ea7 100755 --- a/smoketest/scripts/cli/test_interfaces_wirelessmodem.py +++ b/smoketest/scripts/cli/test_interfaces_wirelessmodem.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -18,7 +18,10 @@ import os import unittest from psutil import process_iter -from vyos.configsession import ConfigSession, ConfigSessionError +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError config_file = '/etc/ppp/peers/{}' base_path = ['interfaces', 'wirelessmodem'] @@ -30,33 +33,31 @@ def get_config_value(interface, key): return list(line.split()) return [] -class WWANInterfaceTest(unittest.TestCase): +class WWANInterfaceTest(VyOSUnitTestSHIM.TestCase): def setUp(self): - self.session = ConfigSession(os.getpid()) self._interfaces = ['wlm0', 'wlm1'] def tearDown(self): - self.session.delete(base_path) - self.session.commit() - del self.session + self.cli_delete(base_path) + self.cli_commit() - def test_wlm_1(self): + def test_wwan(self): for interface in self._interfaces: - self.session.set(base_path + [interface, 'no-peer-dns']) - self.session.set(base_path + [interface, 'connect-on-demand']) + self.cli_set(base_path + [interface, 'no-peer-dns']) + self.cli_set(base_path + [interface, 'connect-on-demand']) # check validate() - APN must be configure with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(base_path + [interface, 'apn', 'vyos.net']) + self.cli_commit() + self.cli_set(base_path + [interface, 'apn', 'vyos.net']) # check validate() - device must be configure with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(base_path + [interface, 'device', 'ttyS0']) + self.cli_commit() + self.cli_set(base_path + [interface, 'device', 'ttyS0']) # commit changes - self.session.commit() + self.cli_commit() # verify configuration file(s) for interface in self._interfaces: diff --git a/smoketest/scripts/cli/test_nat.py b/smoketest/scripts/cli/test_nat.py index 7ca82f86f..0706f234e 100755 --- a/smoketest/scripts/cli/test_nat.py +++ b/smoketest/scripts/cli/test_nat.py @@ -19,6 +19,7 @@ import jmespath import json import unittest +from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError from vyos.util import cmd @@ -28,16 +29,15 @@ base_path = ['nat'] src_path = base_path + ['source'] dst_path = base_path + ['destination'] -class TestNAT(unittest.TestCase): +class TestNAT(VyOSUnitTestSHIM.TestCase): def setUp(self): # ensure we can also run this test on a live system - so lets clean # out the current configuration :) - self.session = ConfigSession(os.getpid()) - self.session.delete(base_path) + self.cli_delete(base_path) def tearDown(self): - self.session.delete(base_path) - self.session.commit() + self.cli_delete(base_path) + self.cli_commit() def test_snat(self): rules = ['100', '110', '120', '130', '200', '210', '220', '230'] @@ -48,15 +48,15 @@ class TestNAT(unittest.TestCase): # depending of rule order we check either for source address for NAT # or configured destination address for NAT if int(rule) < 200: - self.session.set(src_path + ['rule', rule, 'source', 'address', network]) - self.session.set(src_path + ['rule', rule, 'outbound-interface', outbound_iface_100]) - self.session.set(src_path + ['rule', rule, 'translation', 'address', 'masquerade']) + self.cli_set(src_path + ['rule', rule, 'source', 'address', network]) + self.cli_set(src_path + ['rule', rule, 'outbound-interface', outbound_iface_100]) + self.cli_set(src_path + ['rule', rule, 'translation', 'address', 'masquerade']) else: - self.session.set(src_path + ['rule', rule, 'destination', 'address', network]) - self.session.set(src_path + ['rule', rule, 'outbound-interface', outbound_iface_200]) - self.session.set(src_path + ['rule', rule, 'exclude']) + self.cli_set(src_path + ['rule', rule, 'destination', 'address', network]) + self.cli_set(src_path + ['rule', rule, 'outbound-interface', outbound_iface_200]) + self.cli_set(src_path + ['rule', rule, 'exclude']) - self.session.commit() + self.cli_commit() tmp = cmd('sudo nft -j list table nat') data_json = jmespath.search('nftables[?rule].rule[?chain]', json.loads(tmp)) @@ -98,17 +98,17 @@ class TestNAT(unittest.TestCase): for rule in rules: port = f'10{rule}' - self.session.set(dst_path + ['rule', rule, 'source', 'port', port]) - self.session.set(dst_path + ['rule', rule, 'translation', 'address', '192.0.2.1']) - self.session.set(dst_path + ['rule', rule, 'translation', 'port', port]) + self.cli_set(dst_path + ['rule', rule, 'source', 'port', port]) + self.cli_set(dst_path + ['rule', rule, 'translation', 'address', '192.0.2.1']) + self.cli_set(dst_path + ['rule', rule, 'translation', 'port', port]) if int(rule) < 200: - self.session.set(dst_path + ['rule', rule, 'protocol', inbound_proto_100]) - self.session.set(dst_path + ['rule', rule, 'inbound-interface', inbound_iface_100]) + self.cli_set(dst_path + ['rule', rule, 'protocol', inbound_proto_100]) + self.cli_set(dst_path + ['rule', rule, 'inbound-interface', inbound_iface_100]) else: - self.session.set(dst_path + ['rule', rule, 'protocol', inbound_proto_200]) - self.session.set(dst_path + ['rule', rule, 'inbound-interface', inbound_iface_200]) + self.cli_set(dst_path + ['rule', rule, 'protocol', inbound_proto_200]) + self.cli_set(dst_path + ['rule', rule, 'inbound-interface', inbound_iface_200]) - self.session.commit() + self.cli_commit() tmp = cmd('sudo nft -j list table nat') data_json = jmespath.search('nftables[?rule].rule[?chain]', json.loads(tmp)) @@ -138,23 +138,45 @@ class TestNAT(unittest.TestCase): else: self.assertEqual(iface, inbound_iface_200) - def test_snat_required_translation_address(self): # T2813: Ensure translation address is specified rule = '5' - self.session.set(src_path + ['rule', rule, 'source', 'address', '192.0.2.0/24']) + self.cli_set(src_path + ['rule', rule, 'source', 'address', '192.0.2.0/24']) # check validate() - outbound-interface must be defined with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(src_path + ['rule', rule, 'outbound-interface', 'eth0']) + self.cli_commit() + self.cli_set(src_path + ['rule', rule, 'outbound-interface', 'eth0']) # check validate() - translation address not specified with self.assertRaises(ConfigSessionError): - self.session.commit() + self.cli_commit() + + self.cli_set(src_path + ['rule', rule, 'translation', 'address', 'masquerade']) + self.cli_commit() + + def test_dnat_negated_addresses(self): + # T3186: negated addresses are not accepted by nftables + rule = '1000' + self.cli_set(dst_path + ['rule', rule, 'destination', 'address', '!192.0.2.1']) + self.cli_set(dst_path + ['rule', rule, 'destination', 'port', '53']) + self.cli_set(dst_path + ['rule', rule, 'inbound-interface', 'eth0']) + self.cli_set(dst_path + ['rule', rule, 'protocol', 'tcp_udp']) + self.cli_set(dst_path + ['rule', rule, 'source', 'address', '!192.0.2.1']) + self.cli_set(dst_path + ['rule', rule, 'translation', 'address', '192.0.2.1']) + self.cli_set(dst_path + ['rule', rule, 'translation', 'port', '53']) + self.cli_commit() + + def test_nat_no_rules(self): + # T3206: deleting all rules but keep the direction 'destination' or + # 'source' resulteds in KeyError: 'rule'. + # + # Test that both 'nat destination' and 'nat source' nodes can exist + # without any rule + self.cli_set(src_path) + self.cli_set(dst_path) + self.cli_commit() - self.session.set(src_path + ['rule', rule, 'translation', 'address', 'masquerade']) - self.session.commit() if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_nat66.py b/smoketest/scripts/cli/test_nat66.py new file mode 100755 index 000000000..dca92c97d --- /dev/null +++ b/smoketest/scripts/cli/test_nat66.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 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 <http://www.gnu.org/licenses/>. + +import os +import jmespath +import json +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError +from vyos.util import cmd +from vyos.util import dict_search + +base_path = ['nat66'] +src_path = base_path + ['source'] +dst_path = base_path + ['destination'] + +class TestNAT66(VyOSUnitTestSHIM.TestCase): + def setUp(self): + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + self.cli_delete(base_path) + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + def test_source_nat66(self): + source_prefix = 'fc00::/64' + translation_prefix = 'fc01::/64' + self.cli_set(src_path + ['rule', '1', 'outbound-interface', 'eth1']) + self.cli_set(src_path + ['rule', '1', 'source', 'prefix', source_prefix]) + self.cli_set(src_path + ['rule', '1', 'translation', 'address', translation_prefix]) + + # check validate() - outbound-interface must be defined + self.cli_commit() + + tmp = cmd('sudo nft -j list table ip6 nat') + data_json = jmespath.search('nftables[?rule].rule[?chain]', json.loads(tmp)) + + for idx in range(0, len(data_json)): + data = data_json[idx] + + self.assertEqual(data['chain'], 'POSTROUTING') + self.assertEqual(data['family'], 'ip6') + self.assertEqual(data['table'], 'nat') + + iface = dict_search('match.right', data['expr'][0]) + address = dict_search('match.right.prefix.addr', data['expr'][2]) + mask = dict_search('match.right.prefix.len', data['expr'][2]) + translation_address = dict_search('snat.addr.prefix.addr', data['expr'][3]) + translation_mask = dict_search('snat.addr.prefix.len', data['expr'][3]) + + self.assertEqual(iface, 'eth1') + # check for translation address + self.assertEqual(f'{translation_address}/{translation_mask}', translation_prefix) + self.assertEqual(f'{address}/{mask}', source_prefix) + + def test_source_nat66_address(self): + source_prefix = 'fc00::/64' + translation_address = 'fc00::1' + self.cli_set(src_path + ['rule', '1', 'outbound-interface', 'eth1']) + self.cli_set(src_path + ['rule', '1', 'source', 'prefix', source_prefix]) + self.cli_set(src_path + ['rule', '1', 'translation', 'address', translation_address]) + + # check validate() - outbound-interface must be defined + self.cli_commit() + + tmp = cmd('sudo nft -j list table ip6 nat') + data_json = jmespath.search('nftables[?rule].rule[?chain]', json.loads(tmp)) + + for idx in range(0, len(data_json)): + data = data_json[idx] + + self.assertEqual(data['chain'], 'POSTROUTING') + self.assertEqual(data['family'], 'ip6') + self.assertEqual(data['table'], 'nat') + + iface = dict_search('match.right', data['expr'][0]) + address = dict_search('match.right.prefix.addr', data['expr'][2]) + mask = dict_search('match.right.prefix.len', data['expr'][2]) + snat_address = dict_search('snat.addr', data['expr'][3]) + + self.assertEqual(iface, 'eth1') + # check for translation address + self.assertEqual(snat_address, translation_address) + self.assertEqual(f'{address}/{mask}', source_prefix) + + def test_destination_nat66(self): + destination_address = 'fc00::1' + translation_address = 'fc01::1' + self.cli_set(dst_path + ['rule', '1', 'inbound-interface', 'eth1']) + self.cli_set(dst_path + ['rule', '1', 'destination', 'address', destination_address]) + self.cli_set(dst_path + ['rule', '1', 'translation', 'address', translation_address]) + + # check validate() - outbound-interface must be defined + self.cli_commit() + + tmp = cmd('sudo nft -j list table ip6 nat') + data_json = jmespath.search('nftables[?rule].rule[?chain]', json.loads(tmp)) + + for idx in range(0, len(data_json)): + data = data_json[idx] + + self.assertEqual(data['chain'], 'PREROUTING') + self.assertEqual(data['family'], 'ip6') + self.assertEqual(data['table'], 'nat') + + iface = dict_search('match.right', data['expr'][0]) + dnat_addr = dict_search('dnat.addr', data['expr'][3]) + + self.assertEqual(dnat_addr, translation_address) + self.assertEqual(iface, 'eth1') + + def test_destination_nat66_prefix(self): + destination_prefix = 'fc00::/64' + translation_prefix = 'fc01::/64' + self.cli_set(dst_path + ['rule', '1', 'inbound-interface', 'eth1']) + self.cli_set(dst_path + ['rule', '1', 'destination', 'address', destination_prefix]) + self.cli_set(dst_path + ['rule', '1', 'translation', 'address', translation_prefix]) + + # check validate() - outbound-interface must be defined + self.cli_commit() + + tmp = cmd('sudo nft -j list table ip6 nat') + data_json = jmespath.search('nftables[?rule].rule[?chain]', json.loads(tmp)) + + for idx in range(0, len(data_json)): + data = data_json[idx] + + self.assertEqual(data['chain'], 'PREROUTING') + self.assertEqual(data['family'], 'ip6') + self.assertEqual(data['table'], 'nat') + + iface = dict_search('match.right', data['expr'][0]) + translation_address = dict_search('dnat.addr.prefix.addr', data['expr'][3]) + translation_mask = dict_search('dnat.addr.prefix.len', data['expr'][3]) + + self.assertEqual(f'{translation_address}/{translation_mask}', translation_prefix) + self.assertEqual(iface, 'eth1') + + def test_source_nat66_required_translation_prefix(self): + # T2813: Ensure translation address is specified + rule = '5' + source_prefix = 'fc00::/64' + self.cli_set(src_path + ['rule', rule, 'source', 'prefix', source_prefix]) + + # check validate() - outbound-interface must be defined + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(src_path + ['rule', rule, 'outbound-interface', 'eth0']) + + # check validate() - translation address not specified + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(src_path + ['rule', rule, 'translation', 'address', 'masquerade']) + self.cli_commit() + + def test_nat66_no_rules(self): + # T3206: deleting all rules but keep the direction 'destination' or + # 'source' resulteds in KeyError: 'rule'. + # + # Test that both 'nat destination' and 'nat source' nodes can exist + # without any rule + self.cli_set(src_path) + self.cli_set(dst_path) + self.cli_commit() + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py new file mode 100755 index 000000000..57557af68 --- /dev/null +++ b/smoketest/scripts/cli/test_policy.py @@ -0,0 +1,702 @@ +#!/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 <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError +from vyos.util import cmd + +base_path = ['policy'] + +class TestPolicy(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + def test_access_list(self): + acls = { + '50' : { + 'rule' : { + '5' : { + 'action' : 'permit', + 'source' : { 'any' : '' }, + }, + '10' : { + 'action' : 'deny', + 'source' : { 'host' : '1.2.3.4' }, + }, + }, + }, + '150' : { + 'rule' : { + '10' : { + 'action' : 'permit', + 'source' : { 'any' : '' }, + 'destination' : { 'host' : '2.2.2.2' }, + }, + '10' : { + 'action' : 'deny', + 'source' : { 'any' : '' }, + 'destination' : { 'any' : '' }, + }, + }, + }, + '2000' : { + 'rule' : { + '10' : { + 'action' : 'permit', + 'destination' : { 'any' : '' }, + 'source' : { 'network' : '10.0.0.0', 'inverse-mask' : '0.255.255.255' }, + }, + '20' : { + 'action' : 'permit', + 'destination' : { 'any' : '' }, + 'source' : { 'network' : '172.16.0.0', 'inverse-mask' : '0.15.255.255' }, + }, + '30' : { + 'action' : 'permit', + 'destination' : { 'any' : '' }, + 'source' : { 'network' : '192.168.0.0', 'inverse-mask' : '0.0.255.255' }, + }, + '50' : { + 'action' : 'permit', + 'destination' : { 'network' : '172.16.0.0', 'inverse-mask' : '0.15.255.255' }, + 'source' : { 'network' : '10.0.0.0', 'inverse-mask' : '0.255.255.255' }, + }, + '60' : { + 'action' : 'deny', + 'destination' : { 'network' : '192.168.0.0', 'inverse-mask' : '0.0.255.255' }, + 'source' : { 'network' : '172.16.0.0', 'inverse-mask' : '0.15.255.255' }, + }, + '70' : { + 'action' : 'deny', + 'destination' : { 'any' : '' }, + 'source' : { 'any' : '' }, + }, + }, + }, + } + + for acl, acl_config in acls.items(): + path = base_path + ['access-list', acl] + self.cli_set(path + ['description', f'VyOS-ACL-{acl}']) + if 'rule' not in acl_config: + continue + + for rule, rule_config in acl_config['rule'].items(): + self.cli_set(path + ['rule', rule, 'action', rule_config['action']]) + for direction in ['source', 'destination']: + if direction in rule_config: + if 'any' in rule_config[direction]: + self.cli_set(path + ['rule', rule, direction, 'any']) + if 'host' in rule_config[direction]: + self.cli_set(path + ['rule', rule, direction, 'host', rule_config[direction]['host']]) + if 'network' in rule_config[direction]: + self.cli_set(path + ['rule', rule, direction, 'network', rule_config[direction]['network']]) + self.cli_set(path + ['rule', rule, direction, 'inverse-mask', rule_config[direction]['inverse-mask']]) + + self.cli_commit() + + config = self.getFRRconfig('access-list', end='') + for acl, acl_config in acls.items(): + seq = '5' + for rule, rule_config in acl_config['rule'].items(): + tmp = f'access-list {acl} seq {seq}' + if rule_config['action'] == 'permit': + tmp += ' permit' + else: + tmp += ' deny' + + if {'source', 'destination'} <= set(rule_config): + tmp += ' ip' + + for direction in ['source', 'destination']: + if direction in rule_config: + if 'any' in rule_config[direction]: + tmp += ' any' + if 'host' in rule_config[direction]: + tmp += ' ' + rule_config[direction]['host'] + if 'network' in rule_config[direction]: + tmp += ' ' + rule_config[direction]['network'] + ' ' + rule_config[direction]['inverse-mask'] + + self.assertIn(tmp, config) + seq = int(seq) + 5 + + def test_access_list6(self): + acls = { + '50' : { + 'rule' : { + '5' : { + 'action' : 'permit', + 'source' : { 'any' : '' }, + }, + '10' : { + 'action' : 'deny', + 'source' : { 'network' : '2001:db8:10::/48', 'exact-match' : '' }, + }, + '10' : { + 'action' : 'deny', + 'source' : { 'network' : '2001:db8:20::/48' }, + }, + }, + }, + '100' : { + 'rule' : { + '5' : { + 'action' : 'deny', + 'source' : { 'network' : '2001:db8:10::/64', 'exact-match' : '' }, + }, + '10' : { + 'action' : 'deny', + 'source' : { 'network' : '2001:db8:20::/64', }, + }, + '15' : { + 'action' : 'deny', + 'source' : { 'network' : '2001:db8:30::/64', 'exact-match' : '' }, + }, + '20' : { + 'action' : 'deny', + 'source' : { 'network' : '2001:db8:40::/64', 'exact-match' : '' }, + }, + '100' : { + 'action' : 'deny', + 'source' : { 'any' : '' }, + }, + }, + }, + } + + for acl, acl_config in acls.items(): + path = base_path + ['access-list6', acl] + self.cli_set(path + ['description', f'VyOS-ACL-{acl}']) + if 'rule' not in acl_config: + continue + + for rule, rule_config in acl_config['rule'].items(): + self.cli_set(path + ['rule', rule, 'action', rule_config['action']]) + for direction in ['source', 'destination']: + if direction in rule_config: + if 'any' in rule_config[direction]: + self.cli_set(path + ['rule', rule, direction, 'any']) + if 'network' in rule_config[direction]: + self.cli_set(path + ['rule', rule, direction, 'network', rule_config[direction]['network']]) + if 'exact-match' in rule_config[direction]: + self.cli_set(path + ['rule', rule, direction, 'exact-match']) + + self.cli_commit() + + config = self.getFRRconfig('ipv6 access-list', end='') + for acl, acl_config in acls.items(): + seq = '5' + for rule, rule_config in acl_config['rule'].items(): + tmp = f'ipv6 access-list {acl} seq {seq}' + if rule_config['action'] == 'permit': + tmp += ' permit' + else: + tmp += ' deny' + + if {'source', 'destination'} <= set(rule_config): + tmp += ' ip' + + for direction in ['source', 'destination']: + if direction in rule_config: + if 'any' in rule_config[direction]: + tmp += ' any' + if 'network' in rule_config[direction]: + tmp += ' ' + rule_config[direction]['network'] + if 'exact-match' in rule_config[direction]: + tmp += ' exact-match' + + self.assertIn(tmp, config) + seq = int(seq) + 5 + + + def test_as_path_list(self): + test_data = { + 'VyOS' : { + 'rule' : { + '10' : { + 'action' : 'permit', + 'regex' : '^44501 64502$', + }, + '20' : { + 'action' : 'permit', + 'regex' : '44501|44502|44503', + }, + '30' : { + 'action' : 'permit', + 'regex' : '^44501_([0-9]+_)+', + }, + }, + }, + 'Customers' : { + 'rule' : { + '10' : { + 'action' : 'permit', + 'regex' : '_10_', + }, + '20' : { + 'action' : 'permit', + 'regex' : '_20_', + }, + '30' : { + 'action' : 'permit', + 'regex' : '_30_', + }, + '30' : { + 'action' : 'deny', + 'regex' : '_40_', + }, + }, + }, + 'bogons' : { + 'rule' : { + '10' : { + 'action' : 'permit', + 'regex' : '_0_', + }, + '20' : { + 'action' : 'permit', + 'regex' : '_23456_', + }, + '30' : { + 'action' : 'permit', + 'regex' : '_6449[6-9]_|_65[0-4][0-9][0-9]_|_655[0-4][0-9]_|_6555[0-1]_', + }, + '30' : { + 'action' : 'permit', + 'regex' : '_6555[2-9]_|_655[6-9][0-9]_|_65[6-9][0-9][0-9]_|_6[6-9][0-9][0-9][0-]_|_[7-9][0-9][0-9][0-9][0-9]_|_1[0-2][0-9][0-9][0-9][0-9]_|_130[0-9][0-9][0-9]_|_1310[0-6][0-9]_|_13107[01]_', + }, + }, + }, + } + + for as_path, as_path_config in test_data.items(): + path = base_path + ['as-path-list', as_path] + self.cli_set(path + ['description', f'VyOS-ASPATH-{as_path}']) + if 'rule' not in as_path_config: + continue + + for rule, rule_config in as_path_config['rule'].items(): + if 'action' in rule_config: + self.cli_set(path + ['rule', rule, 'action', rule_config['action']]) + if 'regex' in rule_config: + self.cli_set(path + ['rule', rule, 'regex', rule_config['regex']]) + + self.cli_commit() + + config = self.getFRRconfig('bgp as-path access-list', end='') + for as_path, as_path_config in test_data.items(): + if 'rule' not in as_path_config: + continue + + for rule, rule_config in as_path_config['rule'].items(): + tmp = f'bgp as-path access-list {as_path}' + if rule_config['action'] == 'permit': + tmp += ' permit' + else: + tmp += ' deny' + + tmp += ' ' + rule_config['regex'] + + self.assertIn(tmp, config) + + def test_community_list(self): + test_data = { + '100' : { + 'rule' : { + '4' : { + 'action' : 'permit', + 'regex' : '.*', + }, + }, + }, + '200' : { + 'rule' : { + '1' : { + 'action' : 'deny', + 'regex' : '^1:201$', + }, + '2' : { + 'action' : 'deny', + 'regex' : '1:101$', + }, + '3' : { + 'action' : 'deny', + 'regex' : '^1:100$', + }, + }, + }, + } + + for comm_list, comm_list_config in test_data.items(): + path = base_path + ['community-list', comm_list] + self.cli_set(path + ['description', f'VyOS-COMM-{comm_list}']) + if 'rule' not in comm_list_config: + continue + + for rule, rule_config in comm_list_config['rule'].items(): + if 'action' in rule_config: + self.cli_set(path + ['rule', rule, 'action', rule_config['action']]) + if 'regex' in rule_config: + self.cli_set(path + ['rule', rule, 'regex', rule_config['regex']]) + + self.cli_commit() + + config = self.getFRRconfig('bgp community-list', end='') + for comm_list, comm_list_config in test_data.items(): + if 'rule' not in comm_list_config: + continue + + seq = '5' + for rule, rule_config in comm_list_config['rule'].items(): + tmp = f'bgp community-list {comm_list} seq {seq}' + if rule_config['action'] == 'permit': + tmp += ' permit' + else: + tmp += ' deny' + + tmp += ' ' + rule_config['regex'] + + self.assertIn(tmp, config) + seq = int(seq) + 5 + + def test_extended_community_list(self): + test_data = { + 'foo' : { + 'rule' : { + '4' : { + 'action' : 'permit', + 'regex' : '.*', + }, + }, + }, + '200' : { + 'rule' : { + '1' : { + 'action' : 'deny', + 'regex' : '^1:201$', + }, + '2' : { + 'action' : 'deny', + 'regex' : '1:101$', + }, + '3' : { + 'action' : 'deny', + 'regex' : '^1:100$', + }, + }, + }, + } + + for comm_list, comm_list_config in test_data.items(): + path = base_path + ['extcommunity-list', comm_list] + self.cli_set(path + ['description', f'VyOS-EXTCOMM-{comm_list}']) + if 'rule' not in comm_list_config: + continue + + for rule, rule_config in comm_list_config['rule'].items(): + if 'action' in rule_config: + self.cli_set(path + ['rule', rule, 'action', rule_config['action']]) + if 'regex' in rule_config: + self.cli_set(path + ['rule', rule, 'regex', rule_config['regex']]) + + self.cli_commit() + + config = self.getFRRconfig('bgp extcommunity-list', end='') + for comm_list, comm_list_config in test_data.items(): + if 'rule' not in comm_list_config: + continue + + seq = '5' + for rule, rule_config in comm_list_config['rule'].items(): + # if the community is not a number but a name, the expanded + # keyword is used + expanded = '' + if not comm_list.isnumeric(): + expanded = ' expanded' + tmp = f'bgp extcommunity-list{expanded} {comm_list} seq {seq}' + + if rule_config['action'] == 'permit': + tmp += ' permit' + else: + tmp += ' deny' + + tmp += ' ' + rule_config['regex'] + + self.assertIn(tmp, config) + seq = int(seq) + 5 + + + def test_large_community_list(self): + test_data = { + 'foo' : { + 'rule' : { + '10' : { + 'action' : 'permit', + 'regex' : '667:123:100', + }, + }, + }, + 'bar' : { + 'rule' : { + '10' : { + 'action' : 'permit', + 'regex' : '65000:120:10', + }, + '20' : { + 'action' : 'permit', + 'regex' : '65000:120:20', + }, + '30' : { + 'action' : 'permit', + 'regex' : '65000:120:30', + }, + }, + }, + } + + for comm_list, comm_list_config in test_data.items(): + path = base_path + ['large-community-list', comm_list] + self.cli_set(path + ['description', f'VyOS-LARGECOMM-{comm_list}']) + if 'rule' not in comm_list_config: + continue + + for rule, rule_config in comm_list_config['rule'].items(): + if 'action' in rule_config: + self.cli_set(path + ['rule', rule, 'action', rule_config['action']]) + if 'regex' in rule_config: + self.cli_set(path + ['rule', rule, 'regex', rule_config['regex']]) + + self.cli_commit() + + config = self.getFRRconfig('bgp large-community-list', end='') + for comm_list, comm_list_config in test_data.items(): + if 'rule' not in comm_list_config: + continue + + seq = '5' + for rule, rule_config in comm_list_config['rule'].items(): + tmp = f'bgp large-community-list expanded {comm_list} seq {seq}' + + if rule_config['action'] == 'permit': + tmp += ' permit' + else: + tmp += ' deny' + + tmp += ' ' + rule_config['regex'] + + self.assertIn(tmp, config) + seq = int(seq) + 5 + + + def test_prefix_list(self): + test_data = { + 'foo' : { + 'rule' : { + '10' : { + 'action' : 'permit', + 'prefix' : '10.0.0.0/8', + 'ge' : '16', + 'le' : '24', + }, + '20' : { + 'action' : 'deny', + 'prefix' : '172.16.0.0/12', + 'ge' : '16', + }, + '30' : { + 'action' : 'permit', + 'prefix' : '192.168.0.0/16', + }, + }, + }, + 'bar' : { + 'rule' : { + '10' : { + 'action' : 'permit', + 'prefix' : '10.0.10.0/24', + 'ge' : '25', + 'le' : '26', + }, + '20' : { + 'action' : 'deny', + 'prefix' : '10.0.20.0/24', + 'le' : '25', + }, + '25' : { + 'action' : 'permit', + 'prefix' : '10.0.25.0/24', + }, + }, + }, + } + + for prefix_list, prefix_list_config in test_data.items(): + path = base_path + ['prefix-list', prefix_list] + self.cli_set(path + ['description', f'VyOS-PFX-LIST-{prefix_list}']) + if 'rule' not in prefix_list_config: + continue + + for rule, rule_config in prefix_list_config['rule'].items(): + if 'action' in rule_config: + self.cli_set(path + ['rule', rule, 'action', rule_config['action']]) + if 'prefix' in rule_config: + self.cli_set(path + ['rule', rule, 'prefix', rule_config['prefix']]) + if 'ge' in rule_config: + self.cli_set(path + ['rule', rule, 'ge', rule_config['ge']]) + if 'le' in rule_config: + self.cli_set(path + ['rule', rule, 'le', rule_config['le']]) + + self.cli_commit() + + config = self.getFRRconfig('ip prefix-list', end='') + for prefix_list, prefix_list_config in test_data.items(): + if 'rule' not in prefix_list_config: + continue + + for rule, rule_config in prefix_list_config['rule'].items(): + tmp = f'ip prefix-list {prefix_list} seq {rule}' + + if rule_config['action'] == 'permit': + tmp += ' permit' + else: + tmp += ' deny' + + tmp += ' ' + rule_config['prefix'] + + if 'ge' in rule_config: + tmp += ' ge ' + rule_config['ge'] + if 'le' in rule_config: + tmp += ' le ' + rule_config['le'] + + self.assertIn(tmp, config) + + + def test_prefix_list6(self): + test_data = { + 'foo' : { + 'rule' : { + '10' : { + 'action' : 'permit', + 'prefix' : '2001:db8::/32', + 'ge' : '40', + 'le' : '48', + }, + '20' : { + 'action' : 'deny', + 'prefix' : '2001:db8::/32', + 'ge' : '48', + }, + '30' : { + 'action' : 'permit', + 'prefix' : '2001:db8:1000::/64', + }, + }, + }, + 'bar' : { + 'rule' : { + '10' : { + 'action' : 'permit', + 'prefix' : '2001:db8:100::/40', + 'ge' : '48', + }, + '20' : { + 'action' : 'permit', + 'prefix' : '2001:db8:200::/40', + 'ge' : '48', + }, + '25' : { + 'action' : 'deny', + 'prefix' : '2001:db8:300::/40', + 'le' : '64', + }, + }, + }, + } + + for prefix_list, prefix_list_config in test_data.items(): + path = base_path + ['prefix-list6', prefix_list] + self.cli_set(path + ['description', f'VyOS-PFX-LIST-{prefix_list}']) + if 'rule' not in prefix_list_config: + continue + + for rule, rule_config in prefix_list_config['rule'].items(): + if 'action' in rule_config: + self.cli_set(path + ['rule', rule, 'action', rule_config['action']]) + if 'prefix' in rule_config: + self.cli_set(path + ['rule', rule, 'prefix', rule_config['prefix']]) + if 'ge' in rule_config: + self.cli_set(path + ['rule', rule, 'ge', rule_config['ge']]) + if 'le' in rule_config: + self.cli_set(path + ['rule', rule, 'le', rule_config['le']]) + + self.cli_commit() + + config = self.getFRRconfig('ipv6 prefix-list', end='') + for prefix_list, prefix_list_config in test_data.items(): + if 'rule' not in prefix_list_config: + continue + + for rule, rule_config in prefix_list_config['rule'].items(): + tmp = f'ipv6 prefix-list {prefix_list} seq {rule}' + + if rule_config['action'] == 'permit': + tmp += ' permit' + else: + tmp += ' deny' + + tmp += ' ' + rule_config['prefix'] + + if 'ge' in rule_config: + tmp += ' ge ' + rule_config['ge'] + if 'le' in rule_config: + tmp += ' le ' + rule_config['le'] + + self.assertIn(tmp, config) + + + # Test set table for some sources + def test_table_id(self): + path = base_path + ['local-route'] + + sources = ['203.0.113.1', '203.0.113.2'] + rule = '50' + table = '23' + for src in sources: + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'source', src]) + + self.cli_commit() + + # Check generated configuration + + # Expected values + original = """ + 50: from 203.0.113.1 lookup 23 + 50: from 203.0.113.2 lookup 23 + """ + tmp = cmd('ip rule show prio 50') + original = original.split() + tmp = tmp.split() + + self.assertEqual(tmp, original) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_policy_local-route.py b/smoketest/scripts/cli/test_policy_local-route.py deleted file mode 100755 index de1882a65..000000000 --- a/smoketest/scripts/cli/test_policy_local-route.py +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 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 <http://www.gnu.org/licenses/>. - -import os -import unittest - -from vyos.configsession import ConfigSession -from vyos.configsession import ConfigSessionError -from vyos.util import cmd -from vyos.util import process_named_running - -class PolicyLocalRouteTest(unittest.TestCase): - def setUp(self): - self.session = ConfigSession(os.getpid()) - self._sources = ['203.0.113.1', '203.0.113.2'] - - def tearDown(self): - # Delete all policies - self.session.delete(['policy', 'local-route']) - self.session.commit() - del self.session - - # Test set table for some sources - def test_table_id(self): - base = ['policy', 'local-route'] - rule = '50' - table = '23' - for src in self._sources: - self.session.set(base + ['rule', rule, 'set', 'table', table]) - self.session.set(base + ['rule', rule, 'source', src]) - - self.session.commit() - - # Check generated configuration - - # Expected values - original = """ - 50: from 203.0.113.1 lookup 23 - 50: from 203.0.113.2 lookup 23 - """ - tmp = cmd('ip rule show prio 50') - original = original.split() - tmp = tmp.split() - - self.assertEqual(tmp, original) - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_bfd.py b/smoketest/scripts/cli/test_protocols_bfd.py new file mode 100755 index 000000000..a57f8d5f2 --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_bfd.py @@ -0,0 +1,170 @@ +#!/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 <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError +from vyos.util import process_named_running + +PROCESS_NAME = 'bfdd' +base_path = ['protocols', 'bfd'] + +dum_if = 'dum1001' +peers = { + '192.0.2.10' : { + 'intv_rx' : '500', + 'intv_tx' : '600', + 'multihop' : '', + 'source_addr': '192.0.2.254', + }, + '192.0.2.20' : { + 'echo_mode' : '', + 'intv_echo' : '100', + 'intv_mult' : '100', + 'intv_rx' : '222', + 'intv_tx' : '333', + 'shutdown' : '', + 'source_intf': dum_if, + }, + '2001:db8::a' : { + 'source_addr': '2001:db8::1', + 'source_intf': dum_if, + }, + '2001:db8::b' : { + 'source_addr': '2001:db8::1', + 'multihop' : '', + }, +} + +profiles = { + 'foo' : { + 'echo_mode' : '', + 'intv_echo' : '100', + 'intv_mult' : '101', + 'intv_rx' : '222', + 'intv_tx' : '333', + 'shutdown' : '', + }, + 'bar' : { + 'intv_mult' : '102', + 'intv_rx' : '444', + }, +} + +class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_bfd_peer(self): + for peer, peer_config in peers.items(): + if 'echo_mode' in peer_config: + self.cli_set(base_path + ['peer', peer, 'echo-mode']) + if 'intv_echo' in peer_config: + self.cli_set(base_path + ['peer', peer, 'interval', 'echo-interval', peer_config["intv_echo"]]) + if 'intv_mult' in peer_config: + self.cli_set(base_path + ['peer', peer, 'interval', 'multiplier', peer_config["intv_mult"]]) + if 'intv_rx' in peer_config: + self.cli_set(base_path + ['peer', peer, 'interval', 'receive', peer_config["intv_rx"]]) + if 'intv_tx' in peer_config: + self.cli_set(base_path + ['peer', peer, 'interval', 'transmit', peer_config["intv_tx"]]) + if 'multihop' in peer_config: + self.cli_set(base_path + ['peer', peer, 'multihop']) + if 'shutdown' in peer_config: + self.cli_set(base_path + ['peer', peer, 'shutdown']) + if 'source_addr' in peer_config: + self.cli_set(base_path + ['peer', peer, 'source', 'address', peer_config["source_addr"]]) + if 'source_intf' in peer_config: + self.cli_set(base_path + ['peer', peer, 'source', 'interface', peer_config["source_intf"]]) + + # commit changes + self.cli_commit() + + # Verify FRR bgpd configuration + frrconfig = self.getFRRconfig('bfd') + for peer, peer_config in peers.items(): + tmp = f'peer {peer}' + if 'multihop' in peer_config: + tmp += f' multihop' + if 'source_addr' in peer_config: + tmp += f' local-address {peer_config["source_addr"]}' + if 'source_intf' in peer_config: + tmp += f' interface {peer_config["source_intf"]}' + + self.assertIn(tmp, frrconfig) + peerconfig = self.getFRRconfig(f' peer {peer}', end='') + + if 'echo_mode' in peer_config: + self.assertIn(f'echo-mode', peerconfig) + if 'intv_echo' in peer_config: + self.assertIn(f'echo-interval {peer_config["intv_echo"]}', peerconfig) + if 'intv_mult' in peer_config: + self.assertIn(f'detect-multiplier {peer_config["intv_mult"]}', peerconfig) + if 'intv_rx' in peer_config: + self.assertIn(f'receive-interval {peer_config["intv_rx"]}', peerconfig) + if 'intv_tx' in peer_config: + self.assertIn(f'transmit-interval {peer_config["intv_tx"]}', peerconfig) + if 'shutdown' in peer_config: + self.assertIn(f'shutdown', peerconfig) + else: + self.assertNotIn(f'shutdown', peerconfig) + + def test_bfd_profile(self): + peer = '192.0.2.10' + + for profile, profile_config in profiles.items(): + if 'echo_mode' in profile_config: + self.cli_set(base_path + ['profile', profile, 'echo-mode']) + if 'intv_echo' in profile_config: + self.cli_set(base_path + ['profile', profile, 'interval', 'echo-interval', profile_config["intv_echo"]]) + if 'intv_mult' in profile_config: + self.cli_set(base_path + ['profile', profile, 'interval', 'multiplier', profile_config["intv_mult"]]) + if 'intv_rx' in profile_config: + self.cli_set(base_path + ['profile', profile, 'interval', 'receive', profile_config["intv_rx"]]) + if 'intv_tx' in profile_config: + self.cli_set(base_path + ['profile', profile, 'interval', 'transmit', profile_config["intv_tx"]]) + if 'shutdown' in profile_config: + self.cli_set(base_path + ['profile', profile, 'shutdown']) + + self.cli_set(base_path + ['peer', peer, 'profile', list(profiles)[0]]) + + # commit changes + self.cli_commit() + + # Verify FRR bgpd configuration + for profile, profile_config in profiles.items(): + config = self.getFRRconfig(f' profile {profile}', endsection='^ !') + if 'echo_mode' in profile_config: + self.assertIn(f'echo-mode', config) + if 'intv_echo' in profile_config: + self.assertIn(f'echo-interval {profile_config["intv_echo"]}', config) + if 'intv_mult' in profile_config: + self.assertIn(f'detect-multiplier {profile_config["intv_mult"]}', config) + if 'intv_rx' in profile_config: + self.assertIn(f'receive-interval {profile_config["intv_rx"]}', config) + if 'intv_tx' in profile_config: + self.assertIn(f'transmit-interval {profile_config["intv_tx"]}', config) + if 'shutdown' in profile_config: + self.assertIn(f'shutdown', config) + else: + self.assertNotIn(f'shutdown', config) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index 1d93aeda4..4f39948c0 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -14,87 +14,147 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import os import unittest -from vyos.configsession import ConfigSession +from base_vyostest_shim import VyOSUnitTestSHIM + from vyos.configsession import ConfigSessionError -from vyos.util import cmd +from vyos.template import is_ipv6 from vyos.util import process_named_running PROCESS_NAME = 'bgpd' ASN = '64512' -base_path = ['protocols', 'bgp', ASN] +base_path = ['protocols', 'bgp'] + +route_map_in = 'foo-map-in' +route_map_out = 'foo-map-out' +prefix_list_in = 'pfx-foo-in' +prefix_list_out = 'pfx-foo-out' +prefix_list_in6 = 'pfx-foo-in6' +prefix_list_out6 = 'pfx-foo-out6' neighbor_config = { '192.0.2.1' : { - 'cap_dynamic' : '', - 'cap_ext_next': '', - 'remote_as' : '100', - 'adv_interv' : '400', - 'passive' : '', - 'password' : 'VyOS-Secure123', - 'shutdown' : '', - 'cap_over' : '', - 'ttl_security': '5', - 'local_as' : '300', + 'cap_dynamic' : '', + 'cap_ext_next' : '', + 'remote_as' : '100', + 'adv_interv' : '400', + 'passive' : '', + 'password' : 'VyOS-Secure123', + 'shutdown' : '', + 'cap_over' : '', + 'ttl_security' : '5', + 'local_as' : '300', + 'route_map_in' : route_map_in, + 'route_map_out': route_map_out, + 'no_send_comm_ext' : '', + 'addpath_all' : '', }, '192.0.2.2' : { - 'remote_as' : '200', - 'shutdown' : '', - 'no_cap_nego' : '', - 'port' : '667', - 'cap_strict' : '', + 'remote_as' : '200', + 'shutdown' : '', + 'no_cap_nego' : '', + 'port' : '667', + 'cap_strict' : '', + 'pfx_list_in' : prefix_list_in, + 'pfx_list_out' : prefix_list_out, + 'no_send_comm_std' : '', }, '192.0.2.3' : { -# XXX: not available in current Perl backend -# 'description' : 'foo bar baz', - 'remote_as' : '200', - 'passive' : '', - 'multi_hop' : '5', - 'update_src' : 'lo', + 'description' : 'foo bar baz', + 'remote_as' : '200', + 'passive' : '', + 'multi_hop' : '5', + 'update_src' : 'lo', + }, + '2001:db8::1' : { + 'cap_dynamic' : '', + 'cap_ext_next' : '', + 'remote_as' : '123', + 'adv_interv' : '400', + 'passive' : '', + 'password' : 'VyOS-Secure123', + 'shutdown' : '', + 'cap_over' : '', + 'ttl_security' : '5', + 'local_as' : '300', + 'route_map_in' : route_map_in, + 'route_map_out': route_map_out, + 'no_send_comm_std' : '', + 'addpath_per_as' : '', + }, + '2001:db8::2' : { + 'remote_as' : '456', + 'shutdown' : '', + 'no_cap_nego' : '', + 'port' : '667', + 'cap_strict' : '', + 'pfx_list_in' : prefix_list_in6, + 'pfx_list_out' : prefix_list_out6, + 'no_send_comm_ext' : '', }, } peer_group_config = { 'foo' : { - 'remote_as' : '100', - 'passive' : '', - 'password' : 'VyOS-Secure123', - 'shutdown' : '', - 'cap_over' : '', + 'remote_as' : '100', + 'passive' : '', + 'password' : 'VyOS-Secure123', + 'shutdown' : '', + 'cap_over' : '', # XXX: not available in current Perl backend # 'ttl_security': '5', }, 'bar' : { -# XXX: not available in current Perl backend -# 'description' : 'foo peer bar group', - 'remote_as' : '200', - 'shutdown' : '', - 'no_cap_nego' : '', - 'local_as' : '300', + 'description' : 'foo peer bar group', + 'remote_as' : '200', + 'shutdown' : '', + 'no_cap_nego' : '', + 'local_as' : '300', + 'pfx_list_in' : prefix_list_in, + 'pfx_list_out' : prefix_list_out, + 'no_send_comm_ext' : '', }, 'baz' : { - 'cap_dynamic' : '', - 'cap_ext_next': '', - 'remote_as' : '200', - 'passive' : '', - 'multi_hop' : '5', - 'update_src' : 'lo', + 'cap_dynamic' : '', + 'cap_ext_next' : '', + 'remote_as' : '200', + 'passive' : '', + 'multi_hop' : '5', + 'update_src' : 'lo', + 'route_map_in' : route_map_in, + 'route_map_out': route_map_out, }, } -def getFRRBGPconfig(): - return cmd(f'vtysh -c "show run" | sed -n "/router bgp {ASN}/,/^!/p"') -class TestProtocolsBGP(unittest.TestCase): +class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): def setUp(self): - self.session = ConfigSession(os.getpid()) + self.cli_set(['policy', 'route-map', route_map_in, 'rule', '10', 'action', 'permit']) + self.cli_set(['policy', 'route-map', route_map_out, 'rule', '10', 'action', 'permit']) + self.cli_set(['policy', 'prefix-list', prefix_list_in, 'rule', '10', 'action', 'permit']) + self.cli_set(['policy', 'prefix-list', prefix_list_in, 'rule', '10', 'prefix', '192.0.2.0/25']) + self.cli_set(['policy', 'prefix-list', prefix_list_out, 'rule', '10', 'action', 'permit']) + self.cli_set(['policy', 'prefix-list', prefix_list_out, 'rule', '10', 'prefix', '192.0.2.128/25']) + + self.cli_set(['policy', 'prefix-list6', prefix_list_in6, 'rule', '10', 'action', 'permit']) + self.cli_set(['policy', 'prefix-list6', prefix_list_in6, 'rule', '10', 'prefix', '2001:db8:1000::/64']) + self.cli_set(['policy', 'prefix-list6', prefix_list_out6, 'rule', '10', 'action', 'deny']) + self.cli_set(['policy', 'prefix-list6', prefix_list_out6, 'rule', '10', 'prefix', '2001:db8:2000::/64']) def tearDown(self): - self.session.delete(base_path) - self.session.commit() - del self.session + self.cli_delete(['policy', 'route-map', route_map_in]) + self.cli_delete(['policy', 'route-map', route_map_out]) + self.cli_delete(['policy', 'prefix-list', prefix_list_in]) + self.cli_delete(['policy', 'prefix-list', prefix_list_out]) + self.cli_delete(['policy', 'prefix-list6', prefix_list_in6]) + self.cli_delete(['policy', 'prefix-list6', prefix_list_out6]) + + self.cli_delete(base_path) + self.cli_commit() + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) def verify_frr_config(self, peer, peer_config, frrconfig): # recurring patterns to verify for both a simple neighbor and a peer-group @@ -124,121 +184,205 @@ class TestProtocolsBGP(unittest.TestCase): self.assertIn(f' neighbor {peer} ttl-security hops {peer_config["ttl_security"]}', frrconfig) if 'update_src' in peer_config: self.assertIn(f' neighbor {peer} update-source {peer_config["update_src"]}', frrconfig) + if 'route_map_in' in peer_config: + self.assertIn(f' neighbor {peer} route-map {peer_config["route_map_in"]} in', frrconfig) + if 'route_map_out' in peer_config: + self.assertIn(f' neighbor {peer} route-map {peer_config["route_map_out"]} out', frrconfig) + if 'pfx_list_in' in peer_config: + self.assertIn(f' neighbor {peer} prefix-list {peer_config["pfx_list_in"]} in', frrconfig) + if 'pfx_list_out' in peer_config: + self.assertIn(f' neighbor {peer} prefix-list {peer_config["pfx_list_out"]} out', frrconfig) + if 'no_send_comm_std' in peer_config: + self.assertIn(f' no neighbor {peer} send-community', frrconfig) + if 'no_send_comm_ext' in peer_config: + self.assertIn(f' no neighbor {peer} send-community extended', frrconfig) + if 'addpath_all' in peer_config: + self.assertIn(f' neighbor {peer} addpath-tx-all-paths', frrconfig) + if 'addpath_per_as' in peer_config: + self.assertIn(f' neighbor {peer} addpath-tx-bestpath-per-AS', frrconfig) + def test_bgp_01_simple(self): router_id = '127.0.0.1' local_pref = '500' - - self.session.set(base_path + ['parameters', 'router-id', router_id]) - self.session.set(base_path + ['parameters', 'log-neighbor-changes']) - # Default local preference (higher=more preferred) - self.session.set(base_path + ['parameters', 'default', 'local-pref', local_pref]) + stalepath_time = '60' + max_path_v4 = '2' + max_path_v4ibgp = '4' + max_path_v6 = '8' + max_path_v6ibgp = '16' + + self.cli_set(base_path + ['parameters', 'router-id', router_id]) + self.cli_set(base_path + ['parameters', 'log-neighbor-changes']) + + # Local AS number MUST be defined + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['local-as', ASN]) + + # Default local preference (higher = more preferred, default value is 100) + self.cli_set(base_path + ['parameters', 'default', 'local-pref', local_pref]) # Deactivate IPv4 unicast for a peer by default - self.session.set(base_path + ['parameters', 'default', 'no-ipv4-unicast']) + self.cli_set(base_path + ['parameters', 'default', 'no-ipv4-unicast']) + self.cli_set(base_path + ['parameters', 'graceful-restart', 'stalepath-time', stalepath_time]) + self.cli_set(base_path + ['parameters', 'graceful-shutdown']) + self.cli_set(base_path + ['parameters', 'ebgp-requires-policy']) + + # AFI maximum path support + self.cli_set(base_path + ['address-family', 'ipv4-unicast', 'maximum-paths', 'ebgp', max_path_v4]) + self.cli_set(base_path + ['address-family', 'ipv4-unicast', 'maximum-paths', 'ibgp', max_path_v4ibgp]) + self.cli_set(base_path + ['address-family', 'ipv6-unicast', 'maximum-paths', 'ebgp', max_path_v6]) + self.cli_set(base_path + ['address-family', 'ipv6-unicast', 'maximum-paths', 'ibgp', max_path_v6ibgp]) # commit changes - self.session.commit() + self.cli_commit() # Verify FRR bgpd configuration - frrconfig = getFRRBGPconfig() + frrconfig = self.getFRRconfig(f'router bgp {ASN}') self.assertIn(f'router bgp {ASN}', frrconfig) self.assertIn(f' bgp router-id {router_id}', frrconfig) self.assertIn(f' bgp log-neighbor-changes', frrconfig) self.assertIn(f' bgp default local-preference {local_pref}', frrconfig) self.assertIn(f' no bgp default ipv4-unicast', frrconfig) + self.assertIn(f' bgp graceful-restart stalepath-time {stalepath_time}', frrconfig) + self.assertIn(f' bgp graceful-shutdown', frrconfig) + self.assertNotIn(f'bgp ebgp-requires-policy', frrconfig) + + afiv4_config = self.getFRRconfig(' address-family ipv4 unicast') + self.assertIn(f' maximum-paths {max_path_v4}', afiv4_config) + self.assertIn(f' maximum-paths ibgp {max_path_v4ibgp}', afiv4_config) + + afiv6_config = self.getFRRconfig(' address-family ipv6 unicast') + self.assertIn(f' maximum-paths {max_path_v6}', afiv6_config) + self.assertIn(f' maximum-paths ibgp {max_path_v6ibgp}', afiv6_config) - # Check for running process - self.assertTrue(process_named_running(PROCESS_NAME)) def test_bgp_02_neighbors(self): + self.cli_set(base_path + ['local-as', ASN]) # Test out individual neighbor configuration items, not all of them are # also available to a peer-group! - for neighbor, config in neighbor_config.items(): - if 'adv_interv' in config: - self.session.set(base_path + ['neighbor', neighbor, 'advertisement-interval', config["adv_interv"]]) - if 'cap_dynamic' in config: - self.session.set(base_path + ['neighbor', neighbor, 'capability', 'dynamic']) - if 'cap_ext_next' in config: - self.session.set(base_path + ['neighbor', neighbor, 'capability', 'extended-nexthop']) - if 'description' in config: - self.session.set(base_path + ['neighbor', neighbor, 'description', config["description"]]) - if 'no_cap_nego' in config: - self.session.set(base_path + ['neighbor', neighbor, 'disable-capability-negotiation']) - if 'multi_hop' in config: - self.session.set(base_path + ['neighbor', neighbor, 'ebgp-multihop', config["multi_hop"]]) - if 'local_as' in config: - self.session.set(base_path + ['neighbor', neighbor, 'local-as', config["local_as"]]) - if 'cap_over' in config: - self.session.set(base_path + ['neighbor', neighbor, 'override-capability']) - if 'passive' in config: - self.session.set(base_path + ['neighbor', neighbor, 'passive']) - if 'password' in config: - self.session.set(base_path + ['neighbor', neighbor, 'password', config["password"]]) - if 'port' in config: - self.session.set(base_path + ['neighbor', neighbor, 'port', config["port"]]) - if 'remote_as' in config: - self.session.set(base_path + ['neighbor', neighbor, 'remote-as', config["remote_as"]]) - if 'cap_strict' in config: - self.session.set(base_path + ['neighbor', neighbor, 'strict-capability-match']) - if 'shutdown' in config: - self.session.set(base_path + ['neighbor', neighbor, 'shutdown']) - if 'ttl_security' in config: - self.session.set(base_path + ['neighbor', neighbor, 'ttl-security', 'hops', config["ttl_security"]]) - if 'update_src' in config: - self.session.set(base_path + ['neighbor', neighbor, 'update-source', config["update_src"]]) + for peer, peer_config in neighbor_config.items(): + afi = 'ipv4-unicast' + if is_ipv6(peer): + afi = 'ipv6-unicast' + + if 'adv_interv' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'advertisement-interval', peer_config["adv_interv"]]) + if 'cap_dynamic' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'capability', 'dynamic']) + if 'cap_ext_next' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'capability', 'extended-nexthop']) + if 'description' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'description', peer_config["description"]]) + if 'no_cap_nego' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'disable-capability-negotiation']) + if 'multi_hop' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'ebgp-multihop', peer_config["multi_hop"]]) + if 'local_as' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'local-as', peer_config["local_as"]]) + if 'cap_over' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'override-capability']) + if 'passive' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'passive']) + if 'password' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'password', peer_config["password"]]) + if 'port' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'port', peer_config["port"]]) + if 'remote_as' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'remote-as', peer_config["remote_as"]]) + if 'cap_strict' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'strict-capability-match']) + if 'shutdown' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'shutdown']) + if 'ttl_security' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'ttl-security', 'hops', peer_config["ttl_security"]]) + if 'update_src' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'update-source', peer_config["update_src"]]) + if 'route_map_in' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'route-map', 'import', peer_config["route_map_in"]]) + if 'route_map_out' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'route-map', 'export', peer_config["route_map_out"]]) + if 'pfx_list_in' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'prefix-list', 'import', peer_config["pfx_list_in"]]) + if 'pfx_list_out' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'prefix-list', 'export', peer_config["pfx_list_out"]]) + if 'no_send_comm_std' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'disable-send-community', 'standard']) + if 'no_send_comm_ext' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'disable-send-community', 'extended']) + if 'addpath_all' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'addpath-tx-all']) + if 'addpath_per_as' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'addpath-tx-per-as']) # commit changes - self.session.commit() + self.cli_commit() # Verify FRR bgpd configuration - frrconfig = getFRRBGPconfig() + frrconfig = self.getFRRconfig(f'router bgp {ASN}') self.assertIn(f'router bgp {ASN}', frrconfig) for peer, peer_config in neighbor_config.items(): - if 'adv_interv' in config: + if 'adv_interv' in peer_config: self.assertIn(f' neighbor {peer} advertisement-interval {peer_config["adv_interv"]}', frrconfig) - if 'port' in config: + if 'port' in peer_config: self.assertIn(f' neighbor {peer} port {peer_config["port"]}', frrconfig) - if 'cap_strict' in config: + if 'cap_strict' in peer_config: self.assertIn(f' neighbor {peer} strict-capability-match', frrconfig) self.verify_frr_config(peer, peer_config, frrconfig) def test_bgp_03_peer_groups(self): + self.cli_set(base_path + ['local-as', ASN]) # Test out individual peer-group configuration items for peer_group, config in peer_group_config.items(): if 'cap_dynamic' in config: - self.session.set(base_path + ['peer-group', peer_group, 'capability', 'dynamic']) + self.cli_set(base_path + ['peer-group', peer_group, 'capability', 'dynamic']) if 'cap_ext_next' in config: - self.session.set(base_path + ['peer-group', peer_group, 'capability', 'extended-nexthop']) + self.cli_set(base_path + ['peer-group', peer_group, 'capability', 'extended-nexthop']) if 'description' in config: - self.session.set(base_path + ['peer-group', peer_group, 'description', config["description"]]) + self.cli_set(base_path + ['peer-group', peer_group, 'description', config["description"]]) if 'no_cap_nego' in config: - self.session.set(base_path + ['peer-group', peer_group, 'disable-capability-negotiation']) + self.cli_set(base_path + ['peer-group', peer_group, 'disable-capability-negotiation']) if 'multi_hop' in config: - self.session.set(base_path + ['peer-group', peer_group, 'ebgp-multihop', config["multi_hop"]]) + self.cli_set(base_path + ['peer-group', peer_group, 'ebgp-multihop', config["multi_hop"]]) if 'local_as' in config: - self.session.set(base_path + ['peer-group', peer_group, 'local-as', config["local_as"]]) + self.cli_set(base_path + ['peer-group', peer_group, 'local-as', config["local_as"]]) if 'cap_over' in config: - self.session.set(base_path + ['peer-group', peer_group, 'override-capability']) + self.cli_set(base_path + ['peer-group', peer_group, 'override-capability']) if 'passive' in config: - self.session.set(base_path + ['peer-group', peer_group, 'passive']) + self.cli_set(base_path + ['peer-group', peer_group, 'passive']) if 'password' in config: - self.session.set(base_path + ['peer-group', peer_group, 'password', config["password"]]) + self.cli_set(base_path + ['peer-group', peer_group, 'password', config["password"]]) if 'remote_as' in config: - self.session.set(base_path + ['peer-group', peer_group, 'remote-as', config["remote_as"]]) + self.cli_set(base_path + ['peer-group', peer_group, 'remote-as', config["remote_as"]]) if 'shutdown' in config: - self.session.set(base_path + ['peer-group', peer_group, 'shutdown']) + self.cli_set(base_path + ['peer-group', peer_group, 'shutdown']) if 'ttl_security' in config: - self.session.set(base_path + ['peer-group', peer_group, 'ttl-security', 'hops', config["ttl_security"]]) + self.cli_set(base_path + ['peer-group', peer_group, 'ttl-security', 'hops', config["ttl_security"]]) if 'update_src' in config: - self.session.set(base_path + ['peer-group', peer_group, 'update-source', config["update_src"]]) + self.cli_set(base_path + ['peer-group', peer_group, 'update-source', config["update_src"]]) + if 'route_map_in' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'route-map', 'import', config["route_map_in"]]) + if 'route_map_out' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'route-map', 'export', config["route_map_out"]]) + if 'pfx_list_in' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'prefix-list', 'import', config["pfx_list_in"]]) + if 'pfx_list_out' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'prefix-list', 'export', config["pfx_list_out"]]) + if 'no_send_comm_std' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'disable-send-community', 'standard']) + if 'no_send_comm_ext' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'disable-send-community', 'extended']) + if 'addpath_all' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'addpath-tx-all']) + if 'addpath_per_as' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'addpath-tx-per-as']) # commit changes - self.session.commit() + self.cli_commit() # Verify FRR bgpd configuration - frrconfig = getFRRBGPconfig() + frrconfig = self.getFRRconfig(f'router bgp {ASN}') self.assertIn(f'router bgp {ASN}', frrconfig) for peer, peer_config in peer_group_config.items(): @@ -259,27 +403,29 @@ class TestProtocolsBGP(unittest.TestCase): }, } + self.cli_set(base_path + ['local-as', ASN]) + # We want to redistribute ... - redistributes = ['connected', 'kernel', 'ospf', 'rip', 'static'] + redistributes = ['connected', 'isis', 'kernel', 'ospf', 'rip', 'static'] for redistribute in redistributes: - self.session.set(base_path + ['address-family', 'ipv4-unicast', + self.cli_set(base_path + ['address-family', 'ipv4-unicast', 'redistribute', redistribute]) for network, network_config in networks.items(): - self.session.set(base_path + ['address-family', 'ipv4-unicast', + self.cli_set(base_path + ['address-family', 'ipv4-unicast', 'network', network]) if 'as_set' in network_config: - self.session.set(base_path + ['address-family', 'ipv4-unicast', + self.cli_set(base_path + ['address-family', 'ipv4-unicast', 'aggregate-address', network, 'as-set']) if 'summary_only' in network_config: - self.session.set(base_path + ['address-family', 'ipv4-unicast', + self.cli_set(base_path + ['address-family', 'ipv4-unicast', 'aggregate-address', network, 'summary-only']) # commit changes - self.session.commit() + self.cli_commit() # Verify FRR bgpd configuration - frrconfig = getFRRBGPconfig() + frrconfig = self.getFRRconfig(f'router bgp {ASN}') self.assertIn(f'router bgp {ASN}', frrconfig) self.assertIn(f' address-family ipv4 unicast', frrconfig) @@ -297,34 +443,39 @@ class TestProtocolsBGP(unittest.TestCase): def test_bgp_05_afi_ipv6(self): networks = { '2001:db8:100::/48' : { - }, + }, '2001:db8:200::/48' : { - }, + }, '2001:db8:300::/48' : { 'summary_only' : '', - }, + }, } + self.cli_set(base_path + ['local-as', ASN]) + # We want to redistribute ... redistributes = ['connected', 'kernel', 'ospfv3', 'ripng', 'static'] for redistribute in redistributes: - self.session.set(base_path + ['address-family', 'ipv6-unicast', + self.cli_set(base_path + ['address-family', 'ipv6-unicast', 'redistribute', redistribute]) for network, network_config in networks.items(): - self.session.set(base_path + ['address-family', 'ipv6-unicast', + self.cli_set(base_path + ['address-family', 'ipv6-unicast', 'network', network]) if 'summary_only' in network_config: - self.session.set(base_path + ['address-family', 'ipv6-unicast', + self.cli_set(base_path + ['address-family', 'ipv6-unicast', 'aggregate-address', network, 'summary-only']) # commit changes - self.session.commit() + self.cli_commit() # Verify FRR bgpd configuration - frrconfig = getFRRBGPconfig() + frrconfig = self.getFRRconfig(f'router bgp {ASN}') self.assertIn(f'router bgp {ASN}', frrconfig) self.assertIn(f' address-family ipv6 unicast', frrconfig) + # T2100: By default ebgp-requires-policy is disabled to keep VyOS + # 1.3 and 1.2 backwards compatibility + self.assertIn(f' no bgp ebgp-requires-policy', frrconfig) for redistribute in redistributes: # FRR calls this OSPF6 @@ -338,5 +489,95 @@ class TestProtocolsBGP(unittest.TestCase): self.assertIn(f' aggregate-address {network} summary-only', frrconfig) + def test_bgp_06_listen_range(self): + # Implemented via T1875 + limit = '64' + listen_ranges = ['192.0.2.0/25', '192.0.2.128/25'] + peer_group = 'listenfoobar' + + self.cli_set(base_path + ['local-as', ASN]) + self.cli_set(base_path + ['listen', 'limit', limit]) + + for prefix in listen_ranges: + self.cli_set(base_path + ['listen', 'range', prefix]) + # check validate() - peer-group must be defined for range/prefix + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['listen', 'range', prefix, 'peer-group', peer_group]) + + # check validate() - peer-group does yet not exist! + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['peer-group', peer_group, 'remote-as', ASN]) + + # commit changes + self.cli_commit() + + # Verify FRR bgpd configuration + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f' neighbor {peer_group} peer-group', frrconfig) + self.assertIn(f' neighbor {peer_group} remote-as {ASN}', frrconfig) + self.assertIn(f' bgp listen limit {limit}', frrconfig) + for prefix in listen_ranges: + self.assertIn(f' bgp listen range {prefix} peer-group {peer_group}', frrconfig) + + + def test_bgp_07_l2vpn_evpn(self): + vnis = ['10010', '10020', '10030'] + neighbors = ['192.0.2.10', '192.0.2.20', '192.0.2.30'] + + self.cli_set(base_path + ['local-as', ASN]) + + self.cli_set(base_path + ['address-family', 'l2vpn-evpn', 'advertise-all-vni']) + self.cli_set(base_path + ['address-family', 'l2vpn-evpn', 'advertise-default-gw']) + self.cli_set(base_path + ['address-family', 'l2vpn-evpn', 'advertise-svi-ip']) + self.cli_set(base_path + ['address-family', 'l2vpn-evpn', 'flooding', 'disable']) + for vni in vnis: + self.cli_set(base_path + ['address-family', 'l2vpn-evpn', 'vni', vni, 'advertise-default-gw']) + self.cli_set(base_path + ['address-family', 'l2vpn-evpn', 'vni', vni, 'advertise-svi-ip']) + + # commit changes + self.cli_commit() + + # Verify FRR bgpd configuration + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f' address-family l2vpn evpn', frrconfig) + self.assertIn(f' advertise-all-vni', frrconfig) + self.assertIn(f' advertise-default-gw', frrconfig) + self.assertIn(f' advertise-svi-ip', frrconfig) + self.assertIn(f' flooding disable', frrconfig) + for vni in vnis: + vniconfig = self.getFRRconfig(f' vni {vni}') + self.assertIn(f'vni {vni}', vniconfig) + self.assertIn(f' advertise-default-gw', vniconfig) + self.assertIn(f' advertise-svi-ip', vniconfig) + + def test_bgp_08_vrf_simple(self): + router_id = '127.0.0.3' + vrfs = ['red', 'green', 'blue'] + + # It is safe to assume that when the basic VRF test works, all + # other BGP related features work, as we entirely inherit the CLI + # templates and Jinja2 FRR template. + table = '1000' + + for vrf in vrfs: + vrf_base = ['vrf', 'name', vrf] + self.cli_set(vrf_base + ['table', table]) + self.cli_set(vrf_base + ['protocols', 'bgp', 'local-as', ASN]) + self.cli_set(vrf_base + ['protocols', 'bgp', 'parameters', 'router-id', router_id]) + table = str(int(table) + 1000) + + self.cli_commit() + + for vrf in vrfs: + # Verify FRR bgpd configuration + frrconfig = self.getFRRconfig(f'router bgp {ASN} vrf {vrf}') + + self.assertIn(f'router bgp {ASN} vrf {vrf}', frrconfig) + self.assertIn(f' bgp router-id {router_id}', frrconfig) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_igmp-proxy.py b/smoketest/scripts/cli/test_protocols_igmp-proxy.py index 6aaad739d..1eaf21722 100755 --- a/smoketest/scripts/cli/test_protocols_igmp-proxy.py +++ b/smoketest/scripts/cli/test_protocols_igmp-proxy.py @@ -14,9 +14,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import os import unittest +from base_vyostest_shim import VyOSUnitTestSHIM + from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError from vyos.util import read_file @@ -28,46 +29,44 @@ base_path = ['protocols', 'igmp-proxy'] upstream_if = 'eth1' downstream_if = 'eth2' -class TestProtocolsIGMPProxy(unittest.TestCase): +class TestProtocolsIGMPProxy(VyOSUnitTestSHIM.TestCase): def setUp(self): - self.session = ConfigSession(os.getpid()) - self.session.set(['interfaces', 'ethernet', upstream_if, 'address', '172.16.1.1/24']) + self.cli_set(['interfaces', 'ethernet', upstream_if, 'address', '172.16.1.1/24']) def tearDown(self): - self.session.delete(['interfaces', 'ethernet', upstream_if, 'address']) - self.session.delete(base_path) - self.session.commit() - del self.session + self.cli_delete(['interfaces', 'ethernet', upstream_if, 'address']) + self.cli_delete(base_path) + self.cli_commit() def test_igmpproxy(self): threshold = '20' altnet = '192.0.2.0/24' whitelist = '10.0.0.0/8' - self.session.set(base_path + ['disable-quickleave']) - self.session.set(base_path + ['interface', upstream_if, 'threshold', threshold]) - self.session.set(base_path + ['interface', upstream_if, 'alt-subnet', altnet]) - self.session.set(base_path + ['interface', upstream_if, 'whitelist', whitelist]) + self.cli_set(base_path + ['disable-quickleave']) + self.cli_set(base_path + ['interface', upstream_if, 'threshold', threshold]) + self.cli_set(base_path + ['interface', upstream_if, 'alt-subnet', altnet]) + self.cli_set(base_path + ['interface', upstream_if, 'whitelist', whitelist]) # Must define an upstream and at least 1 downstream interface! with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(base_path + ['interface', upstream_if, 'role', 'upstream']) + self.cli_commit() + self.cli_set(base_path + ['interface', upstream_if, 'role', 'upstream']) # Interface does not exist - self.session.set(base_path + ['interface', 'eth20', 'role', 'upstream']) + self.cli_set(base_path + ['interface', 'eth20', 'role', 'upstream']) with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.delete(base_path + ['interface', 'eth20']) + self.cli_commit() + self.cli_delete(base_path + ['interface', 'eth20']) # Only 1 upstream interface allowed - self.session.set(base_path + ['interface', downstream_if, 'role', 'upstream']) + self.cli_set(base_path + ['interface', downstream_if, 'role', 'upstream']) with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(base_path + ['interface', downstream_if, 'role', 'downstream']) + self.cli_commit() + self.cli_set(base_path + ['interface', downstream_if, 'role', 'downstream']) # commit changes - self.session.commit() + self.cli_commit() # Check generated configuration config = read_file(IGMP_PROXY_CONF) diff --git a/smoketest/scripts/cli/test_protocols_isis.py b/smoketest/scripts/cli/test_protocols_isis.py new file mode 100755 index 000000000..623cb044d --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_isis.py @@ -0,0 +1,102 @@ +#!/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 <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Section +from vyos.util import process_named_running + +PROCESS_NAME = 'isisd' +base_path = ['protocols', 'isis'] + +domain = 'VyOS' +net = '49.0001.1921.6800.1002.00' + +class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_isis_01_redistribute(self): + prefix_list = 'EXPORT-ISIS' + route_map = 'EXPORT-ISIS' + rule = '10' + self.cli_set(['policy', 'prefix-list', prefix_list, 'rule', rule, 'action', 'permit']) + self.cli_set(['policy', 'prefix-list', prefix_list, 'rule', rule, 'prefix', '203.0.113.0/24']) + self.cli_set(['policy', 'route-map', route_map, 'rule', rule, 'action', 'permit']) + self.cli_set(['policy', 'route-map', route_map, 'rule', rule, 'match', 'ip', 'address', 'prefix-list', prefix_list]) + + self.cli_set(base_path + ['net', net]) + self.cli_set(base_path + ['redistribute', 'ipv4', 'connected', 'level-2', 'route-map', route_map]) + + interfaces = Section.interfaces('ethernet') + for interface in interfaces: + self.cli_set(base_path + ['interface', interface]) + + # Commit all changes + self.cli_commit() + + # Verify all changes + tmp = self.getFRRconfig(f'router isis {domain}') + self.assertIn(f' net {net}', tmp) + self.assertIn(f' redistribute ipv4 connected level-2 route-map {route_map}', tmp) + + for interface in interfaces: + tmp = self.getFRRconfig(f'interface {interface}') + self.assertIn(f' ip router isis {domain}', tmp) + + self.cli_delete(['policy']) + + + def test_isis_02_vrfs(self): + vrfs = ['red', 'green', 'blue'] + # It is safe to assume that when the basic VRF test works, all other + # IS-IS related features work, as we entirely inherit the CLI templates + # and Jinja2 FRR template. + table = '1000' + vrf = 'red' + vrf_base = ['vrf', 'name', vrf] + vrf_iface = 'eth1' + self.cli_set(vrf_base + ['table', table]) + self.cli_set(vrf_base + ['protocols', 'isis', 'net', net]) + self.cli_set(vrf_base + ['protocols', 'isis', 'interface', vrf_iface]) + self.cli_set(['interfaces', 'ethernet', vrf_iface, 'vrf', vrf]) + + # Also set a default VRF IS-IS config + self.cli_set(base_path + ['net', net]) + self.cli_set(base_path + ['interface', 'eth0']) + self.cli_commit() + + # Verify FRR isisd configuration + tmp = self.getFRRconfig(f'router isis {domain}') + self.assertIn(f'router isis {domain}', tmp) + self.assertIn(f' net {net}', tmp) + + tmp = self.getFRRconfig(f'router isis {domain} vrf {vrf}') + self.assertIn(f'router isis {domain} vrf {vrf}', tmp) + self.assertIn(f' net {net}', tmp) + + self.cli_delete(['vrf', 'name', vrf]) + self.cli_delete(['interfaces', 'ethernet', vrf_iface, 'vrf']) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py new file mode 100755 index 000000000..8d94c86cb --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_ospf.py @@ -0,0 +1,321 @@ +#!/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 <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.ifconfig import Section +from vyos.util import process_named_running + +PROCESS_NAME = 'ospfd' +base_path = ['protocols', 'ospf'] + +route_map = 'foo-bar-baz10' + +class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): + def setUp(self): + self.cli_set(['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit']) + self.cli_set(['policy', 'route-map', route_map, 'rule', '20', 'action', 'permit']) + + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + self.cli_delete(['policy', 'route-map', route_map]) + self.cli_delete(base_path) + self.cli_commit() + + def test_ospf_01_defaults(self): + # commit changes + self.cli_set(base_path) + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf') + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' auto-cost reference-bandwidth 100', frrconfig) + self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults + + def test_ospf_02_simple(self): + router_id = '127.0.0.1' + abr_type = 'ibm' + bandwidth = '1000' + metric = '123' + + self.cli_set(base_path + ['auto-cost', 'reference-bandwidth', bandwidth]) + self.cli_set(base_path + ['parameters', 'router-id', router_id]) + self.cli_set(base_path + ['parameters', 'abr-type', abr_type]) + self.cli_set(base_path + ['log-adjacency-changes', 'detail']) + self.cli_set(base_path + ['default-metric', metric]) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf') + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' auto-cost reference-bandwidth {bandwidth}', frrconfig) + self.assertIn(f' ospf router-id {router_id}', frrconfig) + self.assertIn(f' ospf abr-type {abr_type}', frrconfig) + self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults + self.assertIn(f' default-metric {metric}', frrconfig) + + + def test_ospf_03_access_list(self): + acl = '100' + seq = '10' + protocols = ['bgp', 'connected', 'isis', 'kernel', 'rip', 'static'] + + self.cli_set(['policy', 'access-list', acl, 'rule', seq, 'action', 'permit']) + self.cli_set(['policy', 'access-list', acl, 'rule', seq, 'source', 'any']) + self.cli_set(['policy', 'access-list', acl, 'rule', seq, 'destination', 'any']) + for ptotocol in protocols: + self.cli_set(base_path + ['access-list', acl, 'export', ptotocol]) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf') + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults + for ptotocol in protocols: + self.assertIn(f' distribute-list {acl} out {ptotocol}', frrconfig) # defaults + self.cli_delete(['policy', 'access-list', acl]) + + + def test_ospf_04_default_originate(self): + seq = '100' + metric = '50' + metric_type = '1' + + self.cli_set(base_path + ['default-information', 'originate', 'metric', metric]) + self.cli_set(base_path + ['default-information', 'originate', 'metric-type', metric_type]) + self.cli_set(base_path + ['default-information', 'originate', 'route-map', route_map]) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf') + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults + self.assertIn(f' default-information originate metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) + + # Now set 'always' + self.cli_set(base_path + ['default-information', 'originate', 'always']) + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf') + self.assertIn(f' default-information originate always metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) + + + def test_ospf_05_options(self): + global_distance = '128' + intra_area = '100' + inter_area = '110' + external = '120' + on_startup = '30' + on_shutdown = '60' + refresh = '50' + + self.cli_set(base_path + ['distance', 'global', global_distance]) + self.cli_set(base_path + ['distance', 'ospf', 'external', external]) + self.cli_set(base_path + ['distance', 'ospf', 'intra-area', intra_area]) + + self.cli_set(base_path + ['max-metric', 'router-lsa', 'on-startup', on_startup]) + self.cli_set(base_path + ['max-metric', 'router-lsa', 'on-shutdown', on_shutdown]) + + self.cli_set(base_path + ['mpls-te', 'enable']) + self.cli_set(base_path + ['refresh', 'timers', refresh]) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf') + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' mpls-te on', frrconfig) + self.assertIn(f' mpls-te router-address 0.0.0.0', frrconfig) # default + self.assertIn(f' distance {global_distance}', frrconfig) + self.assertIn(f' distance ospf intra-area {intra_area} external {external}', frrconfig) + self.assertIn(f' max-metric router-lsa on-startup {on_startup}', frrconfig) + self.assertIn(f' max-metric router-lsa on-shutdown {on_shutdown}', frrconfig) + self.assertIn(f' refresh timer {refresh}', frrconfig) + + + # enable inter-area + self.cli_set(base_path + ['distance', 'ospf', 'inter-area', inter_area]) + self.cli_commit() + + frrconfig = self.getFRRconfig('router ospf') + self.assertIn(f' distance ospf intra-area {intra_area} inter-area {inter_area} external {external}', frrconfig) + + + def test_ospf_06_neighbor(self): + priority = '10' + poll_interval = '20' + neighbors = ['1.1.1.1', '2.2.2.2', '3.3.3.3'] + for neighbor in neighbors: + self.cli_set(base_path + ['neighbor', neighbor, 'priority', priority]) + self.cli_set(base_path + ['neighbor', neighbor, 'poll-interval', poll_interval]) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf') + self.assertIn(f'router ospf', frrconfig) + for neighbor in neighbors: + self.assertIn(f' neighbor {neighbor} priority {priority} poll-interval {poll_interval}', frrconfig) # default + + + def test_ospf_07_passive_interface(self): + self.cli_set(base_path + ['passive-interface', 'default']) + interfaces = Section.interfaces('ethernet') + for interface in interfaces: + self.cli_set(base_path + ['passive-interface-exclude', interface]) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf') + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' passive-interface default', frrconfig) # default + for interface in interfaces: + self.assertIn(f' no passive-interface {interface}', frrconfig) # default + + + def test_ospf_08_redistribute(self): + metric = '15' + metric_type = '1' + redistribute = ['bgp', 'connected', 'isis', 'kernel', 'rip', 'static'] + + for protocol in redistribute: + self.cli_set(base_path + ['redistribute', protocol, 'metric', metric]) + self.cli_set(base_path + ['redistribute', protocol, 'route-map', route_map]) + if protocol not in ['kernel', 'static']: + self.cli_set(base_path + ['redistribute', protocol, 'metric-type', metric_type]) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf') + self.assertIn(f'router ospf', frrconfig) + for protocol in redistribute: + if protocol in ['kernel', 'static']: + self.assertIn(f' redistribute {protocol} metric {metric} route-map {route_map}', frrconfig) + else: + self.assertIn(f' redistribute {protocol} metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) + + + def test_ospf_09_virtual_link(self): + networks = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'] + area = '10' + shortcut = 'enable' + virtual_link = '192.0.2.1' + hello = '6' + retransmit = '5' + transmit = '5' + dead = '40' + + self.cli_set(base_path + ['area', area, 'shortcut', shortcut]) + self.cli_set(base_path + ['area', area, 'virtual-link', virtual_link, 'hello-interval', hello]) + self.cli_set(base_path + ['area', area, 'virtual-link', virtual_link, 'retransmit-interval', retransmit]) + self.cli_set(base_path + ['area', area, 'virtual-link', virtual_link, 'transmit-delay', transmit]) + self.cli_set(base_path + ['area', area, 'virtual-link', virtual_link, 'dead-interval', dead]) + for network in networks: + self.cli_set(base_path + ['area', area, 'network', network]) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf') + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' area {area} shortcut {shortcut}', frrconfig) + self.assertIn(f' area {area} virtual-link {virtual_link} hello-interval {hello} retransmit-interval {retransmit} transmit-delay {transmit} dead-interval {dead}', frrconfig) + for network in networks: + self.assertIn(f' network {network} area {area}', frrconfig) + + + def test_ospf_10_interface_configureation(self): + interfaces = Section.interfaces('ethernet') + password = 'vyos1234' + bandwidth = '10000' + cost = '150' + network = 'point-to-point' + priority = '200' + + for interface in interfaces: + self.cli_set(base_path + ['interface', interface, 'authentication', 'plaintext-password', password]) + self.cli_set(base_path + ['interface', interface, 'bandwidth', bandwidth]) + self.cli_set(base_path + ['interface', interface, 'bfd']) + self.cli_set(base_path + ['interface', interface, 'cost', cost]) + self.cli_set(base_path + ['interface', interface, 'mtu-ignore']) + self.cli_set(base_path + ['interface', interface, 'network', network]) + self.cli_set(base_path + ['interface', interface, 'priority', priority]) + + # commit changes + self.cli_commit() + + for interface in interfaces: + config = self.getFRRconfig(f'interface {interface}') + self.assertIn(f'interface {interface}', config) + self.assertIn(f' ip ospf authentication-key {password}', config) + self.assertIn(f' ip ospf bfd', config) + self.assertIn(f' ip ospf cost {cost}', config) + self.assertIn(f' ip ospf mtu-ignore', config) + self.assertIn(f' ip ospf network {network}', config) + self.assertIn(f' ip ospf priority {priority}', config) + self.assertIn(f' bandwidth {bandwidth}', config) + + + def test_ospf_11_vrfs(self): + # It is safe to assume that when the basic VRF test works, all + # other OSPF related features work, as we entirely inherit the CLI + # templates and Jinja2 FRR template. + table = '1000' + vrf = 'blue' + vrf_base = ['vrf', 'name', vrf] + vrf_iface = 'eth1' + self.cli_set(vrf_base + ['table', table]) + self.cli_set(vrf_base + ['protocols', 'ospf', 'interface', vrf_iface]) + self.cli_set(['interfaces', 'ethernet', vrf_iface, 'vrf', vrf]) + + # Also set a default VRF OSPF config + self.cli_set(base_path) + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf') + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' auto-cost reference-bandwidth 100', frrconfig) + self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults + + frrconfig = self.getFRRconfig(f'router ospf vrf {vrf}') + self.assertIn(f'router ospf vrf {vrf}', frrconfig) + self.assertIn(f' auto-cost reference-bandwidth 100', frrconfig) + self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults + + self.cli_delete(['vrf', 'name', vrf]) + self.cli_delete(['interfaces', 'ethernet', vrf_iface, 'vrf']) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_ospfv3.py b/smoketest/scripts/cli/test_protocols_ospfv3.py new file mode 100755 index 000000000..6bb551642 --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_ospfv3.py @@ -0,0 +1,154 @@ +#!/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 <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSession +from vyos.ifconfig import Section +from vyos.util import process_named_running + +PROCESS_NAME = 'ospf6d' +base_path = ['protocols', 'ospfv3'] + +router_id = '192.0.2.1' +default_area = '0' + +class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + self.cli_delete(base_path) + self.cli_commit() + + def test_ospfv3_01_basic(self): + seq = '10' + prefix = '2001:db8::/32' + acl_name = 'foo-acl-100' + + self.cli_set(['policy', 'access-list6', acl_name, 'rule', seq, 'action', 'permit']) + self.cli_set(['policy', 'access-list6', acl_name, 'rule', seq, 'source', 'any']) + + self.cli_set(base_path + ['parameters', 'router-id', router_id]) + self.cli_set(base_path + ['area', default_area, 'range', prefix, 'advertise']) + self.cli_set(base_path + ['area', default_area, 'export-list', acl_name]) + self.cli_set(base_path + ['area', default_area, 'import-list', acl_name]) + + interfaces = Section.interfaces('ethernet') + for interface in interfaces: + self.cli_set(base_path + ['area', default_area, 'interface', interface]) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf6') + self.assertIn(f'router ospf6', frrconfig) + self.assertIn(f' area {default_area} range {prefix}', frrconfig) + self.assertIn(f' ospf6 router-id {router_id}', frrconfig) + self.assertIn(f' area {default_area} import-list {acl_name}', frrconfig) + self.assertIn(f' area {default_area} export-list {acl_name}', frrconfig) + + for interface in interfaces: + self.assertIn(f' interface {interface} area {default_area}', frrconfig) + + self.cli_delete(['policy', 'access-list6', acl_name]) + + + def test_ospfv3_02_distance(self): + dist_global = '200' + dist_external = '110' + dist_inter_area = '120' + dist_intra_area = '130' + + self.cli_set(base_path + ['distance', 'global', dist_global]) + self.cli_set(base_path + ['distance', 'ospfv3', 'external', dist_external]) + self.cli_set(base_path + ['distance', 'ospfv3', 'inter-area', dist_inter_area]) + self.cli_set(base_path + ['distance', 'ospfv3', 'intra-area', dist_intra_area]) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf6') + self.assertIn(f'router ospf6', frrconfig) + self.assertIn(f' distance {dist_global}', frrconfig) + self.assertIn(f' distance ospf6 intra-area {dist_intra_area} inter-area {dist_inter_area} external {dist_external}', frrconfig) + + + def test_ospfv3_03_redistribute(self): + route_map = 'foo-bar' + route_map_seq = '10' + redistribute = ['bgp', 'connected', 'kernel', 'ripng', 'static'] + + self.cli_set(['policy', 'route-map', route_map, 'rule', route_map_seq, 'action', 'permit']) + + for protocol in redistribute: + self.cli_set(base_path + ['redistribute', protocol, 'route-map', route_map]) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf6') + self.assertIn(f'router ospf6', frrconfig) + for protocol in redistribute: + self.assertIn(f' redistribute {protocol} route-map {route_map}', frrconfig) + + def test_ospfv3_04_interfaces(self): + + self.cli_set(base_path + ['parameters', 'router-id', router_id]) + self.cli_set(base_path + ['area', default_area]) + + cost = '100' + priority = '10' + interfaces = Section.interfaces('ethernet') + for interface in interfaces: + if_base = base_path + ['interface', interface] + self.cli_set(if_base + ['bfd']) + self.cli_set(if_base + ['cost', cost]) + self.cli_set(if_base + ['instance-id', '0']) + self.cli_set(if_base + ['mtu-ignore']) + self.cli_set(if_base + ['network', 'point-to-point']) + self.cli_set(if_base + ['passive']) + self.cli_set(if_base + ['priority', priority]) + cost = str(int(cost) + 10) + priority = str(int(priority) + 5) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf6') + self.assertIn(f'router ospf6', frrconfig) + + cost = '100' + priority = '10' + for interface in interfaces: + if_config = self.getFRRconfig(f'interface {interface}') + self.assertIn(f'interface {interface}', if_config) + self.assertIn(f' ipv6 ospf6 bfd', if_config) + self.assertIn(f' ipv6 ospf6 cost {cost}', if_config) + self.assertIn(f' ipv6 ospf6 mtu-ignore', if_config) + self.assertIn(f' ipv6 ospf6 network point-to-point', if_config) + self.assertIn(f' ipv6 ospf6 passive', if_config) + self.assertIn(f' ipv6 ospf6 priority {priority}', if_config) + cost = str(int(cost) + 10) + priority = str(int(priority) + 5) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_rip.py b/smoketest/scripts/cli/test_protocols_rip.py new file mode 100755 index 000000000..3406688c5 --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_rip.py @@ -0,0 +1,131 @@ +#!/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 <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSession +from vyos.ifconfig import Section +from vyos.util import process_named_running + +PROCESS_NAME = 'ripd' +acl_in = '198' +acl_out = '199' +prefix_list_in = 'foo-prefix' +prefix_list_out = 'bar-prefix' +route_map = 'FooBar123' + +base_path = ['protocols', 'rip'] + +class TestProtocolsRIP(VyOSUnitTestSHIM.TestCase): + def setUp(self): + self.cli_set(['policy', 'access-list', acl_in, 'rule', '10', 'action', 'permit']) + self.cli_set(['policy', 'access-list', acl_in, 'rule', '10', 'source', 'any']) + self.cli_set(['policy', 'access-list', acl_in, 'rule', '10', 'destination', 'any']) + self.cli_set(['policy', 'access-list', acl_out, 'rule', '20', 'action', 'deny']) + self.cli_set(['policy', 'access-list', acl_out, 'rule', '20', 'source', 'any']) + self.cli_set(['policy', 'access-list', acl_out, 'rule', '20', 'destination', 'any']) + self.cli_set(['policy', 'prefix-list', prefix_list_in, 'rule', '100', 'action', 'permit']) + self.cli_set(['policy', 'prefix-list', prefix_list_in, 'rule', '100', 'prefix', '192.0.2.0/24']) + self.cli_set(['policy', 'prefix-list', prefix_list_out, 'rule', '200', 'action', 'deny']) + self.cli_set(['policy', 'prefix-list', prefix_list_out, 'rule', '200', 'prefix', '192.0.2.0/24']) + self.cli_set(['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit']) + + def tearDown(self): + self.cli_delete(base_path) + self.cli_delete(['policy', 'access-list', acl_in]) + self.cli_delete(['policy', 'access-list', acl_out]) + self.cli_delete(['policy', 'prefix-list', prefix_list_in]) + self.cli_delete(['policy', 'prefix-list', prefix_list_out]) + self.cli_delete(['policy', 'route-map', route_map]) + self.cli_commit() + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_rip(self): + distance = '40' + network_distance = '66' + metric = '8' + interfaces = Section.interfaces('ethernet') + neighbors = ['1.2.3.4', '1.2.3.5', '1.2.3.6'] + networks = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'] + redistribute = ['bgp', 'connected', 'isis', 'kernel', 'ospf', 'static'] + timer_garbage = '888' + timer_timeout = '1000' + timer_update = '90' + + self.cli_set(base_path + ['default-distance', distance]) + self.cli_set(base_path + ['default-information', 'originate']) + self.cli_set(base_path + ['default-metric', metric]) + self.cli_set(base_path + ['distribute-list', 'access-list', 'in', acl_in]) + self.cli_set(base_path + ['distribute-list', 'access-list', 'out', acl_out]) + self.cli_set(base_path + ['distribute-list', 'prefix-list', 'in', prefix_list_in]) + self.cli_set(base_path + ['distribute-list', 'prefix-list', 'out', prefix_list_out]) + self.cli_set(base_path + ['passive-interface', 'default']) + self.cli_set(base_path + ['timers', 'garbage-collection', timer_garbage]) + self.cli_set(base_path + ['timers', 'timeout', timer_timeout]) + self.cli_set(base_path + ['timers', 'update', timer_update]) + for interface in interfaces: + self.cli_set(base_path + ['interface', interface]) + self.cli_set(base_path + ['distribute-list', 'interface', interface, 'access-list', 'in', acl_in]) + self.cli_set(base_path + ['distribute-list', 'interface', interface, 'access-list', 'out', acl_out]) + self.cli_set(base_path + ['distribute-list', 'interface', interface, 'prefix-list', 'in', prefix_list_in]) + self.cli_set(base_path + ['distribute-list', 'interface', interface, 'prefix-list', 'out', prefix_list_out]) + for neighbor in neighbors: + self.cli_set(base_path + ['neighbor', neighbor]) + for network in networks: + self.cli_set(base_path + ['network', network]) + self.cli_set(base_path + ['network-distance', network, 'distance', network_distance]) + self.cli_set(base_path + ['route', network]) + for proto in redistribute: + self.cli_set(base_path + ['redistribute', proto, 'metric', metric]) + self.cli_set(base_path + ['redistribute', proto, 'route-map', route_map]) + + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router rip') + self.assertIn(f'router rip', frrconfig) + self.assertIn(f' distance {distance}', frrconfig) + self.assertIn(f' default-information originate', frrconfig) + self.assertIn(f' default-metric {metric}', frrconfig) + self.assertIn(f' distribute-list {acl_in} in', frrconfig) + self.assertIn(f' distribute-list {acl_out} out', frrconfig) + self.assertIn(f' distribute-list prefix {prefix_list_in} in', frrconfig) + self.assertIn(f' distribute-list prefix {prefix_list_out} out', frrconfig) + self.assertIn(f' passive-interface default', frrconfig) + self.assertIn(f' timers basic {timer_update} {timer_timeout} {timer_garbage}', frrconfig) + for interface in interfaces: + self.assertIn(f' network {interface}', frrconfig) + self.assertIn(f' distribute-list {acl_in} in {interface}', frrconfig) + self.assertIn(f' distribute-list {acl_out} out {interface}', frrconfig) + self.assertIn(f' distribute-list prefix {prefix_list_in} in {interface}', frrconfig) + self.assertIn(f' distribute-list prefix {prefix_list_out} out {interface}', frrconfig) + for neighbor in neighbors: + self.assertIn(f' neighbor {neighbor}', frrconfig) + for network in networks: + self.assertIn(f' network {network}', frrconfig) + self.assertIn(f' distance {network_distance} {network}', frrconfig) + self.assertIn(f' route {network}', frrconfig) + for proto in redistribute: + self.assertIn(f' redistribute {proto} metric {metric} route-map {route_map}', frrconfig) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_ripng.py b/smoketest/scripts/cli/test_protocols_ripng.py new file mode 100755 index 000000000..add92b73d --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_ripng.py @@ -0,0 +1,126 @@ +#!/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 <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSession +from vyos.ifconfig import Section +from vyos.util import process_named_running + +PROCESS_NAME = 'ripngd' +acl_in = '198' +acl_out = '199' +prefix_list_in = 'foo-prefix' +prefix_list_out = 'bar-prefix' +route_map = 'FooBar123' + +base_path = ['protocols', 'ripng'] + +class TestProtocolsRIPng(VyOSUnitTestSHIM.TestCase): + def setUp(self): + self.cli_set(['policy', 'access-list6', acl_in, 'rule', '10', 'action', 'permit']) + self.cli_set(['policy', 'access-list6', acl_in, 'rule', '10', 'source', 'any']) + self.cli_set(['policy', 'access-list6', acl_out, 'rule', '20', 'action', 'deny']) + self.cli_set(['policy', 'access-list6', acl_out, 'rule', '20', 'source', 'any']) + self.cli_set(['policy', 'prefix-list6', prefix_list_in, 'rule', '100', 'action', 'permit']) + self.cli_set(['policy', 'prefix-list6', prefix_list_in, 'rule', '100', 'prefix', '2001:db8::/32']) + self.cli_set(['policy', 'prefix-list6', prefix_list_out, 'rule', '200', 'action', 'deny']) + self.cli_set(['policy', 'prefix-list6', prefix_list_out, 'rule', '200', 'prefix', '2001:db8::/32']) + self.cli_set(['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit']) + + def tearDown(self): + self.cli_delete(base_path) + self.cli_delete(['policy', 'access-list6', acl_in]) + self.cli_delete(['policy', 'access-list6', acl_out]) + self.cli_delete(['policy', 'prefix-list6', prefix_list_in]) + self.cli_delete(['policy', 'prefix-list6', prefix_list_out]) + self.cli_delete(['policy', 'route-map', route_map]) + self.cli_commit() + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_ripng(self): + metric = '8' + interfaces = Section.interfaces('ethernet') + aggregates = ['2001:db8:1000::/48', '2001:db8:2000::/48', '2001:db8:3000::/48'] + networks = ['2001:db8:1000::/64', '2001:db8:1001::/64', '2001:db8:2000::/64', '2001:db8:2001::/64'] + redistribute = ['bgp', 'connected', 'kernel', 'ospfv3', 'static'] + timer_garbage = '888' + timer_timeout = '1000' + timer_update = '90' + + self.cli_set(base_path + ['default-information', 'originate']) + self.cli_set(base_path + ['default-metric', metric]) + self.cli_set(base_path + ['distribute-list', 'access-list', 'in', acl_in]) + self.cli_set(base_path + ['distribute-list', 'access-list', 'out', acl_out]) + self.cli_set(base_path + ['distribute-list', 'prefix-list', 'in', prefix_list_in]) + self.cli_set(base_path + ['distribute-list', 'prefix-list', 'out', prefix_list_out]) + self.cli_set(base_path + ['passive-interface', 'default']) + self.cli_set(base_path + ['timers', 'garbage-collection', timer_garbage]) + self.cli_set(base_path + ['timers', 'timeout', timer_timeout]) + self.cli_set(base_path + ['timers', 'update', timer_update]) + for aggregate in aggregates: + self.cli_set(base_path + ['aggregate-address', aggregate]) + + for interface in interfaces: + self.cli_set(base_path + ['interface', interface]) + self.cli_set(base_path + ['distribute-list', 'interface', interface, 'access-list', 'in', acl_in]) + self.cli_set(base_path + ['distribute-list', 'interface', interface, 'access-list', 'out', acl_out]) + self.cli_set(base_path + ['distribute-list', 'interface', interface, 'prefix-list', 'in', prefix_list_in]) + self.cli_set(base_path + ['distribute-list', 'interface', interface, 'prefix-list', 'out', prefix_list_out]) + for network in networks: + self.cli_set(base_path + ['network', network]) + self.cli_set(base_path + ['route', network]) + for proto in redistribute: + self.cli_set(base_path + ['redistribute', proto, 'metric', metric]) + self.cli_set(base_path + ['redistribute', proto, 'route-map', route_map]) + + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ripng') + self.assertIn(f'router ripng', frrconfig) + self.assertIn(f' default-information originate', frrconfig) + self.assertIn(f' default-metric {metric}', frrconfig) + self.assertIn(f' ipv6 distribute-list {acl_in} in', frrconfig) + self.assertIn(f' ipv6 distribute-list {acl_out} out', frrconfig) + self.assertIn(f' ipv6 distribute-list prefix {prefix_list_in} in', frrconfig) + self.assertIn(f' ipv6 distribute-list prefix {prefix_list_out} out', frrconfig) + self.assertIn(f' passive-interface default', frrconfig) + self.assertIn(f' timers basic {timer_update} {timer_timeout} {timer_garbage}', frrconfig) + for aggregate in aggregates: + self.assertIn(f' aggregate-address {aggregate}', frrconfig) + for interface in interfaces: + self.assertIn(f' network {interface}', frrconfig) + self.assertIn(f' ipv6 distribute-list {acl_in} in {interface}', frrconfig) + self.assertIn(f' ipv6 distribute-list {acl_out} out {interface}', frrconfig) + self.assertIn(f' ipv6 distribute-list prefix {prefix_list_in} in {interface}', frrconfig) + self.assertIn(f' ipv6 distribute-list prefix {prefix_list_out} out {interface}', frrconfig) + for network in networks: + self.assertIn(f' network {network}', frrconfig) + self.assertIn(f' route {network}', frrconfig) + for proto in redistribute: + if proto == 'ospfv3': + proto = 'ospf6' + self.assertIn(f' redistribute {proto} metric {metric} route-map {route_map}', frrconfig) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_rpki.py b/smoketest/scripts/cli/test_protocols_rpki.py new file mode 100755 index 000000000..8212e9469 --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_rpki.py @@ -0,0 +1,154 @@ +#!/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 <http://www.gnu.org/licenses/>. + +import os +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError +from vyos.util import cmd +from vyos.util import process_named_running + +base_path = ['protocols', 'rpki'] +PROCESS_NAME = 'bgpd' + +rpki_known_hosts = '/config/auth/known_hosts' +rpki_ssh_key = '/config/auth/id_rsa_rpki' +rpki_ssh_pub = f'{rpki_ssh_key}.pub' + +class TestProtocolsRPKI(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + # Nothing RPKI specific should be left over in the config + # + # Disabled until T3266 is resolved + # frrconfig = self.getFRRconfig('rpki') + # self.assertNotIn('rpki', frrconfig) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_rpki(self): + polling = '7200' + cache = { + '192.0.2.1' : { + 'port' : '8080', + 'preference' : '1' + }, + '192.0.2.2' : { + 'port' : '9090', + 'preference' : '2' + }, + '2001:db8::1' : { + 'port' : '1234', + 'preference' : '3' + }, + '2001:db8::2' : { + 'port' : '5678', + 'preference' : '4' + }, + } + + self.cli_set(base_path + ['polling-period', polling]) + for peer, peer_config in cache.items(): + self.cli_set(base_path + ['cache', peer, 'port', peer_config['port']]) + self.cli_set(base_path + ['cache', peer, 'preference', peer_config['preference']]) + + # commit changes + self.cli_commit() + + # Verify FRR configuration + frrconfig = self.getFRRconfig('rpki') + self.assertIn(f'rpki polling_period {polling}', frrconfig) + + for peer, peer_config in cache.items(): + port = peer_config['port'] + preference = peer_config['preference'] + self.assertIn(f'rpki cache {peer} {port} preference {preference}', frrconfig) + + def test_rpki_ssh(self): + polling = '7200' + cache = { + '192.0.2.3' : { + 'port' : '1234', + 'username' : 'foo', + 'preference' : '10' + }, + '192.0.2.4' : { + 'port' : '5678', + 'username' : 'bar', + 'preference' : '20' + }, + } + + self.cli_set(base_path + ['polling-period', polling]) + + for peer, peer_config in cache.items(): + self.cli_set(base_path + ['cache', peer, 'port', peer_config['port']]) + self.cli_set(base_path + ['cache', peer, 'preference', peer_config['preference']]) + self.cli_set(base_path + ['cache', peer, 'ssh', 'username', peer_config['username']]) + self.cli_set(base_path + ['cache', peer, 'ssh', 'public-key-file', rpki_ssh_pub]) + self.cli_set(base_path + ['cache', peer, 'ssh', 'private-key-file', rpki_ssh_key]) + self.cli_set(base_path + ['cache', peer, 'ssh', 'known-hosts-file', rpki_known_hosts]) + + # commit changes + self.cli_commit() + + # Verify FRR configuration + frrconfig = self.getFRRconfig('rpki') + self.assertIn(f'rpki polling_period {polling}', frrconfig) + + for peer, peer_config in cache.items(): + port = peer_config['port'] + preference = peer_config['preference'] + username = peer_config['username'] + self.assertIn(f'rpki cache {peer} {port} {username} {rpki_ssh_key} {rpki_known_hosts} preference {preference}', frrconfig) + + + def test_rpki_verify_preference(self): + cache = { + '192.0.2.1' : { + 'port' : '8080', + 'preference' : '1' + }, + '192.0.2.2' : { + 'port' : '9090', + 'preference' : '1' + }, + } + + for peer, peer_config in cache.items(): + self.cli_set(base_path + ['cache', peer, 'port', peer_config['port']]) + self.cli_set(base_path + ['cache', peer, 'preference', peer_config['preference']]) + + # check validate() - preferences must be unique + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + +if __name__ == '__main__': + # Create OpenSSH keypair used in RPKI tests + if not os.path.isfile(rpki_ssh_key): + cmd(f'ssh-keygen -t rsa -f {rpki_ssh_key} -N ""') + + if not os.path.isfile(rpki_known_hosts): + cmd(f'touch {rpki_known_hosts}') + + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_static.py b/smoketest/scripts/cli/test_protocols_static.py new file mode 100755 index 000000000..75d3e6a42 --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_static.py @@ -0,0 +1,396 @@ +#!/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 <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError +from vyos.template import is_ipv6 +from vyos.util import get_interface_config + +base_path = ['protocols', 'static'] +vrf_path = ['protocols', 'vrf'] + +routes = { + '10.0.0.0/8' : { + 'next_hop' : { + '192.0.2.100' : { 'distance' : '100' }, + '192.0.2.110' : { 'distance' : '110', 'interface' : 'eth0' }, + '192.0.2.120' : { 'distance' : '120', 'disable' : '' }, + }, + 'interface' : { + 'eth0' : { 'distance' : '130' }, + 'eth1' : { 'distance' : '140' }, + }, + 'blackhole' : { 'distance' : '250', 'tag' : '500' }, + }, + '172.16.0.0/12' : { + 'interface' : { + 'eth0' : { 'distance' : '50', 'vrf' : 'black' }, + 'eth1' : { 'distance' : '60', 'vrf' : 'black' }, + }, + 'blackhole' : { 'distance' : '90' }, + }, + '192.0.2.0/24' : { + 'interface' : { + 'eth0' : { 'distance' : '50', 'vrf' : 'black' }, + 'eth1' : { 'disable' : '' }, + }, + 'blackhole' : { 'distance' : '90' }, + }, + '100.64.0.0/10' : { + 'blackhole' : { }, + }, + '2001:db8:100::/40' : { + 'next_hop' : { + '2001:db8::1' : { 'distance' : '10' }, + '2001:db8::2' : { 'distance' : '20', 'interface' : 'eth0' }, + '2001:db8::3' : { 'distance' : '30', 'disable' : '' }, + }, + 'interface' : { + 'eth0' : { 'distance' : '40', 'vrf' : 'black' }, + 'eth1' : { 'distance' : '50', 'disable' : '' }, + }, + 'blackhole' : { 'distance' : '250', 'tag' : '500' }, + }, + '2001:db8:200::/40' : { + 'interface' : { + 'eth0' : { 'distance' : '40' }, + 'eth1' : { 'distance' : '50', 'disable' : '' }, + }, + 'blackhole' : { 'distance' : '250', 'tag' : '500' }, + }, + '2001:db8::/32' : { + 'blackhole' : { 'distance' : '200', 'tag' : '600' }, + }, +} + +tables = ['80', '81', '82'] + +class StaticRouteTest(VyOSUnitTestSHIM.TestCase): + def setUp(self): + # This is our "target" VRF when leaking routes: + self.cli_set(['vrf', 'name', 'black', 'table', '43210']) + + def tearDown(self): + for route, route_config in routes.items(): + route_type = 'route' + if is_ipv6(route): + route_type = 'route6' + self.cli_delete(base_path + [route_type, route]) + + for table in tables: + self.cli_delete(base_path + ['table', table]) + + tmp = self.getFRRconfig('', end='') + self.cli_commit() + + def test_protocols_static(self): + for route, route_config in routes.items(): + route_type = 'route' + if is_ipv6(route): + route_type = 'route6' + base = base_path + [route_type, route] + if 'next_hop' in route_config: + for next_hop, next_hop_config in route_config['next_hop'].items(): + self.cli_set(base + ['next-hop', next_hop]) + if 'disable' in next_hop_config: + self.cli_set(base + ['next-hop', next_hop, 'disable']) + if 'distance' in next_hop_config: + self.cli_set(base + ['next-hop', next_hop, 'distance', next_hop_config['distance']]) + if 'interface' in next_hop_config: + self.cli_set(base + ['next-hop', next_hop, 'interface', next_hop_config['interface']]) + if 'vrf' in next_hop_config: + self.cli_set(base + ['next-hop', next_hop, 'vrf', next_hop_config['vrf']]) + + + if 'interface' in route_config: + for interface, interface_config in route_config['interface'].items(): + self.cli_set(base + ['interface', interface]) + if 'disable' in interface_config: + self.cli_set(base + ['interface', interface, 'disable']) + if 'distance' in interface_config: + self.cli_set(base + ['interface', interface, 'distance', interface_config['distance']]) + if 'vrf' in interface_config: + self.cli_set(base + ['interface', interface, 'vrf', interface_config['vrf']]) + + if 'blackhole' in route_config: + self.cli_set(base + ['blackhole']) + if 'distance' in route_config['blackhole']: + self.cli_set(base + ['blackhole', 'distance', route_config['blackhole']['distance']]) + if 'tag' in route_config['blackhole']: + self.cli_set(base + ['blackhole', 'tag', route_config['blackhole']['tag']]) + + # commit changes + self.cli_commit() + + # Verify FRR bgpd configuration + frrconfig = self.getFRRconfig('ip route', end='') + + # Verify routes + for route, route_config in routes.items(): + ip_ipv6 = 'ip' + if is_ipv6(route): + ip_ipv6 = 'ipv6' + + if 'next_hop' in route_config: + for next_hop, next_hop_config in route_config['next_hop'].items(): + tmp = f'{ip_ipv6} route {route} {next_hop}' + if 'interface' in next_hop_config: + tmp += ' ' + next_hop_config['interface'] + if 'distance' in next_hop_config: + tmp += ' ' + next_hop_config['distance'] + if 'vrf' in next_hop_config: + tmp += ' nexthop-vrf ' + next_hop_config['vrf'] + + if 'disable' in next_hop_config: + self.assertNotIn(tmp, frrconfig) + else: + self.assertIn(tmp, frrconfig) + + if 'interface' in route_config: + for interface, interface_config in route_config['interface'].items(): + tmp = f'{ip_ipv6} route {route} {interface}' + if 'interface' in interface_config: + tmp += ' ' + interface_config['interface'] + if 'distance' in interface_config: + tmp += ' ' + interface_config['distance'] + if 'vrf' in interface_config: + tmp += ' nexthop-vrf ' + interface_config['vrf'] + + if 'disable' in interface_config: + self.assertNotIn(tmp, frrconfig) + else: + self.assertIn(tmp, frrconfig) + + if 'blackhole' in route_config: + tmp = f'{ip_ipv6} route {route} blackhole' + if 'tag' in route_config['blackhole']: + tmp += ' tag ' + route_config['blackhole']['tag'] + if 'distance' in route_config['blackhole']: + tmp += ' ' + route_config['blackhole']['distance'] + + self.assertIn(tmp, frrconfig) + + def test_protocols_static_table(self): + for table in tables: + for route, route_config in routes.items(): + route_type = 'route' + if is_ipv6(route): + route_type = 'route6' + base = base_path + ['table', table, route_type, route] + + if 'next_hop' in route_config: + for next_hop, next_hop_config in route_config['next_hop'].items(): + self.cli_set(base + ['next-hop', next_hop]) + if 'disable' in next_hop_config: + self.cli_set(base + ['next-hop', next_hop, 'disable']) + if 'distance' in next_hop_config: + self.cli_set(base + ['next-hop', next_hop, 'distance', next_hop_config['distance']]) + if 'interface' in next_hop_config: + self.cli_set(base + ['next-hop', next_hop, 'interface', next_hop_config['interface']]) + if 'vrf' in next_hop_config: + self.cli_set(base + ['next-hop', next_hop, 'vrf', next_hop_config['vrf']]) + + + if 'interface' in route_config: + for interface, interface_config in route_config['interface'].items(): + self.cli_set(base + ['interface', interface]) + if 'disable' in interface_config: + self.cli_set(base + ['interface', interface, 'disable']) + if 'distance' in interface_config: + self.cli_set(base + ['interface', interface, 'distance', interface_config['distance']]) + if 'vrf' in interface_config: + self.cli_set(base + ['interface', interface, 'vrf', interface_config['vrf']]) + + if 'blackhole' in route_config: + self.cli_set(base + ['blackhole']) + if 'distance' in route_config['blackhole']: + self.cli_set(base + ['blackhole', 'distance', route_config['blackhole']['distance']]) + if 'tag' in route_config['blackhole']: + self.cli_set(base + ['blackhole', 'tag', route_config['blackhole']['tag']]) + + # commit changes + self.cli_commit() + + # Verify FRR bgpd configuration + frrconfig = self.getFRRconfig('ip route', end='') + + for table in tables: + # Verify routes + for route, route_config in routes.items(): + ip_ipv6 = 'ip' + if is_ipv6(route): + ip_ipv6 = 'ipv6' + + if 'next_hop' in route_config: + for next_hop, next_hop_config in route_config['next_hop'].items(): + tmp = f'{ip_ipv6} route {route} {next_hop}' + if 'interface' in next_hop_config: + tmp += ' ' + next_hop_config['interface'] + if 'distance' in next_hop_config: + tmp += ' ' + next_hop_config['distance'] + if 'vrf' in next_hop_config: + tmp += ' nexthop-vrf ' + next_hop_config['vrf'] + + tmp += ' table ' + table + if 'disable' in next_hop_config: + self.assertNotIn(tmp, frrconfig) + else: + self.assertIn(tmp, frrconfig) + + if 'interface' in route_config: + for interface, interface_config in route_config['interface'].items(): + tmp = f'{ip_ipv6} route {route} {interface}' + if 'interface' in interface_config: + tmp += ' ' + interface_config['interface'] + if 'distance' in interface_config: + tmp += ' ' + interface_config['distance'] + if 'vrf' in interface_config: + tmp += ' nexthop-vrf ' + interface_config['vrf'] + + tmp += ' table ' + table + if 'disable' in interface_config: + self.assertNotIn(tmp, frrconfig) + else: + self.assertIn(tmp, frrconfig) + + if 'blackhole' in route_config: + tmp = f'{ip_ipv6} route {route} blackhole' + if 'tag' in route_config['blackhole']: + tmp += ' tag ' + route_config['blackhole']['tag'] + if 'distance' in route_config['blackhole']: + tmp += ' ' + route_config['blackhole']['distance'] + + tmp += ' table ' + table + self.assertIn(tmp, frrconfig) + + + def test_protocols_vrf_static(self): + # Create VRF instances and apply the static routes from above to FRR. + # Re-read the configured routes and match them if they are programmed + # properly. This also includes VRF leaking + vrfs = { + 'red' : { 'table' : '1000' }, + 'green' : { 'table' : '2000' }, + 'blue' : { 'table' : '3000' }, + } + + for vrf, vrf_config in vrfs.items(): + vrf_base_path = ['vrf', 'name', vrf] + self.cli_set(vrf_base_path + ['table', vrf_config['table']]) + + for route, route_config in routes.items(): + route_type = 'route' + if is_ipv6(route): + route_type = 'route6' + route_base_path = vrf_base_path + ['protocols', 'static', route_type, route] + + if 'next_hop' in route_config: + for next_hop, next_hop_config in route_config['next_hop'].items(): + self.cli_set(route_base_path + ['next-hop', next_hop]) + if 'disable' in next_hop_config: + self.cli_set(route_base_path + ['next-hop', next_hop, 'disable']) + if 'distance' in next_hop_config: + self.cli_set(route_base_path + ['next-hop', next_hop, 'distance', next_hop_config['distance']]) + if 'interface' in next_hop_config: + self.cli_set(route_base_path + ['next-hop', next_hop, 'interface', next_hop_config['interface']]) + if 'vrf' in next_hop_config: + self.cli_set(route_base_path + ['next-hop', next_hop, 'vrf', next_hop_config['vrf']]) + + + if 'interface' in route_config: + for interface, interface_config in route_config['interface'].items(): + self.cli_set(route_base_path + ['interface', interface]) + if 'disable' in interface_config: + self.cli_set(route_base_path + ['interface', interface, 'disable']) + if 'distance' in interface_config: + self.cli_set(route_base_path + ['interface', interface, 'distance', interface_config['distance']]) + if 'vrf' in interface_config: + self.cli_set(route_base_path + ['interface', interface, 'vrf', interface_config['vrf']]) + + if 'blackhole' in route_config: + self.cli_set(route_base_path + ['blackhole']) + if 'distance' in route_config['blackhole']: + self.cli_set(route_base_path + ['blackhole', 'distance', route_config['blackhole']['distance']]) + if 'tag' in route_config['blackhole']: + self.cli_set(route_base_path + ['blackhole', 'tag', route_config['blackhole']['tag']]) + + # commit changes + self.cli_commit() + + for vrf, vrf_config in vrfs.items(): + tmp = get_interface_config(vrf) + + # Compare VRF table ID + self.assertEqual(tmp['linkinfo']['info_data']['table'], int(vrf_config['table'])) + self.assertEqual(tmp['linkinfo']['info_kind'], 'vrf') + + # Verify FRR bgpd configuration + frrconfig = self.getFRRconfig(f'vrf {vrf}') + self.assertIn(f'vrf {vrf}', frrconfig) + + # Verify routes + for route, route_config in routes.items(): + ip_ipv6 = 'ip' + if is_ipv6(route): + ip_ipv6 = 'ipv6' + + if 'next_hop' in route_config: + for next_hop, next_hop_config in route_config['next_hop'].items(): + tmp = f'{ip_ipv6} route {route} {next_hop}' + if 'interface' in next_hop_config: + tmp += ' ' + next_hop_config['interface'] + if 'distance' in next_hop_config: + tmp += ' ' + next_hop_config['distance'] + if 'vrf' in next_hop_config: + tmp += ' nexthop-vrf ' + next_hop_config['vrf'] + + if 'disable' in next_hop_config: + self.assertNotIn(tmp, frrconfig) + else: + self.assertIn(tmp, frrconfig) + + if 'interface' in route_config: + for interface, interface_config in route_config['interface'].items(): + tmp = f'{ip_ipv6} route {route} {interface}' + if 'interface' in interface_config: + tmp += ' ' + interface_config['interface'] + if 'distance' in interface_config: + tmp += ' ' + interface_config['distance'] + if 'vrf' in interface_config: + tmp += ' nexthop-vrf ' + interface_config['vrf'] + + if 'disable' in interface_config: + self.assertNotIn(tmp, frrconfig) + else: + self.assertIn(tmp, frrconfig) + + if 'blackhole' in route_config: + tmp = f'{ip_ipv6} route {route} blackhole' + if 'tag' in route_config['blackhole']: + tmp += ' tag ' + route_config['blackhole']['tag'] + if 'distance' in route_config['blackhole']: + tmp += ' ' + route_config['blackhole']['distance'] + + self.assertIn(tmp, frrconfig) + + self.cli_delete(['vrf']) + +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 index c28509714..58b730ab4 100755 --- a/smoketest/scripts/cli/test_service_bcast-relay.py +++ b/smoketest/scripts/cli/test_service_bcast-relay.py @@ -14,47 +14,46 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import os import unittest +from base_vyostest_shim import VyOSUnitTestSHIM + from psutil import process_iter -from vyos.configsession import ConfigSession, ConfigSessionError +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError base_path = ['service', 'broadcast-relay'] -class TestServiceBroadcastRelay(unittest.TestCase): +class TestServiceBroadcastRelay(VyOSUnitTestSHIM.TestCase): _address1 = '192.0.2.1/24' _address2 = '192.0.2.1/24' def setUp(self): - self.session = ConfigSession(os.getpid()) - self.session.set(['interfaces', 'dummy', 'dum1001', 'address', self._address1]) - self.session.set(['interfaces', 'dummy', 'dum1002', 'address', self._address2]) - self.session.commit() + self.cli_set(['interfaces', 'dummy', 'dum1001', 'address', self._address1]) + self.cli_set(['interfaces', 'dummy', 'dum1002', 'address', self._address2]) def tearDown(self): - self.session.delete(['interfaces', 'dummy', 'dum1001']) - self.session.delete(['interfaces', 'dummy', 'dum1002']) - self.session.delete(base_path) - self.session.commit() - del self.session + 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.session.set(base + ['description', 'vyos']) - self.session.set(base + ['port', str(10000 + 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.session.commit() + self.cli_commit() - self.session.set(base + ['interface', 'dum1001']) - self.session.set(base + ['interface', 'dum1002']) - self.session.set(base + ['address', self._address1.split('/')[0]]) + self.cli_set(base + ['interface', 'dum1001']) + self.cli_set(base + ['interface', 'dum1002']) + self.cli_set(base + ['address', self._address1.split('/')[0]]) - self.session.commit() + self.cli_commit() for id in ids: # check if process is running diff --git a/smoketest/scripts/cli/test_service_dhcp-relay.py b/smoketest/scripts/cli/test_service_dhcp-relay.py index 676c4a481..db2edba54 100755 --- a/smoketest/scripts/cli/test_service_dhcp-relay.py +++ b/smoketest/scripts/cli/test_service_dhcp-relay.py @@ -14,14 +14,13 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import re -import os import unittest +from base_vyostest_shim import VyOSUnitTestSHIM + from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section -from vyos.util import cmd from vyos.util import process_named_running from vyos.util import read_file @@ -29,14 +28,10 @@ PROCESS_NAME = 'dhcrelay' RELAY_CONF = '/run/dhcp-relay/dhcrelay.conf' base_path = ['service', 'dhcp-relay'] -class TestServiceDHCPRelay(unittest.TestCase): - def setUp(self): - self.session = ConfigSession(os.getpid()) - +class TestServiceDHCPRelay(VyOSUnitTestSHIM.TestCase): def tearDown(self): - self.session.delete(base_path) - self.session.commit() - del self.session + self.cli_delete(base_path) + self.cli_commit() def test_relay_default(self): max_size = '800' @@ -44,28 +39,28 @@ class TestServiceDHCPRelay(unittest.TestCase): agents_packets = 'append' servers = ['192.0.2.1', '192.0.2.2'] - self.session.set(base_path + ['interface', 'lo']) + self.cli_set(base_path + ['interface', 'lo']) # check validate() - DHCP relay does not support the loopback interface with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.delete(base_path + ['interface', 'lo']) + self.cli_commit() + self.cli_delete(base_path + ['interface', 'lo']) # activate DHCP relay on all ethernet interfaces for tmp in Section.interfaces("ethernet"): - self.session.set(base_path + ['interface', tmp]) + self.cli_set(base_path + ['interface', tmp]) # check validate() - No DHCP relay server(s) configured with self.assertRaises(ConfigSessionError): - self.session.commit() + self.cli_commit() for server in servers: - self.session.set(base_path + ['server', server]) + self.cli_set(base_path + ['server', server]) - self.session.set(base_path + ['relay-options', 'max-size', max_size]) - self.session.set(base_path + ['relay-options', 'hop-count', hop_count]) - self.session.set(base_path + ['relay-options', 'relay-agents-packets', agents_packets]) + self.cli_set(base_path + ['relay-options', 'max-size', max_size]) + self.cli_set(base_path + ['relay-options', 'hop-count', hop_count]) + self.cli_set(base_path + ['relay-options', 'relay-agents-packets', agents_packets]) # commit changes - self.session.commit() + self.cli_commit() # Check configured port config = read_file(RELAY_CONF) diff --git a/smoketest/scripts/cli/test_service_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py index db7b2dda4..d3f6f21f1 100755 --- a/smoketest/scripts/cli/test_service_dhcp-server.py +++ b/smoketest/scripts/cli/test_service_dhcp-server.py @@ -14,13 +14,12 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import re -import os import unittest +from base_vyostest_shim import VyOSUnitTestSHIM + from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError -from vyos.util import cmd from vyos.util import process_named_running from vyos.util import read_file from vyos.template import address_from_cidr @@ -37,17 +36,15 @@ dns_1 = inc_ip(subnet, 2) dns_2 = inc_ip(subnet, 3) domain_name = 'vyos.net' -class TestServiceDHCPServer(unittest.TestCase): +class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): def setUp(self): - self.session = ConfigSession(os.getpid()) cidr_mask = subnet.split('/')[-1] - self.session.set(['interfaces', 'dummy', 'dum8765', 'address', f'{router}/{cidr_mask}']) + self.cli_set(['interfaces', 'dummy', 'dum8765', 'address', f'{router}/{cidr_mask}']) def tearDown(self): - self.session.delete(['interfaces', 'dummy', 'dum8765']) - self.session.delete(base_path) - self.session.commit() - del self.session + self.cli_delete(['interfaces', 'dummy', 'dum8765']) + self.cli_delete(base_path) + self.cli_commit() def test_dhcp_single_pool_range(self): shared_net_name = 'SMOKE-1' @@ -57,25 +54,25 @@ class TestServiceDHCPServer(unittest.TestCase): range_1_start = inc_ip(subnet, 40) range_1_stop = inc_ip(subnet, 50) - self.session.set(base_path + ['dynamic-dns-update']) + self.cli_set(base_path + ['dynamic-dns-update']) pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] # we use the first subnet IP address as default gateway - self.session.set(pool + ['default-router', router]) - self.session.set(pool + ['dns-server', dns_1]) - self.session.set(pool + ['dns-server', dns_2]) - self.session.set(pool + ['domain-name', domain_name]) + self.cli_set(pool + ['default-router', router]) + self.cli_set(pool + ['dns-server', dns_1]) + self.cli_set(pool + ['dns-server', dns_2]) + self.cli_set(pool + ['domain-name', domain_name]) # check validate() - No DHCP address range or active static-mapping set with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(pool + ['range', '0', 'start', range_0_start]) - self.session.set(pool + ['range', '0', 'stop', range_0_stop]) - self.session.set(pool + ['range', '1', 'start', range_1_start]) - self.session.set(pool + ['range', '1', 'stop', range_1_stop]) + self.cli_commit() + self.cli_set(pool + ['range', '0', 'start', range_0_start]) + self.cli_set(pool + ['range', '0', 'stop', range_0_stop]) + self.cli_set(pool + ['range', '1', 'start', range_1_start]) + self.cli_set(pool + ['range', '1', 'stop', range_1_stop]) # commit changes - self.session.commit() + self.cli_commit() config = read_file(DHCPD_CONF) network = address_from_cidr(subnet) @@ -110,42 +107,42 @@ class TestServiceDHCPServer(unittest.TestCase): pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] # we use the first subnet IP address as default gateway - self.session.set(pool + ['default-router', router]) - self.session.set(pool + ['dns-server', dns_1]) - self.session.set(pool + ['dns-server', dns_2]) - self.session.set(pool + ['domain-name', domain_name]) - self.session.set(pool + ['ip-forwarding']) - self.session.set(pool + ['smtp-server', smtp_server]) - self.session.set(pool + ['pop-server', smtp_server]) - self.session.set(pool + ['time-server', time_server]) - self.session.set(pool + ['tftp-server-name', tftp_server]) + self.cli_set(pool + ['default-router', router]) + self.cli_set(pool + ['dns-server', dns_1]) + self.cli_set(pool + ['dns-server', dns_2]) + self.cli_set(pool + ['domain-name', domain_name]) + self.cli_set(pool + ['ip-forwarding']) + self.cli_set(pool + ['smtp-server', smtp_server]) + self.cli_set(pool + ['pop-server', smtp_server]) + self.cli_set(pool + ['time-server', time_server]) + self.cli_set(pool + ['tftp-server-name', tftp_server]) for search in search_domains: - self.session.set(pool + ['domain-search', search]) - self.session.set(pool + ['bootfile-name', bootfile_name]) - self.session.set(pool + ['bootfile-server', bootfile_server]) - self.session.set(pool + ['wpad-url', wpad]) - self.session.set(pool + ['server-identifier', server_identifier]) + self.cli_set(pool + ['domain-search', search]) + self.cli_set(pool + ['bootfile-name', bootfile_name]) + self.cli_set(pool + ['bootfile-server', bootfile_server]) + self.cli_set(pool + ['wpad-url', wpad]) + self.cli_set(pool + ['server-identifier', server_identifier]) - self.session.set(pool + ['static-route', 'destination-subnet', '10.0.0.0/24']) - self.session.set(pool + ['static-route', 'router', '192.0.2.1']) + self.cli_set(pool + ['static-route', 'destination-subnet', '10.0.0.0/24']) + self.cli_set(pool + ['static-route', 'router', '192.0.2.1']) # check validate() - No DHCP address range or active static-mapping set with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(pool + ['range', '0', 'start', range_0_start]) - self.session.set(pool + ['range', '0', 'stop', range_0_stop]) + self.cli_commit() + self.cli_set(pool + ['range', '0', 'start', range_0_start]) + self.cli_set(pool + ['range', '0', 'stop', range_0_stop]) # failover failover_local = router failover_remote = inc_ip(router, 1) - self.session.set(pool + ['failover', 'local-address', failover_local]) - self.session.set(pool + ['failover', 'name', shared_net_name]) - self.session.set(pool + ['failover', 'peer-address', failover_remote]) - self.session.set(pool + ['failover', 'status', 'primary']) + self.cli_set(pool + ['failover', 'local-address', failover_local]) + self.cli_set(pool + ['failover', 'name', shared_net_name]) + self.cli_set(pool + ['failover', 'peer-address', failover_remote]) + self.cli_set(pool + ['failover', 'status', 'primary']) # commit changes - self.session.commit() + self.cli_commit() config = read_file(DHCPD_CONF) @@ -200,27 +197,28 @@ class TestServiceDHCPServer(unittest.TestCase): def test_dhcp_single_pool_static_mapping(self): shared_net_name = 'SMOKE-2' + domain_name = 'private' pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] # we use the first subnet IP address as default gateway - self.session.set(pool + ['default-router', router]) - self.session.set(pool + ['dns-server', dns_1]) - self.session.set(pool + ['dns-server', dns_2]) - self.session.set(pool + ['domain-name', domain_name]) + self.cli_set(pool + ['default-router', router]) + self.cli_set(pool + ['dns-server', dns_1]) + self.cli_set(pool + ['dns-server', dns_2]) + self.cli_set(pool + ['domain-name', domain_name]) # check validate() - No DHCP address range or active static-mapping set with self.assertRaises(ConfigSessionError): - self.session.commit() + self.cli_commit() client_base = 10 for client in ['client1', 'client2', 'client3']: mac = '00:50:00:00:00:{}'.format(client_base) - self.session.set(pool + ['static-mapping', client, 'mac-address', mac]) - self.session.set(pool + ['static-mapping', client, 'ip-address', inc_ip(subnet, client_base)]) + self.cli_set(pool + ['static-mapping', client, 'mac-address', mac]) + self.cli_set(pool + ['static-mapping', client, 'ip-address', inc_ip(subnet, client_base)]) client_base += 1 # commit changes - self.session.commit() + self.cli_commit() config = read_file(DHCPD_CONF) network = address_from_cidr(subnet) @@ -263,25 +261,25 @@ class TestServiceDHCPServer(unittest.TestCase): pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] # we use the first subnet IP address as default gateway - self.session.set(pool + ['default-router', router]) - self.session.set(pool + ['dns-server', dns_1]) - self.session.set(pool + ['domain-name', domain_name]) - self.session.set(pool + ['lease', lease_time]) + self.cli_set(pool + ['default-router', router]) + self.cli_set(pool + ['dns-server', dns_1]) + self.cli_set(pool + ['domain-name', domain_name]) + self.cli_set(pool + ['lease', lease_time]) - self.session.set(pool + ['range', '0', 'start', range_0_start]) - self.session.set(pool + ['range', '0', 'stop', range_0_stop]) - self.session.set(pool + ['range', '1', 'start', range_1_start]) - self.session.set(pool + ['range', '1', 'stop', range_1_stop]) + self.cli_set(pool + ['range', '0', 'start', range_0_start]) + self.cli_set(pool + ['range', '0', 'stop', range_0_stop]) + self.cli_set(pool + ['range', '1', 'start', range_1_start]) + self.cli_set(pool + ['range', '1', 'stop', range_1_stop]) client_base = 60 for client in ['client1', 'client2', 'client3', 'client4']: mac = '02:50:00:00:00:{}'.format(client_base) - self.session.set(pool + ['static-mapping', client, 'mac-address', mac]) - self.session.set(pool + ['static-mapping', client, 'ip-address', inc_ip(subnet, client_base)]) + self.cli_set(pool + ['static-mapping', client, 'mac-address', mac]) + self.cli_set(pool + ['static-mapping', client, 'ip-address', inc_ip(subnet, client_base)]) client_base += 1 # commit changes - self.session.commit() + self.cli_commit() config = read_file(DHCPD_CONF) for network in ['0', '1', '2', '3']: @@ -328,13 +326,13 @@ class TestServiceDHCPServer(unittest.TestCase): range_0_stop = inc_ip(subnet, 20) pool = base_path + ['shared-network-name', 'EXCLUDE-TEST', 'subnet', subnet] - self.session.set(pool + ['default-router', router]) - self.session.set(pool + ['exclude', router]) - self.session.set(pool + ['range', '0', 'start', range_0_start]) - self.session.set(pool + ['range', '0', 'stop', range_0_stop]) + self.cli_set(pool + ['default-router', router]) + self.cli_set(pool + ['exclude', router]) + self.cli_set(pool + ['range', '0', 'start', range_0_start]) + self.cli_set(pool + ['range', '0', 'stop', range_0_stop]) # commit changes - self.session.commit() + self.cli_commit() # VErify config = read_file(DHCPD_CONF) @@ -361,13 +359,13 @@ class TestServiceDHCPServer(unittest.TestCase): range_0_start_excl = inc_ip(exclude_addr, 1) pool = base_path + ['shared-network-name', 'EXCLUDE-TEST-2', 'subnet', subnet] - self.session.set(pool + ['default-router', router]) - self.session.set(pool + ['exclude', exclude_addr]) - self.session.set(pool + ['range', '0', 'start', range_0_start]) - self.session.set(pool + ['range', '0', 'stop', range_0_stop]) + self.cli_set(pool + ['default-router', router]) + self.cli_set(pool + ['exclude', exclude_addr]) + self.cli_set(pool + ['range', '0', 'start', range_0_start]) + self.cli_set(pool + ['range', '0', 'stop', range_0_stop]) # commit changes - self.session.commit() + self.cli_commit() # Verify config = read_file(DHCPD_CONF) @@ -385,7 +383,7 @@ class TestServiceDHCPServer(unittest.TestCase): def test_dhcp_relay_server(self): # Listen on specific address and return DHCP leases from a non # directly connected pool - self.session.set(base_path + ['listen-address', router]) + self.cli_set(base_path + ['listen-address', router]) relay_subnet = '10.0.0.0/16' relay_router = inc_ip(relay_subnet, 1) @@ -394,12 +392,12 @@ class TestServiceDHCPServer(unittest.TestCase): range_0_stop = '10.0.250.255' pool = base_path + ['shared-network-name', 'RELAY', 'subnet', relay_subnet] - self.session.set(pool + ['default-router', relay_router]) - self.session.set(pool + ['range', '0', 'start', range_0_start]) - self.session.set(pool + ['range', '0', 'stop', range_0_stop]) + self.cli_set(pool + ['default-router', relay_router]) + self.cli_set(pool + ['range', '0', 'start', range_0_start]) + self.cli_set(pool + ['range', '0', 'stop', range_0_stop]) # commit changes - self.session.commit() + self.cli_commit() config = read_file(DHCPD_CONF) network = address_from_cidr(subnet) diff --git a/smoketest/scripts/cli/test_service_dhcpv6-relay.py b/smoketest/scripts/cli/test_service_dhcpv6-relay.py index e36c237bc..5a9dd1aa6 100755 --- a/smoketest/scripts/cli/test_service_dhcpv6-relay.py +++ b/smoketest/scripts/cli/test_service_dhcpv6-relay.py @@ -14,15 +14,14 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import re -import os import unittest +from base_vyostest_shim import VyOSUnitTestSHIM + from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section from vyos.template import address_from_cidr -from vyos.util import cmd from vyos.util import process_named_running from vyos.util import read_file @@ -35,52 +34,50 @@ upstream_if_addr = '2001:db8::1/64' listen_addr = '2001:db8:ffff::1/64' interfaces = [] -class TestServiceDHCPv6Relay(unittest.TestCase): +class TestServiceDHCPv6Relay(VyOSUnitTestSHIM.TestCase): def setUp(self): - self.session = ConfigSession(os.getpid()) for tmp in interfaces: listen = listen_addr if tmp == upstream_if: listen = upstream_if_addr - self.session.set(['interfaces', 'ethernet', tmp, 'address', listen]) + self.cli_set(['interfaces', 'ethernet', tmp, 'address', listen]) def tearDown(self): - self.session.delete(base_path) + self.cli_delete(base_path) for tmp in interfaces: listen = listen_addr if tmp == upstream_if: listen = upstream_if_addr - self.session.delete(['interfaces', 'ethernet', tmp, 'address', listen]) + self.cli_delete(['interfaces', 'ethernet', tmp, 'address', listen]) - self.session.commit() - del self.session + self.cli_commit() def test_relay_default(self): dhcpv6_server = '2001:db8::ffff' hop_count = '20' - self.session.set(base_path + ['use-interface-id-option']) - self.session.set(base_path + ['max-hop-count', hop_count]) + self.cli_set(base_path + ['use-interface-id-option']) + self.cli_set(base_path + ['max-hop-count', hop_count]) # check validate() - Must set at least one listen and upstream # interface addresses. with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(base_path + ['upstream-interface', upstream_if, 'address', dhcpv6_server]) + self.cli_commit() + self.cli_set(base_path + ['upstream-interface', upstream_if, 'address', dhcpv6_server]) # check validate() - Must set at least one listen and upstream # interface addresses. with self.assertRaises(ConfigSessionError): - self.session.commit() + self.cli_commit() # add listener on all ethernet interfaces except the upstream interface for tmp in interfaces: if tmp == upstream_if: continue - self.session.set(base_path + ['listen-interface', tmp, 'address', listen_addr.split('/')[0]]) + self.cli_set(base_path + ['listen-interface', tmp, 'address', listen_addr.split('/')[0]]) # commit changes - self.session.commit() + self.cli_commit() # Check configured port config = read_file(RELAY_CONF) diff --git a/smoketest/scripts/cli/test_service_dhcpv6-server.py b/smoketest/scripts/cli/test_service_dhcpv6-server.py index 319891a94..e85a055c7 100755 --- a/smoketest/scripts/cli/test_service_dhcpv6-server.py +++ b/smoketest/scripts/cli/test_service_dhcpv6-server.py @@ -14,14 +14,13 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import re -import os import unittest +from base_vyostest_shim import VyOSUnitTestSHIM + from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError from vyos.template import inc_ip -from vyos.util import cmd from vyos.util import process_named_running from vyos.util import read_file @@ -37,16 +36,14 @@ nis_servers = ['2001:db8:ffff::1', '2001:db8:ffff::2'] interface = 'eth1' interface_addr = inc_ip(subnet, 1) + '/64' -class TestServiceDHCPServer(unittest.TestCase): +class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): def setUp(self): - self.session = ConfigSession(os.getpid()) - self.session.set(['interfaces', 'ethernet', interface, 'address', interface_addr]) + self.cli_set(['interfaces', 'ethernet', interface, 'address', interface_addr]) def tearDown(self): - self.session.delete(base_path) - self.session.delete(['interfaces', 'ethernet', interface, 'address', interface_addr]) - self.session.commit() - del self.session + self.cli_delete(base_path) + self.cli_delete(['interfaces', 'ethernet', interface, 'address', interface_addr]) + self.cli_commit() def test_single_pool(self): shared_net_name = 'SMOKE-1' @@ -62,37 +59,37 @@ class TestServiceDHCPServer(unittest.TestCase): pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] - self.session.set(base_path + ['preference', preference]) + self.cli_set(base_path + ['preference', preference]) # we use the first subnet IP address as default gateway - self.session.set(pool + ['name-server', dns_1]) - self.session.set(pool + ['name-server', dns_2]) - self.session.set(pool + ['name-server', dns_2]) - self.session.set(pool + ['lease-time', 'default', lease_time]) - self.session.set(pool + ['lease-time', 'maximum', max_lease_time]) - self.session.set(pool + ['lease-time', 'minimum', min_lease_time]) - self.session.set(pool + ['nis-domain', domain]) - self.session.set(pool + ['nisplus-domain', domain]) - self.session.set(pool + ['sip-server', sip_server]) - self.session.set(pool + ['sntp-server', sntp_server]) - self.session.set(pool + ['address-range', 'start', range_start, 'stop', range_stop]) + self.cli_set(pool + ['name-server', dns_1]) + self.cli_set(pool + ['name-server', dns_2]) + self.cli_set(pool + ['name-server', dns_2]) + self.cli_set(pool + ['lease-time', 'default', lease_time]) + self.cli_set(pool + ['lease-time', 'maximum', max_lease_time]) + self.cli_set(pool + ['lease-time', 'minimum', min_lease_time]) + self.cli_set(pool + ['nis-domain', domain]) + self.cli_set(pool + ['nisplus-domain', domain]) + self.cli_set(pool + ['sip-server', sip_server]) + self.cli_set(pool + ['sntp-server', sntp_server]) + self.cli_set(pool + ['address-range', 'start', range_start, 'stop', range_stop]) for server in nis_servers: - self.session.set(pool + ['nis-server', server]) - self.session.set(pool + ['nisplus-server', server]) + self.cli_set(pool + ['nis-server', server]) + self.cli_set(pool + ['nisplus-server', server]) for search in search_domains: - self.session.set(pool + ['domain-search', search]) + self.cli_set(pool + ['domain-search', search]) client_base = 1 for client in ['client1', 'client2', 'client3']: cid = '00:01:00:01:12:34:56:78:aa:bb:cc:dd:ee:{}'.format(client_base) - self.session.set(pool + ['static-mapping', client, 'identifier', cid]) - self.session.set(pool + ['static-mapping', client, 'ipv6-address', inc_ip(subnet, client_base)]) + self.cli_set(pool + ['static-mapping', client, 'identifier', cid]) + self.cli_set(pool + ['static-mapping', client, 'ipv6-address', inc_ip(subnet, client_base)]) client_base += 1 # commit changes - self.session.commit() + self.cli_commit() config = read_file(DHCPD_CONF) self.assertIn(f'option dhcp6.preference {preference};', config) @@ -136,12 +133,12 @@ class TestServiceDHCPServer(unittest.TestCase): pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] - self.session.set(pool + ['address-range', 'start', range_start, 'stop', range_stop]) - self.session.set(pool + ['prefix-delegation', 'start', delegate_start, 'stop', delegate_stop]) - self.session.set(pool + ['prefix-delegation', 'start', delegate_start, 'prefix-length', delegate_len]) + self.cli_set(pool + ['address-range', 'start', range_start, 'stop', range_stop]) + self.cli_set(pool + ['prefix-delegation', 'start', delegate_start, 'stop', delegate_stop]) + self.cli_set(pool + ['prefix-delegation', 'start', delegate_start, 'prefix-length', delegate_len]) # commit changes - self.session.commit() + self.cli_commit() config = read_file(DHCPD_CONF) self.assertIn(f'subnet6 {subnet}' + r' {', config) @@ -151,5 +148,26 @@ class TestServiceDHCPServer(unittest.TestCase): # Check for running process self.assertTrue(process_named_running(PROCESS_NAME)) + def test_global_nameserver(self): + shared_net_name = 'SMOKE-3' + ns_global_1 = '2001:db8::1111' + ns_global_2 = '2001:db8::2222' + + self.cli_set(base_path + ['global-parameters', 'name-server', ns_global_1]) + self.cli_set(base_path + ['global-parameters', 'name-server', ns_global_2]) + self.cli_set(base_path + ['shared-network-name', shared_net_name, 'subnet', subnet]) + + # commit changes + self.cli_commit() + + config = read_file(DHCPD_CONF) + self.assertIn(f'option dhcp6.name-servers {ns_global_1};', config) + self.assertIn(f'option dhcp6.name-servers {ns_global_2};', config) + self.assertIn(f'subnet6 {subnet}' + r' {', config) + self.assertIn(f'set shared-networkname = "{shared_net_name}";', config) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_dns_dynamic.py b/smoketest/scripts/cli/test_service_dns_dynamic.py index 83eede64a..d8a87ffd4 100755 --- a/smoketest/scripts/cli/test_service_dns_dynamic.py +++ b/smoketest/scripts/cli/test_service_dns_dynamic.py @@ -18,10 +18,11 @@ import re import os import unittest -from getpass import getuser +from base_vyostest_shim import VyOSUnitTestSHIM + from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError -from vyos.util import read_file +from vyos.util import cmd from vyos.util import process_named_running PROCESS_NAME = 'ddclient' @@ -29,21 +30,17 @@ DDCLIENT_CONF = '/run/ddclient/ddclient.conf' base_path = ['service', 'dns', 'dynamic'] def get_config_value(key): - tmp = read_file(DDCLIENT_CONF) + tmp = cmd(f'sudo cat {DDCLIENT_CONF}') tmp = re.findall(r'\n?{}=+(.*)'.format(key), tmp) tmp = tmp[0].rstrip(',') return tmp -class TestServiceDDNS(unittest.TestCase): - def setUp(self): - self.session = ConfigSession(os.getpid()) +class TestServiceDDNS(VyOSUnitTestSHIM.TestCase): def tearDown(self): # Delete DDNS configuration - self.session.delete(base_path) - self.session.commit() - - del self.session + self.cli_delete(base_path) + self.cli_commit() def test_dyndns_service(self): ddns = ['interface', 'eth0', 'service'] @@ -53,45 +50,44 @@ class TestServiceDDNS(unittest.TestCase): user = 'vyos_user' password = 'vyos_pass' zone = 'vyos.io' - self.session.delete(base_path) - self.session.set(base_path + ddns + [service, 'host-name', 'test.ddns.vyos.io']) - self.session.set(base_path + ddns + [service, 'login', user]) - self.session.set(base_path + ddns + [service, 'password', password]) - self.session.set(base_path + ddns + [service, 'zone', zone]) + self.cli_delete(base_path) + self.cli_set(base_path + ddns + [service, 'host-name', 'test.ddns.vyos.io']) + self.cli_set(base_path + ddns + [service, 'login', user]) + self.cli_set(base_path + ddns + [service, 'password', password]) + self.cli_set(base_path + ddns + [service, 'zone', zone]) # commit changes if service == 'cloudflare': - self.session.commit() + self.cli_commit() else: # zone option only works on cloudflare, an exception is raised # for all others with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.delete(base_path + ddns + [service, 'zone', 'vyos.io']) + self.cli_commit() + self.cli_delete(base_path + ddns + [service, 'zone', 'vyos.io']) # commit changes again - now it should work - self.session.commit() + self.cli_commit() # we can only read the configuration file when we operate as 'root' - if getuser() == 'root': - protocol = get_config_value('protocol') - login = get_config_value('login') - pwd = get_config_value('password') - - # some services need special treatment - protoname = service - if service == 'cloudflare': - tmp = get_config_value('zone') - self.assertTrue(tmp == zone) - elif service == 'afraid': - protoname = 'freedns' - elif service == 'dyndns': - protoname = 'dyndns2' - elif service == 'zoneedit': - protoname = 'zoneedit1' - - self.assertTrue(protocol == protoname) - self.assertTrue(login == user) - self.assertTrue(pwd == "'" + password + "'") + protocol = get_config_value('protocol') + login = get_config_value('login') + pwd = get_config_value('password') + + # some services need special treatment + protoname = service + if service == 'cloudflare': + tmp = get_config_value('zone') + self.assertTrue(tmp == zone) + elif service == 'afraid': + protoname = 'freedns' + elif service == 'dyndns': + protoname = 'dyndns2' + elif service == 'zoneedit': + protoname = 'zoneedit1' + + self.assertTrue(protocol == protoname) + self.assertTrue(login == user) + self.assertTrue(pwd == "'" + password + "'") # Check for running process self.assertTrue(process_named_running(PROCESS_NAME)) @@ -101,11 +97,11 @@ class TestServiceDDNS(unittest.TestCase): ddns = ['interface', 'eth0', 'rfc2136', 'vyos'] ddns_key_file = '/config/auth/my.key' - self.session.set(base_path + ddns + ['key', ddns_key_file]) - self.session.set(base_path + ddns + ['record', 'test.ddns.vyos.io']) - self.session.set(base_path + ddns + ['server', 'ns1.vyos.io']) - self.session.set(base_path + ddns + ['ttl', '300']) - self.session.set(base_path + ddns + ['zone', 'vyos.io']) + self.cli_set(base_path + ddns + ['key', ddns_key_file]) + self.cli_set(base_path + ddns + ['record', 'test.ddns.vyos.io']) + self.cli_set(base_path + ddns + ['server', 'ns1.vyos.io']) + self.cli_set(base_path + ddns + ['ttl', '300']) + self.cli_set(base_path + ddns + ['zone', 'vyos.io']) # ensure an exception will be raised as no key is present if os.path.exists(ddns_key_file): @@ -113,13 +109,13 @@ class TestServiceDDNS(unittest.TestCase): # check validate() - the key file does not exist yet with self.assertRaises(ConfigSessionError): - self.session.commit() + self.cli_commit() with open(ddns_key_file, 'w') as f: f.write('S3cretKey') # commit changes - self.session.commit() + self.cli_commit() # TODO: inspect generated configuration file diff --git a/smoketest/scripts/cli/test_service_dns_forwarding.py b/smoketest/scripts/cli/test_service_dns_forwarding.py index ada53e8dd..8005eb319 100755 --- a/smoketest/scripts/cli/test_service_dns_forwarding.py +++ b/smoketest/scripts/cli/test_service_dns_forwarding.py @@ -15,10 +15,12 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import re -import os import unittest -from vyos.configsession import ConfigSession, ConfigSessionError +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError from vyos.util import read_file from vyos.util import process_named_running @@ -37,44 +39,40 @@ def get_config_value(key, file=CONFIG_FILE): tmp = re.findall(r'\n{}=+(.*)'.format(key), tmp) return tmp[0] -class TestServicePowerDNS(unittest.TestCase): - def setUp(self): - self.session = ConfigSession(os.getpid()) - +class TestServicePowerDNS(VyOSUnitTestSHIM.TestCase): def tearDown(self): # Delete DNS forwarding configuration - self.session.delete(base_path) - self.session.commit() - del self.session + self.cli_delete(base_path) + self.cli_commit() def test_basic_forwarding(self): # Check basic DNS forwarding settings cache_size = '20' negative_ttl = '120' - self.session.set(base_path + ['cache-size', cache_size]) - self.session.set(base_path + ['negative-ttl', negative_ttl]) + self.cli_set(base_path + ['cache-size', cache_size]) + self.cli_set(base_path + ['negative-ttl', negative_ttl]) # check validate() - allow from must be defined with self.assertRaises(ConfigSessionError): - self.session.commit() + self.cli_commit() for network in allow_from: - self.session.set(base_path + ['allow-from', network]) + self.cli_set(base_path + ['allow-from', network]) # check validate() - listen-address must be defined with self.assertRaises(ConfigSessionError): - self.session.commit() + self.cli_commit() for address in listen_adress: - self.session.set(base_path + ['listen-address', address]) + self.cli_set(base_path + ['listen-address', address]) # configure DNSSEC - self.session.set(base_path + ['dnssec', 'validate']) + self.cli_set(base_path + ['dnssec', 'validate']) # Do not use local /etc/hosts file in name resolution - self.session.set(base_path + ['ignore-hosts-file']) + self.cli_set(base_path + ['ignore-hosts-file']) # commit changes - self.session.commit() + self.cli_commit() # Check configured cache-size tmp = get_config_value('max-cache-entries') @@ -103,16 +101,16 @@ class TestServicePowerDNS(unittest.TestCase): # DNSSEC option testing for network in allow_from: - self.session.set(base_path + ['allow-from', network]) + self.cli_set(base_path + ['allow-from', network]) for address in listen_adress: - self.session.set(base_path + ['listen-address', address]) + self.cli_set(base_path + ['listen-address', address]) options = ['off', 'process-no-validate', 'process', 'log-fail', 'validate'] for option in options: - self.session.set(base_path + ['dnssec', option]) + self.cli_set(base_path + ['dnssec', option]) # commit changes - self.session.commit() + self.cli_commit() tmp = get_config_value('dnssec') self.assertEqual(tmp, option) @@ -124,16 +122,16 @@ class TestServicePowerDNS(unittest.TestCase): # Externe Domain Name Servers (DNS) addresses for network in allow_from: - self.session.set(base_path + ['allow-from', network]) + self.cli_set(base_path + ['allow-from', network]) for address in listen_adress: - self.session.set(base_path + ['listen-address', address]) + self.cli_set(base_path + ['listen-address', address]) nameservers = ['192.0.2.1', '192.0.2.2'] for nameserver in nameservers: - self.session.set(base_path + ['name-server', nameserver]) + self.cli_set(base_path + ['name-server', nameserver]) # commit changes - self.session.commit() + self.cli_commit() tmp = get_config_value(r'\+.', file=FORWARD_FILE) self.assertEqual(tmp, ', '.join(nameservers)) @@ -148,26 +146,26 @@ class TestServicePowerDNS(unittest.TestCase): def test_domain_forwarding(self): for network in allow_from: - self.session.set(base_path + ['allow-from', network]) + self.cli_set(base_path + ['allow-from', network]) for address in listen_adress: - self.session.set(base_path + ['listen-address', address]) + self.cli_set(base_path + ['listen-address', address]) domains = ['vyos.io', 'vyos.net', 'vyos.com'] nameservers = ['192.0.2.1', '192.0.2.2'] for domain in domains: for nameserver in nameservers: - self.session.set(base_path + ['domain', domain, 'server', nameserver]) + self.cli_set(base_path + ['domain', domain, 'server', nameserver]) # Test 'recursion-desired' flag for only one domain if domain == domains[0]: - self.session.set(base_path + ['domain', domain, 'recursion-desired']) + self.cli_set(base_path + ['domain', domain, 'recursion-desired']) # Test 'negative trust anchor' flag for the second domain only if domain == domains[1]: - self.session.set(base_path + ['domain', domain, 'addnta']) + self.cli_set(base_path + ['domain', domain, 'addnta']) # commit changes - self.session.commit() + self.cli_commit() # Test configured name-servers hosts_conf = read_file(HOSTSD_FILE) diff --git a/smoketest/scripts/cli/test_service_https.py b/smoketest/scripts/cli/test_service_https.py index fd0f6bfbd..3ed7655e9 100755 --- a/smoketest/scripts/cli/test_service_https.py +++ b/smoketest/scripts/cli/test_service_https.py @@ -14,28 +14,27 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import os import unittest +from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSession from vyos.util import run base_path = ['service', 'https'] -class TestHTTPSService(unittest.TestCase): +class TestHTTPSService(VyOSUnitTestSHIM.TestCase): def setUp(self): - self.session = ConfigSession(os.getpid()) # ensure we can also run this test on a live system - so lets clean # out the current configuration :) - self.session.delete(base_path) + self.cli_delete(base_path) def tearDown(self): - self.session.delete(base_path) - self.session.commit() + self.cli_delete(base_path) + self.cli_commit() def test_default(self): - self.session.set(base_path) - self.session.commit() + self.cli_set(base_path) + self.cli_commit() ret = run('sudo /usr/sbin/nginx -t') self.assertEqual(ret, 0) @@ -48,11 +47,11 @@ class TestHTTPSService(unittest.TestCase): test_path = base_path + ['virtual-host', vhost_id] - self.session.set(test_path + ['listen-address', address]) - self.session.set(test_path + ['listen-port', port]) - self.session.set(test_path + ['server-name', name]) + self.cli_set(test_path + ['listen-address', address]) + self.cli_set(test_path + ['listen-port', port]) + self.cli_set(test_path + ['server-name', name]) - self.session.commit() + self.cli_commit() ret = run('sudo /usr/sbin/nginx -t') self.assertEqual(ret, 0) diff --git a/smoketest/scripts/cli/test_service_mdns-repeater.py b/smoketest/scripts/cli/test_service_mdns-repeater.py index e6986b92a..b1092c3e5 100755 --- a/smoketest/scripts/cli/test_service_mdns-repeater.py +++ b/smoketest/scripts/cli/test_service_mdns-repeater.py @@ -14,35 +14,32 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import os import unittest +from base_vyostest_shim import VyOSUnitTestSHIM + from vyos.configsession import ConfigSession from vyos.util import process_named_running base_path = ['service', 'mdns', 'repeater'] intf_base = ['interfaces', 'dummy'] -class TestServiceMDNSrepeater(unittest.TestCase): - def setUp(self): - self.session = ConfigSession(os.getpid()) - +class TestServiceMDNSrepeater(VyOSUnitTestSHIM.TestCase): def tearDown(self): - self.session.delete(base_path) - self.session.delete(intf_base + ['dum10']) - self.session.delete(intf_base + ['dum20']) - self.session.commit() - del self.session + self.cli_delete(base_path) + self.cli_delete(intf_base + ['dum10']) + self.cli_delete(intf_base + ['dum20']) + self.cli_commit() def test_service(self): # Service required a configured IP address on the interface - self.session.set(intf_base + ['dum10', 'address', '192.0.2.1/30']) - self.session.set(intf_base + ['dum20', 'address', '192.0.2.5/30']) + self.cli_set(intf_base + ['dum10', 'address', '192.0.2.1/30']) + self.cli_set(intf_base + ['dum20', 'address', '192.0.2.5/30']) - self.session.set(base_path + ['interface', 'dum10']) - self.session.set(base_path + ['interface', 'dum20']) - self.session.commit() + self.cli_set(base_path + ['interface', 'dum10']) + self.cli_set(base_path + ['interface', 'dum20']) + self.cli_commit() # Check for running process self.assertTrue(process_named_running('mdns-repeater')) diff --git a/smoketest/scripts/cli/test_service_pppoe-server.py b/smoketest/scripts/cli/test_service_pppoe-server.py index a4bb6e9f9..2b11ee362 100755 --- a/smoketest/scripts/cli/test_service_pppoe-server.py +++ b/smoketest/scripts/cli/test_service_pppoe-server.py @@ -27,7 +27,7 @@ local_if = ['interfaces', 'dummy', 'dum667'] ac_name = 'ACN' interface = 'eth0' -class TestServicePPPoEServer(BasicAccelPPPTest.BaseTest): +class TestServicePPPoEServer(BasicAccelPPPTest.TestCase): def setUp(self): self._base_path = ['service', 'pppoe-server'] self._process_name = 'accel-pppd' @@ -37,7 +37,7 @@ class TestServicePPPoEServer(BasicAccelPPPTest.BaseTest): super().setUp() def tearDown(self): - self.session.delete(local_if) + self.cli_delete(local_if) super().tearDown() def verify(self, conf): @@ -66,7 +66,7 @@ class TestServicePPPoEServer(BasicAccelPPPTest.BaseTest): super().verify(conf) def basic_config(self): - self.session.set(local_if + ['address', '192.0.2.1/32']) + self.cli_set(local_if + ['address', '192.0.2.1/32']) self.set(['access-concentrator', ac_name]) self.set(['interface', interface]) @@ -92,7 +92,7 @@ class TestServicePPPoEServer(BasicAccelPPPTest.BaseTest): self.set(['ppp-options', 'mru', mru]) # commit changes - self.session.commit() + self.cli_commit() # Validate configuration values conf = ConfigParser(allow_no_value=True, delimiters='=') @@ -124,7 +124,7 @@ class TestServicePPPoEServer(BasicAccelPPPTest.BaseTest): self.set( ['authentication', 'protocols', 'mschap-v2']) # commit changes - self.session.commit() + self.cli_commit() # Validate configuration values conf = ConfigParser(allow_no_value=True) @@ -144,12 +144,13 @@ class TestServicePPPoEServer(BasicAccelPPPTest.BaseTest): start = '192.0.2.10' stop = '192.0.2.20' - start_stop = f'{start}-{stop}' + stop_octet = stop.split('.')[3] + start_stop = f'{start}-{stop_octet}' self.set(['client-ip-pool', 'start', start]) self.set(['client-ip-pool', 'stop', stop]) # commit changes - self.session.commit() + self.cli_commit() # Validate configuration values conf = ConfigParser(allow_no_value=True) @@ -186,7 +187,7 @@ class TestServicePPPoEServer(BasicAccelPPPTest.BaseTest): self.set(['client-ipv6-pool', 'delegate', delegate_prefix, 'delegation-prefix', delegate_mask]) # commit changes - self.session.commit() + self.cli_commit() # Validate configuration values conf = ConfigParser(allow_no_value=True, delimiters='=') diff --git a/smoketest/scripts/cli/test_service_router-advert.py b/smoketest/scripts/cli/test_service_router-advert.py index b80eb3c43..b19c49c6e 100755 --- a/smoketest/scripts/cli/test_service_router-advert.py +++ b/smoketest/scripts/cli/test_service_router-advert.py @@ -15,9 +15,10 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import re -import os import unittest +from base_vyostest_shim import VyOSUnitTestSHIM + from vyos.configsession import ConfigSession from vyos.util import read_file from vyos.util import process_named_running @@ -33,26 +34,24 @@ def get_config_value(key): tmp = re.findall(r'\n?{}\s+(.*)'.format(key), tmp) return tmp[0].split()[0].replace(';','') -class TestServiceRADVD(unittest.TestCase): +class TestServiceRADVD(VyOSUnitTestSHIM.TestCase): def setUp(self): - self.session = ConfigSession(os.getpid()) - self.session.set(address_base + ['2001:db8::1/64']) + self.cli_set(address_base + ['2001:db8::1/64']) def tearDown(self): - self.session.delete(address_base) - self.session.delete(base_path) - self.session.commit() - del self.session + self.cli_delete(address_base) + self.cli_delete(base_path) + self.cli_commit() def test_single(self): - self.session.set(base_path + ['prefix', '::/64', 'no-on-link-flag']) - self.session.set(base_path + ['prefix', '::/64', 'no-autonomous-flag']) - self.session.set(base_path + ['prefix', '::/64', 'valid-lifetime', 'infinity']) - self.session.set(base_path + ['dnssl', '2001:db8::1234']) - self.session.set(base_path + ['other-config-flag']) + self.cli_set(base_path + ['prefix', '::/64', 'no-on-link-flag']) + self.cli_set(base_path + ['prefix', '::/64', 'no-autonomous-flag']) + self.cli_set(base_path + ['prefix', '::/64', 'valid-lifetime', 'infinity']) + self.cli_set(base_path + ['dnssl', '2001:db8::1234']) + self.cli_set(base_path + ['other-config-flag']) # commit changes - self.session.commit() + self.cli_commit() # verify values tmp = get_config_value('interface') diff --git a/smoketest/scripts/cli/test_service_snmp.py b/smoketest/scripts/cli/test_service_snmp.py index 81045d0b4..008271102 100755 --- a/smoketest/scripts/cli/test_service_snmp.py +++ b/smoketest/scripts/cli/test_service_snmp.py @@ -14,10 +14,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import os import re import unittest +from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError @@ -35,15 +35,11 @@ def get_config_value(key): tmp = re.findall(r'\n?{}\s+(.*)'.format(key), tmp) return tmp[0] -class TestSNMPService(unittest.TestCase): +class TestSNMPService(VyOSUnitTestSHIM.TestCase): def setUp(self): - self.session = ConfigSession(os.getpid()) # ensure we can also run this test on a live system - so lets clean # out the current configuration :) - self.session.delete(base_path) - - def tearDown(self): - del self.session + self.cli_delete(base_path) def test_snmp_basic(self): # Check if SNMP can be configured and service runs @@ -53,19 +49,19 @@ class TestSNMPService(unittest.TestCase): for auth in ['ro', 'rw']: community = 'VyOS' + auth - self.session.set(base_path + ['community', community, 'authorization', auth]) + self.cli_set(base_path + ['community', community, 'authorization', auth]) for client in clients: - self.session.set(base_path + ['community', community, 'client', client]) + self.cli_set(base_path + ['community', community, 'client', client]) for network in networks: - self.session.set(base_path + ['community', community, 'network', network]) + self.cli_set(base_path + ['community', community, 'network', network]) for addr in listen: - self.session.set(base_path + ['listen-address', addr]) + self.cli_set(base_path + ['listen-address', addr]) - self.session.set(base_path + ['contact', 'maintainers@vyos.io']) - self.session.set(base_path + ['location', 'qemu']) + self.cli_set(base_path + ['contact', 'maintainers@vyos.io']) + self.cli_set(base_path + ['location', 'qemu']) - self.session.commit() + self.cli_commit() # verify listen address, it will be returned as # ['unix:/run/snmpd.socket,udp:127.0.0.1:161,udp6:[::1]:161'] @@ -88,30 +84,30 @@ class TestSNMPService(unittest.TestCase): # Check if SNMPv3 can be configured with SHA authentication # and service runs - self.session.set(base_path + ['v3', 'engineid', '000000000000000000000002']) - self.session.set(base_path + ['v3', 'group', 'default', 'mode', 'ro']) + self.cli_set(base_path + ['v3', 'engineid', '000000000000000000000002']) + self.cli_set(base_path + ['v3', 'group', 'default', 'mode', 'ro']) # check validate() - a view must be created before this can be comitted with self.assertRaises(ConfigSessionError): - self.session.commit() + self.cli_commit() - self.session.set(base_path + ['v3', 'view', 'default', 'oid', '1']) - self.session.set(base_path + ['v3', 'group', 'default', 'view', 'default']) + self.cli_set(base_path + ['v3', 'view', 'default', 'oid', '1']) + self.cli_set(base_path + ['v3', 'group', 'default', 'view', 'default']) # create user - self.session.set(base_path + ['v3', 'user', 'vyos', 'auth', 'plaintext-password', 'vyos12345678']) - self.session.set(base_path + ['v3', 'user', 'vyos', 'auth', 'type', 'sha']) - self.session.set(base_path + ['v3', 'user', 'vyos', 'privacy', 'plaintext-password', 'vyos12345678']) - self.session.set(base_path + ['v3', 'user', 'vyos', 'privacy', 'type', 'aes']) - self.session.set(base_path + ['v3', 'user', 'vyos', 'group', 'default']) + self.cli_set(base_path + ['v3', 'user', 'vyos', 'auth', 'plaintext-password', 'vyos12345678']) + self.cli_set(base_path + ['v3', 'user', 'vyos', 'auth', 'type', 'sha']) + self.cli_set(base_path + ['v3', 'user', 'vyos', 'privacy', 'plaintext-password', 'vyos12345678']) + self.cli_set(base_path + ['v3', 'user', 'vyos', 'privacy', 'type', 'aes']) + self.cli_set(base_path + ['v3', 'user', 'vyos', 'group', 'default']) - self.session.commit() + self.cli_commit() # commit will alter the CLI values - check if they have been updated: hashed_password = '4e52fe55fd011c9c51ae2c65f4b78ca93dcafdfe' - tmp = self.session.show_config(base_path + ['v3', 'user', 'vyos', 'auth', 'encrypted-password']).split()[1] + tmp = self._session.show_config(base_path + ['v3', 'user', 'vyos', 'auth', 'encrypted-password']).split()[1] self.assertEqual(tmp, hashed_password) - tmp = self.session.show_config(base_path + ['v3', 'user', 'vyos', 'privacy', 'encrypted-password']).split()[1] + tmp = self._session.show_config(base_path + ['v3', 'user', 'vyos', 'privacy', 'encrypted-password']).split()[1] self.assertEqual(tmp, hashed_password) # TODO: read in config file and check values @@ -123,30 +119,30 @@ class TestSNMPService(unittest.TestCase): # Check if SNMPv3 can be configured with MD5 authentication # and service runs - self.session.set(base_path + ['v3', 'engineid', '000000000000000000000002']) - self.session.set(base_path + ['v3', 'group', 'default', 'mode', 'ro']) + self.cli_set(base_path + ['v3', 'engineid', '000000000000000000000002']) + self.cli_set(base_path + ['v3', 'group', 'default', 'mode', 'ro']) # check validate() - a view must be created before this can be comitted with self.assertRaises(ConfigSessionError): - self.session.commit() + self.cli_commit() - self.session.set(base_path + ['v3', 'view', 'default', 'oid', '1']) - self.session.set(base_path + ['v3', 'group', 'default', 'view', 'default']) + self.cli_set(base_path + ['v3', 'view', 'default', 'oid', '1']) + self.cli_set(base_path + ['v3', 'group', 'default', 'view', 'default']) # create user - self.session.set(base_path + ['v3', 'user', 'vyos', 'auth', 'plaintext-password', 'vyos12345678']) - self.session.set(base_path + ['v3', 'user', 'vyos', 'auth', 'type', 'md5']) - self.session.set(base_path + ['v3', 'user', 'vyos', 'privacy', 'plaintext-password', 'vyos12345678']) - self.session.set(base_path + ['v3', 'user', 'vyos', 'privacy', 'type', 'des']) - self.session.set(base_path + ['v3', 'user', 'vyos', 'group', 'default']) + self.cli_set(base_path + ['v3', 'user', 'vyos', 'auth', 'plaintext-password', 'vyos12345678']) + self.cli_set(base_path + ['v3', 'user', 'vyos', 'auth', 'type', 'md5']) + self.cli_set(base_path + ['v3', 'user', 'vyos', 'privacy', 'plaintext-password', 'vyos12345678']) + self.cli_set(base_path + ['v3', 'user', 'vyos', 'privacy', 'type', 'des']) + self.cli_set(base_path + ['v3', 'user', 'vyos', 'group', 'default']) - self.session.commit() + self.cli_commit() # commit will alter the CLI values - check if they have been updated: hashed_password = '4c67690d45d3dfcd33d0d7e308e370ad' - tmp = self.session.show_config(base_path + ['v3', 'user', 'vyos', 'auth', 'encrypted-password']).split()[1] + tmp = self._session.show_config(base_path + ['v3', 'user', 'vyos', 'auth', 'encrypted-password']).split()[1] self.assertEqual(tmp, hashed_password) - tmp = self.session.show_config(base_path + ['v3', 'user', 'vyos', 'privacy', 'encrypted-password']).split()[1] + tmp = self._session.show_config(base_path + ['v3', 'user', 'vyos', 'privacy', 'encrypted-password']).split()[1] self.assertEqual(tmp, hashed_password) # TODO: read in config file and check values diff --git a/smoketest/scripts/cli/test_service_ssh.py b/smoketest/scripts/cli/test_service_ssh.py index 0bb907c3a..c76f709b1 100755 --- a/smoketest/scripts/cli/test_service_ssh.py +++ b/smoketest/scripts/cli/test_service_ssh.py @@ -14,10 +14,12 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import re import os +import re import unittest +from base_vyostest_shim import VyOSUnitTestSHIM + from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError from vyos.util import cmd @@ -25,40 +27,41 @@ from vyos.util import process_named_running from vyos.util import read_file PROCESS_NAME = 'sshd' -SSHD_CONF = '/run/ssh/sshd_config' +SSHD_CONF = '/run/sshd/sshd_config' base_path = ['service', 'ssh'] -vrf = 'ssh-test' +vrf = 'mgmt' + +key_rsa = '/etc/ssh/ssh_host_rsa_key' +key_dsa = '/etc/ssh/ssh_host_dsa_key' +key_ed25519 = '/etc/ssh/ssh_host_ed25519_key' def get_config_value(key): tmp = read_file(SSHD_CONF) tmp = re.findall(f'\n?{key}\s+(.*)', tmp) return tmp -class TestServiceSSH(unittest.TestCase): +class TestServiceSSH(VyOSUnitTestSHIM.TestCase): def setUp(self): - self.session = ConfigSession(os.getpid()) # ensure we can also run this test on a live system - so lets clean # out the current configuration :) - self.session.delete(base_path) + self.cli_delete(base_path) def tearDown(self): # delete testing SSH config - self.session.delete(base_path) - # restore "plain" SSH access - self.session.set(base_path) - # delete VRF - self.session.delete(['vrf', 'name', vrf]) + self.cli_delete(base_path) + self.cli_commit() - self.session.commit() - del self.session + self.assertTrue(os.path.isfile(key_rsa)) + self.assertTrue(os.path.isfile(key_dsa)) + self.assertTrue(os.path.isfile(key_ed25519)) def test_ssh_default(self): # Check if SSH service runs with default settings - used for checking # behavior of <defaultValue> in XML definition - self.session.set(base_path) + self.cli_set(base_path) # commit changes - self.session.commit() + self.cli_commit() # Check configured port port = get_config_value('Port')[0] @@ -69,15 +72,15 @@ class TestServiceSSH(unittest.TestCase): def test_ssh_single_listen_address(self): # Check if SSH service can be configured and runs - self.session.set(base_path + ['port', '1234']) - self.session.set(base_path + ['disable-host-validation']) - self.session.set(base_path + ['disable-password-authentication']) - self.session.set(base_path + ['loglevel', 'verbose']) - self.session.set(base_path + ['client-keepalive-interval', '100']) - self.session.set(base_path + ['listen-address', '127.0.0.1']) + self.cli_set(base_path + ['port', '1234']) + self.cli_set(base_path + ['disable-host-validation']) + self.cli_set(base_path + ['disable-password-authentication']) + self.cli_set(base_path + ['loglevel', 'verbose']) + self.cli_set(base_path + ['client-keepalive-interval', '100']) + self.cli_set(base_path + ['listen-address', '127.0.0.1']) # commit changes - self.session.commit() + self.cli_commit() # Check configured port port = get_config_value('Port')[0] @@ -109,16 +112,16 @@ class TestServiceSSH(unittest.TestCase): def test_ssh_multiple_listen_addresses(self): # Check if SSH service can be configured and runs with multiple # listen ports and listen-addresses - ports = ['22', '2222'] + ports = ['22', '2222', '2223', '2224'] for port in ports: - self.session.set(base_path + ['port', port]) + self.cli_set(base_path + ['port', port]) addresses = ['127.0.0.1', '::1'] for address in addresses: - self.session.set(base_path + ['listen-address', address]) + self.cli_set(base_path + ['listen-address', address]) # commit changes - self.session.commit() + self.cli_commit() # Check configured port tmp = get_config_value('Port') @@ -136,17 +139,17 @@ class TestServiceSSH(unittest.TestCase): def test_ssh_vrf(self): # Check if SSH service can be bound to given VRF port = '22' - self.session.set(base_path + ['port', port]) - self.session.set(base_path + ['vrf', vrf]) + self.cli_set(base_path + ['port', port]) + self.cli_set(base_path + ['vrf', vrf]) # VRF does yet not exist - an error must be thrown with self.assertRaises(ConfigSessionError): - self.session.commit() + self.cli_commit() - self.session.set(['vrf', 'name', vrf, 'table', '1001']) + self.cli_set(['vrf', 'name', vrf, 'table', '1338']) # commit changes - self.session.commit() + self.cli_commit() # Check configured port tmp = get_config_value('Port') @@ -159,5 +162,8 @@ class TestServiceSSH(unittest.TestCase): tmp = cmd(f'ip vrf pids {vrf}') self.assertIn(PROCESS_NAME, tmp) + # delete VRF + self.cli_delete(['vrf', 'name', vrf]) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_tftp-server.py b/smoketest/scripts/cli/test_service_tftp-server.py index 82e5811ff..aed4c6beb 100755 --- a/smoketest/scripts/cli/test_service_tftp-server.py +++ b/smoketest/scripts/cli/test_service_tftp-server.py @@ -14,11 +14,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import re -import os import unittest from psutil import process_iter +from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError @@ -32,28 +31,26 @@ dummy_if_path = ['interfaces', 'dummy', 'dum69'] address_ipv4 = '192.0.2.1' address_ipv6 = '2001:db8::1' -class TestServiceTFTPD(unittest.TestCase): +class TestServiceTFTPD(VyOSUnitTestSHIM.TestCase): def setUp(self): - self.session = ConfigSession(os.getpid()) - self.session.set(dummy_if_path + ['address', address_ipv4 + '/32']) - self.session.set(dummy_if_path + ['address', address_ipv6 + '/128']) + self.cli_set(dummy_if_path + ['address', address_ipv4 + '/32']) + self.cli_set(dummy_if_path + ['address', address_ipv6 + '/128']) def tearDown(self): - self.session.delete(base_path) - self.session.delete(dummy_if_path) - self.session.commit() - del self.session + self.cli_delete(base_path) + self.cli_delete(dummy_if_path) + self.cli_commit() def test_01_tftpd_single(self): directory = '/tmp' port = '69' # default port - self.session.set(base_path + ['allow-upload']) - self.session.set(base_path + ['directory', directory]) - self.session.set(base_path + ['listen-address', address_ipv4]) + self.cli_set(base_path + ['allow-upload']) + self.cli_set(base_path + ['directory', directory]) + self.cli_set(base_path + ['listen-address', address_ipv4]) # commit changes - self.session.commit() + self.cli_commit() config = read_file('/etc/default/tftpd0') # verify listen IP address @@ -71,13 +68,13 @@ class TestServiceTFTPD(unittest.TestCase): address = [address_ipv4, address_ipv6] port = '70' - self.session.set(base_path + ['directory', directory]) + self.cli_set(base_path + ['directory', directory]) for addr in address: - self.session.set(base_path + ['listen-address', addr]) - self.session.set(base_path + ['port', port]) + self.cli_set(base_path + ['listen-address', addr]) + self.cli_set(base_path + ['port', port]) # commit changes - self.session.commit() + self.cli_commit() for idx in range(0, len(address)): config = read_file(f'/etc/default/tftpd{idx}') diff --git a/smoketest/scripts/cli/test_service_webproxy.py b/smoketest/scripts/cli/test_service_webproxy.py index 3db2daa8f..d47bd452d 100755 --- a/smoketest/scripts/cli/test_service_webproxy.py +++ b/smoketest/scripts/cli/test_service_webproxy.py @@ -14,9 +14,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import os import unittest +from base_vyostest_shim import VyOSUnitTestSHIM + from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError from vyos.util import cmd @@ -29,23 +30,21 @@ base_path = ['service', 'webproxy'] listen_if = 'dum3632' listen_ip = '192.0.2.1' -class TestServiceWebProxy(unittest.TestCase): +class TestServiceWebProxy(VyOSUnitTestSHIM.TestCase): def setUp(self): - self.session = ConfigSession(os.getpid()) - self.session.set(['interfaces', 'dummy', listen_if, 'address', listen_ip + '/32']) + self.cli_set(['interfaces', 'dummy', listen_if, 'address', listen_ip + '/32']) def tearDown(self): - self.session.delete(['interfaces', 'dummy', listen_if]) - self.session.delete(base_path) - self.session.commit() - del self.session + self.cli_delete(['interfaces', 'dummy', listen_if]) + self.cli_delete(base_path) + self.cli_commit() def test_01_basic_proxy(self): default_cache = '100' - self.session.set(base_path + ['listen-address', listen_ip]) + self.cli_set(base_path + ['listen-address', listen_ip]) # commit changes - self.session.commit() + self.cli_commit() config = read_file(PROXY_CONF) self.assertIn(f'http_port {listen_ip}:3128 intercept', config) @@ -84,24 +83,24 @@ class TestServiceWebProxy(unittest.TestCase): block_mine = ['application/pdf', 'application/x-sh'] body_max_size = '4096' - self.session.set(base_path + ['listen-address', listen_ip]) - self.session.set(base_path + ['append-domain', domain]) - self.session.set(base_path + ['default-port', port]) - self.session.set(base_path + ['cache-size', cache_size]) - self.session.set(base_path + ['disable-access-log']) + self.cli_set(base_path + ['listen-address', listen_ip]) + self.cli_set(base_path + ['append-domain', domain]) + self.cli_set(base_path + ['default-port', port]) + self.cli_set(base_path + ['cache-size', cache_size]) + self.cli_set(base_path + ['disable-access-log']) - self.session.set(base_path + ['minimum-object-size', min_obj_size]) - self.session.set(base_path + ['maximum-object-size', max_obj_size]) + self.cli_set(base_path + ['minimum-object-size', min_obj_size]) + self.cli_set(base_path + ['maximum-object-size', max_obj_size]) - self.session.set(base_path + ['outgoing-address', listen_ip]) + self.cli_set(base_path + ['outgoing-address', listen_ip]) for mime in block_mine: - self.session.set(base_path + ['reply-block-mime', mime]) + self.cli_set(base_path + ['reply-block-mime', mime]) - self.session.set(base_path + ['reply-body-max-size', body_max_size]) + self.cli_set(base_path + ['reply-body-max-size', body_max_size]) # commit changes - self.session.commit() + self.cli_commit() config = read_file(PROXY_CONF) self.assertIn(f'http_port {listen_ip}:{port} intercept', config) @@ -132,34 +131,34 @@ class TestServiceWebProxy(unittest.TestCase): ldap_attr = 'cn' ldap_filter = '(cn=%s)' - self.session.set(base_path + ['listen-address', listen_ip, 'disable-transparent']) - self.session.set(base_path + ['authentication', 'children', auth_children]) - self.session.set(base_path + ['authentication', 'credentials-ttl', cred_ttl]) + self.cli_set(base_path + ['listen-address', listen_ip, 'disable-transparent']) + self.cli_set(base_path + ['authentication', 'children', auth_children]) + self.cli_set(base_path + ['authentication', 'credentials-ttl', cred_ttl]) - self.session.set(base_path + ['authentication', 'realm', realm]) - self.session.set(base_path + ['authentication', 'method', 'ldap']) + self.cli_set(base_path + ['authentication', 'realm', realm]) + self.cli_set(base_path + ['authentication', 'method', 'ldap']) # check validate() - LDAP authentication is enabled, but server not set with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(base_path + ['authentication', 'ldap', 'server', ldap_server]) + self.cli_commit() + self.cli_set(base_path + ['authentication', 'ldap', 'server', ldap_server]) # check validate() - LDAP password can not be set when bind-dn is not define - self.session.set(base_path + ['authentication', 'ldap', 'password', ldap_password]) + self.cli_set(base_path + ['authentication', 'ldap', 'password', ldap_password]) with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(base_path + ['authentication', 'ldap', 'bind-dn', ldap_bind_dn]) + self.cli_commit() + self.cli_set(base_path + ['authentication', 'ldap', 'bind-dn', ldap_bind_dn]) # check validate() - LDAP base-dn must be set with self.assertRaises(ConfigSessionError): - self.session.commit() - self.session.set(base_path + ['authentication', 'ldap', 'base-dn', ldap_base_dn]) + self.cli_commit() + self.cli_set(base_path + ['authentication', 'ldap', 'base-dn', ldap_base_dn]) - self.session.set(base_path + ['authentication', 'ldap', 'username-attribute', ldap_attr]) - self.session.set(base_path + ['authentication', 'ldap', 'filter-expression', ldap_filter]) - self.session.set(base_path + ['authentication', 'ldap', 'use-ssl']) + self.cli_set(base_path + ['authentication', 'ldap', 'username-attribute', ldap_attr]) + self.cli_set(base_path + ['authentication', 'ldap', 'filter-expression', ldap_filter]) + self.cli_set(base_path + ['authentication', 'ldap', 'use-ssl']) # commit changes - self.session.commit() + self.cli_commit() config = read_file(PROXY_CONF) self.assertIn(f'http_port {listen_ip}:3128', config) # disable-transparent @@ -175,7 +174,7 @@ class TestServiceWebProxy(unittest.TestCase): self.assertTrue(process_named_running(PROCESS_NAME)) def test_04_cache_peer(self): - self.session.set(base_path + ['listen-address', listen_ip]) + self.cli_set(base_path + ['listen-address', listen_ip]) cache_peers = { 'foo' : '192.0.2.1', @@ -183,12 +182,12 @@ class TestServiceWebProxy(unittest.TestCase): 'baz' : '192.0.2.3', } for peer in cache_peers: - self.session.set(base_path + ['cache-peer', peer, 'address', cache_peers[peer]]) + self.cli_set(base_path + ['cache-peer', peer, 'address', cache_peers[peer]]) if peer == 'baz': - self.session.set(base_path + ['cache-peer', peer, 'type', 'sibling']) + self.cli_set(base_path + ['cache-peer', peer, 'type', 'sibling']) # commit changes - self.session.commit() + self.cli_commit() config = read_file(PROXY_CONF) self.assertIn('never_direct allow all', config) @@ -214,22 +213,22 @@ class TestServiceWebProxy(unittest.TestCase): local_ok = ['10.0.0.0', 'vyos.net'] local_ok_url = ['vyos.net', 'vyos.io'] - self.session.set(base_path + ['listen-address', listen_ip]) - self.session.set(base_path + ['url-filtering', 'squidguard', 'log', 'all']) + self.cli_set(base_path + ['listen-address', listen_ip]) + self.cli_set(base_path + ['url-filtering', 'squidguard', 'log', 'all']) for block in local_block: - self.session.set(base_path + ['url-filtering', 'squidguard', 'local-block', block]) + self.cli_set(base_path + ['url-filtering', 'squidguard', 'local-block', block]) for ok in local_ok: - self.session.set(base_path + ['url-filtering', 'squidguard', 'local-ok', ok]) + self.cli_set(base_path + ['url-filtering', 'squidguard', 'local-ok', ok]) for url in local_block_url: - self.session.set(base_path + ['url-filtering', 'squidguard', 'local-block-url', url]) + self.cli_set(base_path + ['url-filtering', 'squidguard', 'local-block-url', url]) for url in local_ok_url: - self.session.set(base_path + ['url-filtering', 'squidguard', 'local-ok-url', url]) + self.cli_set(base_path + ['url-filtering', 'squidguard', 'local-ok-url', url]) for pattern in local_block_pattern: - self.session.set(base_path + ['url-filtering', 'squidguard', 'local-block-keyword', pattern]) + self.cli_set(base_path + ['url-filtering', 'squidguard', 'local-block-keyword', pattern]) # commit changes - self.session.commit() + self.cli_commit() # Check regular Squid config config = read_file(PROXY_CONF) diff --git a/smoketest/scripts/cli/test_system_acceleration_qat.py b/smoketest/scripts/cli/test_system_acceleration_qat.py index cadb263f5..9584888d6 100755 --- a/smoketest/scripts/cli/test_system_acceleration_qat.py +++ b/smoketest/scripts/cli/test_system_acceleration_qat.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 Francois Mertz fireboxled@gmail.com +# Copyright (C) 2020-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 @@ -14,34 +14,31 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import os import unittest +from base_vyostest_shim import VyOSUnitTestSHIM + from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError base_path = ['system', 'acceleration', 'qat'] -class TestSystemLCD(unittest.TestCase): - def setUp(self): - self.session = ConfigSession(os.getpid()) - +class TestIntelQAT(VyOSUnitTestSHIM.TestCase): def tearDown(self): - self.session.delete(base_path) - self.session.commit() - del self.session + self.cli_delete(base_path) + self.cli_commit() - def test_basic(self): - """ Check if configuration script is in place and that the config - script throws an error as QAT device is not present in Qemu. This *must* - be extended with QAT autodetection once run on a QAT enabled device """ + def test_simple_unsupported(self): + # Check if configuration script is in place and that the config script + # throws an error as QAT device is not present in Qemu. This *must* be + # extended with QAT autodetection once run on a QAT enabled device # configure some system display - self.session.set(base_path) + self.cli_set(base_path) # An error must be thrown if QAT device could not be found with self.assertRaises(ConfigSessionError): - self.session.commit() + self.cli_commit() if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_ip.py b/smoketest/scripts/cli/test_system_ip.py index 8fc18ba88..e98a4e234 100755 --- a/smoketest/scripts/cli/test_system_ip.py +++ b/smoketest/scripts/cli/test_system_ip.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 Francois Mertz fireboxled@gmail.com +# Copyright (C) 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 @@ -14,22 +14,18 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import os import unittest +from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSession from vyos.util import read_file base_path = ['system', 'ip'] -class TestSystemIP(unittest.TestCase): - def setUp(self): - self.session = ConfigSession(os.getpid()) - +class TestSystemIP(VyOSUnitTestSHIM.TestCase): def tearDown(self): - self.session.delete(base_path) - self.session.commit() - del self.session + self.cli_delete(base_path) + self.cli_commit() def test_system_ip_forwarding(self): # Test if IPv4 forwarding can be disabled globally, default is '1' @@ -37,8 +33,8 @@ class TestSystemIP(unittest.TestCase): all_forwarding = '/proc/sys/net/ipv4/conf/all/forwarding' self.assertEqual(read_file(all_forwarding), '1') - self.session.set(base_path + ['disable-forwarding']) - self.session.commit() + self.cli_set(base_path + ['disable-forwarding']) + self.cli_commit() self.assertEqual(read_file(all_forwarding), '0') @@ -50,9 +46,9 @@ class TestSystemIP(unittest.TestCase): self.assertEqual(read_file(use_neigh), '0') self.assertEqual(read_file(hash_policy), '0') - self.session.set(base_path + ['multipath', 'ignore-unreachable-nexthops']) - self.session.set(base_path + ['multipath', 'layer4-hashing']) - self.session.commit() + self.cli_set(base_path + ['multipath', 'ignore-unreachable-nexthops']) + self.cli_set(base_path + ['multipath', 'layer4-hashing']) + self.cli_commit() self.assertEqual(read_file(use_neigh), '1') self.assertEqual(read_file(hash_policy), '1') @@ -69,8 +65,8 @@ class TestSystemIP(unittest.TestCase): self.assertEqual(read_file(gc_thresh1), '1024') for size in [1024, 2048, 4096, 8192, 16384, 32768]: - self.session.set(base_path + ['arp', 'table-size', str(size)]) - self.session.commit() + self.cli_set(base_path + ['arp', 'table-size', str(size)]) + self.cli_commit() self.assertEqual(read_file(gc_thresh3), str(size)) self.assertEqual(read_file(gc_thresh2), str(size // 2)) diff --git a/smoketest/scripts/cli/test_system_ipv6.py b/smoketest/scripts/cli/test_system_ipv6.py new file mode 100755 index 000000000..c9c9e833d --- /dev/null +++ b/smoketest/scripts/cli/test_system_ipv6.py @@ -0,0 +1,98 @@ +#!/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 <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM +from vyos.configsession import ConfigSession +from vyos.util import read_file + +base_path = ['system', 'ipv6'] + +file_forwarding = '/proc/sys/net/ipv6/conf/all/forwarding' +file_disable = '/etc/modprobe.d/vyos_disable_ipv6.conf' +file_dad = '/proc/sys/net/ipv6/conf/all/accept_dad' +file_multipath = '/proc/sys/net/ipv6/fib_multipath_hash_policy' + +class TestSystemIPv6(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + def test_system_ipv6_forwarding(self): + # Test if IPv6 forwarding can be disabled globally, default is '1' + # which means forwearding enabled + self.assertEqual(read_file(file_forwarding), '1') + + self.cli_set(base_path + ['disable-forwarding']) + self.cli_commit() + + self.assertEqual(read_file(file_forwarding), '0') + + def test_system_ipv6_disable(self): + # Do not assign any IPv6 address on interfaces, this requires a reboot + # which can not be tested, but we can read the config file :) + self.cli_set(base_path + ['disable']) + self.cli_commit() + + # Verify configuration file + self.assertEqual(read_file(file_disable), 'options ipv6 disable_ipv6=1') + + def test_system_ipv6_strict_dad(self): + # This defaults to 1 + self.assertEqual(read_file(file_dad), '1') + + # Do not assign any IPv6 address on interfaces, this requires a reboot + # which can not be tested, but we can read the config file :) + self.cli_set(base_path + ['strict-dad']) + self.cli_commit() + + # Verify configuration file + self.assertEqual(read_file(file_dad), '2') + + def test_system_ipv6_multipath(self): + # This defaults to 0 + self.assertEqual(read_file(file_multipath), '0') + + # Do not assign any IPv6 address on interfaces, this requires a reboot + # which can not be tested, but we can read the config file :) + self.cli_set(base_path + ['multipath', 'layer4-hashing']) + self.cli_commit() + + # Verify configuration file + self.assertEqual(read_file(file_multipath), '1') + + def test_system_ipv6_neighbor_table_size(self): + # Maximum number of entries to keep in the ARP cache, the + # default is 8192 + + gc_thresh3 = '/proc/sys/net/ipv6/neigh/default/gc_thresh3' + gc_thresh2 = '/proc/sys/net/ipv6/neigh/default/gc_thresh2' + gc_thresh1 = '/proc/sys/net/ipv6/neigh/default/gc_thresh1' + self.assertEqual(read_file(gc_thresh3), '8192') + self.assertEqual(read_file(gc_thresh2), '4096') + self.assertEqual(read_file(gc_thresh1), '1024') + + for size in [1024, 2048, 4096, 8192, 16384, 32768]: + self.cli_set(base_path + ['neighbor', 'table-size', str(size)]) + self.cli_commit() + + self.assertEqual(read_file(gc_thresh3), str(size)) + self.assertEqual(read_file(gc_thresh2), str(size // 2)) + self.assertEqual(read_file(gc_thresh1), str(size // 8)) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_lcd.py b/smoketest/scripts/cli/test_system_lcd.py index 2bf601e3b..7a39e2986 100755 --- a/smoketest/scripts/cli/test_system_lcd.py +++ b/smoketest/scripts/cli/test_system_lcd.py @@ -14,32 +14,29 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import os import unittest +from base_vyostest_shim import VyOSUnitTestSHIM from configparser import ConfigParser + from vyos.configsession import ConfigSession from vyos.util import process_named_running config_file = '/run/LCDd/LCDd.conf' base_path = ['system', 'lcd'] -class TestSystemLCD(unittest.TestCase): - def setUp(self): - self.session = ConfigSession(os.getpid()) - +class TestSystemLCD(VyOSUnitTestSHIM.TestCase): def tearDown(self): - self.session.delete(base_path) - self.session.commit() - del self.session + self.cli_delete(base_path) + self.cli_commit() def test_system_display(self): # configure some system display - self.session.set(base_path + ['device', 'ttyS1']) - self.session.set(base_path + ['model', 'cfa-533']) + self.cli_set(base_path + ['device', 'ttyS1']) + self.cli_set(base_path + ['model', 'cfa-533']) # commit changes - self.session.commit() + self.cli_commit() # load up ini-styled LCDd.conf conf = ConfigParser() diff --git a/smoketest/scripts/cli/test_system_login.py b/smoketest/scripts/cli/test_system_login.py index 6188cf38b..aa97511e0 100755 --- a/smoketest/scripts/cli/test_system_login.py +++ b/smoketest/scripts/cli/test_system_login.py @@ -14,47 +14,46 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import os import re import platform import unittest +from base_vyostest_shim import VyOSUnitTestSHIM + from distutils.version import LooseVersion from platform import release as kernel_version from subprocess import Popen, PIPE from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError from vyos.util import cmd from vyos.util import read_file +from vyos.template import inc_ip base_path = ['system', 'login'] users = ['vyos1', 'vyos2'] -class TestSystemLogin(unittest.TestCase): - def setUp(self): - self.session = ConfigSession(os.getpid()) - +class TestSystemLogin(VyOSUnitTestSHIM.TestCase): def tearDown(self): # Delete individual users from configuration for user in users: - self.session.delete(base_path + ['user', user]) + self.cli_delete(base_path + ['user', user]) - self.session.commit() - del self.session + self.cli_commit() - def test_local_user(self): + def test_system_login_user(self): # Check if user can be created and we can SSH to localhost - self.session.set(['service', 'ssh', 'port', '22']) + self.cli_set(['service', 'ssh', 'port', '22']) for user in users: name = "VyOS Roxx " + user home_dir = "/tmp/" + user - self.session.set(base_path + ['user', user, 'authentication', 'plaintext-password', user]) - self.session.set(base_path + ['user', user, 'full-name', 'VyOS Roxx']) - self.session.set(base_path + ['user', user, 'home-directory', home_dir]) + self.cli_set(base_path + ['user', user, 'authentication', 'plaintext-password', user]) + self.cli_set(base_path + ['user', user, 'full-name', 'VyOS Roxx']) + self.cli_set(base_path + ['user', user, 'home-directory', home_dir]) - self.session.commit() + self.cli_commit() for user in users: cmd = ['su','-', user] @@ -82,7 +81,7 @@ class TestSystemLogin(unittest.TestCase): for option in options: self.assertIn(f'{option}=y', kernel_config) - def test_radius_config(self): + def test_system_login_radius_ipv4(self): # Verify generated RADIUS configuration files radius_key = 'VyOSsecretVyOS' @@ -91,12 +90,18 @@ class TestSystemLogin(unittest.TestCase): radius_port = '2000' radius_timeout = '1' - self.session.set(base_path + ['radius', 'server', radius_server, 'key', radius_key]) - self.session.set(base_path + ['radius', 'server', radius_server, 'port', radius_port]) - self.session.set(base_path + ['radius', 'server', radius_server, 'timeout', radius_timeout]) - self.session.set(base_path + ['radius', 'source-address', radius_source]) + self.cli_set(base_path + ['radius', 'server', radius_server, 'key', radius_key]) + self.cli_set(base_path + ['radius', 'server', radius_server, 'port', radius_port]) + self.cli_set(base_path + ['radius', 'server', radius_server, 'timeout', radius_timeout]) + self.cli_set(base_path + ['radius', 'source-address', radius_source]) + self.cli_set(base_path + ['radius', 'source-address', inc_ip(radius_source, 1)]) + + # check validate() - Only one IPv4 source-address supported + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['radius', 'source-address', inc_ip(radius_source, 1)]) - self.session.commit() + self.cli_commit() # this file must be read with higher permissions pam_radius_auth_conf = cmd('sudo cat /etc/pam_radius_auth.conf') @@ -130,5 +135,59 @@ class TestSystemLogin(unittest.TestCase): tmp = re.findall(r'group:\s+mapname\s+files', nsswitch_conf) self.assertTrue(tmp) + def test_system_login_radius_ipv6(self): + # Verify generated RADIUS configuration files + + radius_key = 'VyOS-VyOS' + radius_server = '2001:db8::1' + radius_source = '::1' + radius_port = '4000' + radius_timeout = '4' + + self.cli_set(base_path + ['radius', 'server', radius_server, 'key', radius_key]) + self.cli_set(base_path + ['radius', 'server', radius_server, 'port', radius_port]) + self.cli_set(base_path + ['radius', 'server', radius_server, 'timeout', radius_timeout]) + self.cli_set(base_path + ['radius', 'source-address', radius_source]) + self.cli_set(base_path + ['radius', 'source-address', inc_ip(radius_source, 1)]) + + # check validate() - Only one IPv4 source-address supported + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['radius', 'source-address', inc_ip(radius_source, 1)]) + + self.cli_commit() + + # this file must be read with higher permissions + pam_radius_auth_conf = cmd('sudo cat /etc/pam_radius_auth.conf') + tmp = re.findall(r'\n?\[{}\]:{}\s+{}\s+{}\s+\[{}\]'.format(radius_server, + radius_port, radius_key, radius_timeout, + radius_source), pam_radius_auth_conf) + self.assertTrue(tmp) + + # required, static options + self.assertIn('priv-lvl 15', pam_radius_auth_conf) + self.assertIn('mapped_priv_user radius_priv_user', pam_radius_auth_conf) + + # PAM + pam_common_account = read_file('/etc/pam.d/common-account') + self.assertIn('pam_radius_auth.so', pam_common_account) + + pam_common_auth = read_file('/etc/pam.d/common-auth') + self.assertIn('pam_radius_auth.so', pam_common_auth) + + pam_common_session = read_file('/etc/pam.d/common-session') + self.assertIn('pam_radius_auth.so', pam_common_session) + + pam_common_session_noninteractive = read_file('/etc/pam.d/common-session-noninteractive') + self.assertIn('pam_radius_auth.so', pam_common_session_noninteractive) + + # NSS + nsswitch_conf = read_file('/etc/nsswitch.conf') + tmp = re.findall(r'passwd:\s+mapuid\s+files\s+mapname', nsswitch_conf) + self.assertTrue(tmp) + + tmp = re.findall(r'group:\s+mapname\s+files', nsswitch_conf) + self.assertTrue(tmp) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_nameserver.py b/smoketest/scripts/cli/test_system_nameserver.py index 5610c90c7..50dc466c2 100755 --- a/smoketest/scripts/cli/test_system_nameserver.py +++ b/smoketest/scripts/cli/test_system_nameserver.py @@ -14,12 +14,15 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import os import re import unittest -from vyos.configsession import ConfigSession, ConfigSessionError -import vyos.util as util +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError + +from vyos.util import read_file RESOLV_CONF = '/etc/resolv.conf' @@ -27,25 +30,20 @@ test_servers = ['192.0.2.10', '2001:db8:1::100'] base_path = ['system', 'name-server'] def get_name_servers(): - resolv_conf = util.read_file(RESOLV_CONF) + resolv_conf = read_file(RESOLV_CONF) return re.findall(r'\n?nameserver\s+(.*)', resolv_conf) -class TestSystemNameServer(unittest.TestCase): - def setUp(self): - self.session = ConfigSession(os.getpid()) - +class TestSystemNameServer(VyOSUnitTestSHIM.TestCase): def tearDown(self): # Delete existing name servers - self.session.delete(base_path) - self.session.commit() - - del self.session + self.cli_delete(base_path) + self.cli_commit() def test_nameserver_add(self): # Check if server is added to resolv.conf for s in test_servers: - self.session.set(base_path + [s]) - self.session.commit() + self.cli_set(base_path + [s]) + self.cli_commit() servers = get_name_servers() for s in servers: @@ -54,8 +52,8 @@ class TestSystemNameServer(unittest.TestCase): def test_nameserver_delete(self): # Test if a deleted server disappears from resolv.conf for s in test_servers: - self.session.delete(base_path + [s]) - self.session.commit() + self.cli_delete(base_path + [s]) + self.cli_commit() servers = get_name_servers() for s in servers: diff --git a/smoketest/scripts/cli/test_system_ntp.py b/smoketest/scripts/cli/test_system_ntp.py index 7d1bc144f..2b86ebd7c 100755 --- a/smoketest/scripts/cli/test_system_ntp.py +++ b/smoketest/scripts/cli/test_system_ntp.py @@ -15,9 +15,10 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import re -import os import unittest +from base_vyostest_shim import VyOSUnitTestSHIM + from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError from vyos.template import address_from_cidr @@ -26,7 +27,7 @@ from vyos.util import read_file from vyos.util import process_named_running PROCESS_NAME = 'ntpd' -NTP_CONF = '/etc/ntp.conf' +NTP_CONF = '/run/ntpd/ntpd.conf' base_path = ['system', 'ntp'] def get_config_value(key): @@ -35,17 +36,17 @@ def get_config_value(key): # remove possible trailing whitespaces return [item.strip() for item in tmp] -class TestSystemNTP(unittest.TestCase): +class TestSystemNTP(VyOSUnitTestSHIM.TestCase): def setUp(self): - self.session = ConfigSession(os.getpid()) # ensure we can also run this test on a live system - so lets clean # out the current configuration :) - self.session.delete(base_path) + self.cli_delete(base_path) def tearDown(self): - self.session.delete(base_path) - self.session.commit() - del self.session + self.cli_delete(base_path) + self.cli_commit() + + self.assertFalse(process_named_running(PROCESS_NAME)) def test_ntp_options(self): # Test basic NTP support with multiple servers and their options @@ -55,13 +56,13 @@ class TestSystemNTP(unittest.TestCase): for server in servers: for option in options: - self.session.set(base_path + ['server', server, option]) + self.cli_set(base_path + ['server', server, option]) # Test NTP pool - self.session.set(base_path + ['server', ntp_pool, 'pool']) + self.cli_set(base_path + ['server', ntp_pool, 'pool']) # commit changes - self.session.commit() + self.cli_commit() # Check generated configuration tmp = get_config_value('server') @@ -77,19 +78,23 @@ class TestSystemNTP(unittest.TestCase): def test_ntp_clients(self): # Test the allowed-networks statement + listen_address = ['127.0.0.1', '::1'] + for listen in listen_address: + self.cli_set(base_path + ['listen-address', listen]) + networks = ['192.0.2.0/24', '2001:db8:1000::/64'] for network in networks: - self.session.set(base_path + ['allow-clients', 'address', network]) + self.cli_set(base_path + ['allow-clients', 'address', network]) # Verify "NTP server not configured" verify() statement with self.assertRaises(ConfigSessionError): - self.session.commit() + self.cli_commit() servers = ['192.0.2.1', '192.0.2.2'] for server in servers: - self.session.set(base_path + ['server', server]) + self.cli_set(base_path + ['server', server]) - self.session.commit() + self.cli_commit() # Check generated client address configuration for network in networks: @@ -102,7 +107,9 @@ class TestSystemNTP(unittest.TestCase): # Check listen address tmp = get_config_value('interface') - test = ['ignore wildcard', 'listen 127.0.0.1', 'listen ::1'] + test = ['ignore wildcard'] + for listen in listen_address: + test.append(f'listen {listen}') self.assertEqual(tmp, test) # Check for running process diff --git a/smoketest/scripts/cli/test_vpn_openconnect.py b/smoketest/scripts/cli/test_vpn_openconnect.py index e27216e09..bf528c8b7 100755 --- a/smoketest/scripts/cli/test_vpn_openconnect.py +++ b/smoketest/scripts/cli/test_vpn_openconnect.py @@ -14,9 +14,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import os import unittest +from base_vyostest_shim import VyOSUnitTestSHIM + from vyos.configsession import ConfigSession from vyos.util import process_named_running @@ -25,29 +26,24 @@ base_path = ['vpn', 'openconnect'] cert = '/etc/ssl/certs/ssl-cert-snakeoil.pem' cert_key = '/etc/ssl/private/ssl-cert-snakeoil.key' -class TestVpnOpenconnect(unittest.TestCase): - def setUp(self): - self.session = ConfigSession(os.getpid()) - +class TestVpnOpenconnect(VyOSUnitTestSHIM.TestCase): def tearDown(self): # Delete vpn openconnect configuration - self.session.delete(base_path) - self.session.commit() - - del self.session + self.cli_delete(base_path) + self.cli_commit() def test_vpn(self): user = 'vyos_user' password = 'vyos_pass' - self.session.delete(base_path) - self.session.set(base_path + ["authentication", "local-users", "username", user, "password", password]) - self.session.set(base_path + ["authentication", "mode", "local"]) - self.session.set(base_path + ["network-settings", "client-ip-settings", "subnet", "192.0.2.0/24"]) - self.session.set(base_path + ["ssl", "ca-cert-file", cert]) - self.session.set(base_path + ["ssl", "cert-file", cert]) - self.session.set(base_path + ["ssl", "key-file", cert_key]) - - self.session.commit() + self.cli_delete(base_path) + self.cli_set(base_path + ["authentication", "local-users", "username", user, "password", password]) + self.cli_set(base_path + ["authentication", "mode", "local"]) + self.cli_set(base_path + ["network-settings", "client-ip-settings", "subnet", "192.0.2.0/24"]) + self.cli_set(base_path + ["ssl", "ca-cert-file", cert]) + self.cli_set(base_path + ["ssl", "cert-file", cert]) + self.cli_set(base_path + ["ssl", "key-file", cert_key]) + + self.cli_commit() # Check for running process self.assertTrue(process_named_running('ocserv-main')) diff --git a/smoketest/scripts/cli/test_vpn_sstp.py b/smoketest/scripts/cli/test_vpn_sstp.py index 95fe38dd9..033338685 100755 --- a/smoketest/scripts/cli/test_vpn_sstp.py +++ b/smoketest/scripts/cli/test_vpn_sstp.py @@ -23,18 +23,14 @@ ca_cert = '/tmp/ca.crt' ssl_cert = '/tmp/server.crt' ssl_key = '/tmp/server.key' -class TestVPNSSTPServer(BasicAccelPPPTest.BaseTest): +class TestVPNSSTPServer(BasicAccelPPPTest.TestCase): def setUp(self): self._base_path = ['vpn', 'sstp'] self._process_name = 'accel-pppd' self._config_file = '/run/accel-pppd/sstp.conf' self._chap_secrets = '/run/accel-pppd/sstp.chap-secrets' - super().setUp() - def tearDown(self): - super().tearDown() - def basic_config(self): # SSL is mandatory self.set(['ssl', 'ca-cert-file', ca_cert]) diff --git a/smoketest/scripts/cli/test_vrf.py b/smoketest/scripts/cli/test_vrf.py index 7bcfea861..591630c46 100755 --- a/smoketest/scripts/cli/test_vrf.py +++ b/smoketest/scripts/cli/test_vrf.py @@ -14,53 +14,151 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. +import re import os +import json import unittest -from vyos.configsession import ConfigSession, ConfigSessionError +from netifaces import interfaces +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Interface +from vyos.ifconfig import Section +from vyos.template import is_ipv6 +from vyos.util import cmd from vyos.util import read_file from vyos.validate import is_intf_addr_assigned -class VRFTest(unittest.TestCase): - def setUp(self): - self.session = ConfigSession(os.getpid()) - self._vrfs = ['red', 'green', 'blue'] +base_path = ['vrf'] +vrfs = ['red', 'green', 'blue', 'foo-bar', 'baz_foo'] + +class VRFTest(VyOSUnitTestSHIM.TestCase): + _interfaces = [] + + @classmethod + def setUpClass(cls): + # we need to filter out VLAN interfaces identified by a dot (.) + # in their name - just in case! + if 'TEST_ETH' in os.environ: + tmp = os.environ['TEST_ETH'].split() + cls._interfaces = tmp + else: + for tmp in Section.interfaces('ethernet'): + if not '.' in tmp: + cls._interfaces.append(tmp) + # call base-classes classmethod + super(cls, cls).setUpClass() def tearDown(self): # delete all VRFs - self.session.delete(['vrf']) - self.session.commit() - del self.session + self.cli_delete(base_path) + self.cli_commit() + for vrf in vrfs: + self.assertNotIn(vrf, interfaces()) def test_vrf_table_id(self): - table = 1000 - for vrf in self._vrfs: - base = ['vrf', 'name', vrf] - description = "VyOS-VRF-" + vrf - self.session.set(base + ['description', description]) + table = '1000' + for vrf in vrfs: + base = base_path + ['name', vrf] + description = f'VyOS-VRF-{vrf}' + self.cli_set(base + ['description', description]) # check validate() - a table ID is mandatory with self.assertRaises(ConfigSessionError): - self.session.commit() + self.cli_commit() - self.session.set(base + ['table', str(table)]) - table += 1 + self.cli_set(base + ['table', table]) + if vrf == 'green': + self.cli_set(base + ['disable']) + + table = str(int(table) + 1) # commit changes - self.session.commit() + self.cli_commit() + + # Verify VRF configuration + table = '1000' + iproute2_config = read_file('/etc/iproute2/rt_tables.d/vyos-vrf.conf') + for vrf in vrfs: + description = f'VyOS-VRF-{vrf}' + self.assertTrue(vrf in interfaces()) + vrf_if = Interface(vrf) + # validate proper interface description + self.assertEqual(vrf_if.get_alias(), description) + # validate admin up/down state of VRF + state = 'up' + if vrf == 'green': + state = 'down' + self.assertEqual(vrf_if.get_admin_state(), state) + + # Test the iproute2 lookup file, syntax is as follows: + # + # # id vrf name comment + # 1000 red # VyOS-VRF-red + # 1001 green # VyOS-VRF-green + # ... + regex = f'{table}\s+{vrf}\s+#\s+{description}' + self.assertTrue(re.findall(regex, iproute2_config)) + table = str(int(table) + 1) def test_vrf_loopback_ips(self): - table = 1000 - for vrf in self._vrfs: - base = ['vrf', 'name', vrf] - self.session.set(base + ['table', str(table)]) - table += 1 + table = '2000' + for vrf in vrfs: + base = base_path + ['name', vrf] + self.cli_set(base + ['table', str(table)]) + table = str(int(table) + 1) # commit changes - self.session.commit() - for vrf in self._vrfs: + self.cli_commit() + + # Verify VRF configuration + for vrf in vrfs: + self.assertTrue(vrf in interfaces()) self.assertTrue(is_intf_addr_assigned(vrf, '127.0.0.1')) self.assertTrue(is_intf_addr_assigned(vrf, '::1')) + def test_vrf_table_id_is_unalterable(self): + # Linux Kernel prohibits the change of a VRF table on the fly. + # VRF must be deleted and recreated! + table = '1000' + vrf = vrfs[0] + base = base_path + ['name', vrf] + self.cli_set(base + ['table', table]) + + # commit changes + self.cli_commit() + + # Check if VRF has been created + self.assertTrue(vrf in interfaces()) + + table = str(int(table) + 1) + self.cli_set(base + ['table', table]) + # check validate() - table ID can not be altered! + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + def test_vrf_assign_interface(self): + vrf = vrfs[0] + table = '5000' + self.cli_set(['vrf', 'name', vrf, 'table', table]) + + for interface in self._interfaces: + section = Section.section(interface) + self.cli_set(['interfaces', section, interface, 'vrf', vrf]) + + # commit changes + self.cli_commit() + + # Verify & cleanup + for interface in self._interfaces: + # os.readlink resolves to: '../../../../../virtual/net/foovrf' + tmp = os.readlink(f'/sys/class/net/{interface}/master').split('/')[-1] + self.assertEqual(tmp, vrf) + # cleanup + section = Section.section(interface) + self.cli_delete(['interfaces', section, interface, 'vrf']) + if __name__ == '__main__': unittest.main(verbosity=2) |