From d95200e96763e4a7ed02577b1b177c84abb77838 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Fri, 16 Dec 2022 11:41:33 +0100 Subject: dhcp: T3316: Migrate dhcp/dhcpv6 server to Kea --- smoketest/scripts/cli/test_service_dhcp-server.py | 444 +++++++++++++-------- .../scripts/cli/test_service_dhcpv6-server.py | 156 ++++++-- 2 files changed, 404 insertions(+), 196 deletions(-) (limited to 'smoketest/scripts/cli') diff --git a/smoketest/scripts/cli/test_service_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py index 093e43494..aeff2aa82 100755 --- a/smoketest/scripts/cli/test_service_dhcp-server.py +++ b/smoketest/scripts/cli/test_service_dhcp-server.py @@ -14,11 +14,15 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import os import unittest +from json import loads + from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError +from vyos.utils.dict import dict_search_recursive from vyos.utils.process import process_named_running from vyos.utils.file import read_file from vyos.template import address_from_cidr @@ -26,8 +30,10 @@ from vyos.template import inc_ip from vyos.template import dec_ip from vyos.template import netmask_from_cidr -PROCESS_NAME = 'dhcpd' -DHCPD_CONF = '/run/dhcp-server/dhcpd.conf' +PROCESS_NAME = 'kea-dhcp4' +CTRL_PROCESS_NAME = 'kea-ctrl-agent' +KEA4_CONF = '/run/kea/kea-dhcp4.conf' +KEA4_CTRL = '/run/kea/dhcp4-ctrl-socket' base_path = ['service', 'dhcp-server'] subnet = '192.0.2.0/25' router = inc_ip(subnet, 1) @@ -52,6 +58,36 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): self.cli_delete(base_path) self.cli_commit() + def walk_path(self, obj, path): + current = obj + + for i, key in enumerate(path): + if isinstance(key, str): + self.assertTrue(isinstance(current, dict), msg=f'Failed path: {path}') + self.assertTrue(key in current, msg=f'Failed path: {path}') + elif isinstance(key, int): + self.assertTrue(isinstance(current, list), msg=f'Failed path: {path}') + self.assertTrue(0 <= key < len(current), msg=f'Failed path: {path}') + else: + assert False, "Invalid type" + + current = current[key] + + return current + + def verify_config_object(self, obj, path, value): + base_obj = self.walk_path(obj, path) + self.assertTrue(isinstance(base_obj, list)) + self.assertTrue(any(True for v in base_obj if v == value)) + + def verify_config_value(self, obj, path, key, value): + base_obj = self.walk_path(obj, path) + if isinstance(base_obj, list): + self.assertTrue(any(True for v in base_obj if key in v and v[key] == value)) + elif isinstance(base_obj, dict): + self.assertTrue(key in base_obj) + self.assertEqual(base_obj[key], value) + def test_dhcp_single_pool_range(self): shared_net_name = 'SMOKE-1' @@ -60,15 +96,12 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): range_1_start = inc_ip(subnet, 40) range_1_stop = inc_ip(subnet, 50) - 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.cli_set(pool + ['default-router', router]) self.cli_set(pool + ['name-server', dns_1]) self.cli_set(pool + ['name-server', dns_2]) self.cli_set(pool + ['domain-name', domain_name]) - self.cli_set(pool + ['ping-check']) # check validate() - No DHCP address range or active static-mapping set with self.assertRaises(ConfigSessionError): @@ -81,20 +114,37 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): # commit changes self.cli_commit() - config = read_file(DHCPD_CONF) - network = address_from_cidr(subnet) - netmask = netmask_from_cidr(subnet) - self.assertIn(f'ddns-update-style interim;', config) - self.assertIn(f'subnet {network} netmask {netmask}' + r' {', config) - self.assertIn(f'option domain-name-servers {dns_1}, {dns_2};', config) - self.assertIn(f'option routers {router};', config) - self.assertIn(f'option domain-name "{domain_name}";', config) - self.assertIn(f'default-lease-time 86400;', config) - self.assertIn(f'max-lease-time 86400;', config) - self.assertIn(f'ping-check true;', config) - self.assertIn(f'range {range_0_start} {range_0_stop};', config) - self.assertIn(f'range {range_1_start} {range_1_stop};', config) - self.assertIn(f'set shared-networkname = "{shared_net_name}";', config) + config = read_file(KEA4_CONF) + obj = loads(config) + + self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'valid-lifetime', 86400) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'max-valid-lifetime', 86400) + + # Verify options + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'domain-name', 'data': domain_name}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'domain-name-servers', 'data': f'{dns_1}, {dns_2}'}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'routers', 'data': router}) + + # Verify pools + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'], + {'pool': f'{range_0_start} - {range_0_stop}'}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'], + {'pool': f'{range_1_start} - {range_1_stop}'}) # Check for running process self.assertTrue(process_named_running(PROCESS_NAME)) @@ -144,38 +194,79 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): # commit changes self.cli_commit() - config = read_file(DHCPD_CONF) - - network = address_from_cidr(subnet) - netmask = netmask_from_cidr(subnet) - self.assertIn(f'ddns-update-style none;', config) - self.assertIn(f'subnet {network} netmask {netmask}' + r' {', config) - self.assertIn(f'option domain-name-servers {dns_1}, {dns_2};', config) - self.assertIn(f'option routers {router};', config) - self.assertIn(f'option domain-name "{domain_name}";', config) - - search = '"' + ('", "').join(search_domains) + '"' - self.assertIn(f'option domain-search {search};', config) - - self.assertIn(f'option ip-forwarding true;', config) - self.assertIn(f'option smtp-server {smtp_server};', config) - self.assertIn(f'option pop-server {smtp_server};', config) - self.assertIn(f'option time-servers {time_server};', config) - self.assertIn(f'option wpad-url "{wpad}";', config) - self.assertIn(f'option dhcp-server-identifier {server_identifier};', config) - self.assertIn(f'option tftp-server-name "{tftp_server}";', config) - self.assertIn(f'option bootfile-name "{bootfile_name}";', config) - self.assertIn(f'filename "{bootfile_name}";', config) - self.assertIn(f'next-server {bootfile_server};', config) - self.assertIn(f'default-lease-time 86400;', config) - self.assertIn(f'max-lease-time 86400;', config) - self.assertIn(f'range {range_0_start} {range_0_stop};', config) - self.assertIn(f'set shared-networkname = "{shared_net_name}";', config) - self.assertIn(f'option rfc8925-ipv6-only-preferred {ipv6_only_preferred};', config) - - # weird syntax for those static routes - self.assertIn(f'option rfc3442-static-route 24,10,0,0,192,0,2,1, 0,192,0,2,1;', config) - self.assertIn(f'option windows-static-route 24,10,0,0,192,0,2,1;', config) + config = read_file(KEA4_CONF) + obj = loads(config) + + self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'boot-file-name', bootfile_name) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'next-server', bootfile_server) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'valid-lifetime', 86400) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'max-valid-lifetime', 86400) + + # Verify options + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'domain-name', 'data': domain_name}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'domain-name-servers', 'data': f'{dns_1}, {dns_2}'}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'domain-search', 'data': ', '.join(search_domains)}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'pop-server', 'data': smtp_server}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'smtp-server', 'data': smtp_server}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'time-servers', 'data': time_server}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'routers', 'data': router}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'dhcp-server-identifier', 'data': server_identifier}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'tftp-server-name', 'data': tftp_server}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'wpad-url', 'data': wpad}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'rfc3442-static-route', 'data': '24,10,0,0,192,0,2,1, 0,192,0,2,1'}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'windows-static-route', 'data': '24,10,0,0,192,0,2,1'}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'v6-only-preferred', 'data': ipv6_only_preferred}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'ip-forwarding', 'data': "true"}) + + # Verify pools + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'], + {'pool': f'{range_0_start} - {range_0_stop}'}) # Check for running process self.assertTrue(process_named_running(PROCESS_NAME)) @@ -205,27 +296,39 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): # commit changes self.cli_commit() - config = read_file(DHCPD_CONF) - network = address_from_cidr(subnet) - netmask = netmask_from_cidr(subnet) - self.assertIn(f'ddns-update-style none;', config) - self.assertIn(f'subnet {network} netmask {netmask}' + r' {', config) - self.assertIn(f'option domain-name-servers {dns_1}, {dns_2};', config) - self.assertIn(f'option routers {router};', config) - self.assertIn(f'option domain-name "{domain_name}";', config) - self.assertIn(f'default-lease-time 86400;', config) - self.assertIn(f'max-lease-time 86400;', config) + config = read_file(KEA4_CONF) + obj = loads(config) + + self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'valid-lifetime', 86400) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'max-valid-lifetime', 86400) + + # Verify options + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'domain-name', 'data': domain_name}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'domain-name-servers', 'data': f'{dns_1}, {dns_2}'}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'routers', 'data': router}) client_base = 10 for client in ['client1', 'client2', 'client3']: mac = '00:50:00:00:00:{}'.format(client_base) ip = inc_ip(subnet, client_base) - self.assertIn(f'host {shared_net_name}_{client}' + ' {', config) - self.assertIn(f'fixed-address {ip};', config) - self.assertIn(f'hardware ethernet {mac};', config) - client_base += 1 - self.assertIn(f'set shared-networkname = "{shared_net_name}";', config) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'reservations'], + {'hw-address': mac, 'ip-address': ip}) + + client_base += 1 # Check for running process self.assertTrue(process_named_running(PROCESS_NAME)) @@ -266,7 +369,9 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): # commit changes self.cli_commit() - config = read_file(DHCPD_CONF) + config = read_file(KEA4_CONF) + obj = loads(config) + for network in ['0', '1', '2', '3']: shared_net_name = f'VyOS-SMOKETEST-{network}' subnet = f'192.0.{network}.0/24' @@ -278,27 +383,43 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): range_1_start = inc_ip(subnet, 30) range_1_stop = inc_ip(subnet, 40) - network = address_from_cidr(subnet) - netmask = netmask_from_cidr(subnet) - - self.assertIn(f'ddns-update-style none;', config) - self.assertIn(f'subnet {network} netmask {netmask}' + r' {', config) - self.assertIn(f'option domain-name-servers {dns_1};', config) - self.assertIn(f'option routers {router};', config) - self.assertIn(f'option domain-name "{domain_name}";', config) - self.assertIn(f'default-lease-time {lease_time};', config) - self.assertIn(f'max-lease-time {lease_time};', config) - self.assertIn(f'range {range_0_start} {range_0_stop};', config) - self.assertIn(f'range {range_1_start} {range_1_stop};', config) - self.assertIn(f'set shared-networkname = "{shared_net_name}";', config) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', int(network), 'subnet4'], 'subnet', subnet) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', int(network), 'subnet4'], 'valid-lifetime', int(lease_time)) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', int(network), 'subnet4'], 'max-valid-lifetime', int(lease_time)) + + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', int(network), 'subnet4', 0, 'option-data'], + {'name': 'domain-name', 'data': domain_name}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', int(network), 'subnet4', 0, 'option-data'], + {'name': 'domain-name-servers', 'data': dns_1}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', int(network), 'subnet4', 0, 'option-data'], + {'name': 'routers', 'data': router}) + + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', int(network), 'subnet4', 0, 'pools'], + {'pool': f'{range_0_start} - {range_0_stop}'}) + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', int(network), 'subnet4', 0, 'pools'], + {'pool': f'{range_1_start} - {range_1_stop}'}) client_base = 60 for client in ['client1', 'client2', 'client3', 'client4']: mac = '02:50:00:00:00:{}'.format(client_base) ip = inc_ip(subnet, client_base) - self.assertIn(f'host {shared_net_name}_{client}' + ' {', config) - self.assertIn(f'fixed-address {ip};', config) - self.assertIn(f'hardware ethernet {mac};', config) + + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', int(network), 'subnet4', 0, 'reservations'], + {'hw-address': mac, 'ip-address': ip}) + client_base += 1 # Check for running process @@ -319,14 +440,23 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): # commit changes self.cli_commit() - # VErify - config = read_file(DHCPD_CONF) - network = address_from_cidr(subnet) - netmask = netmask_from_cidr(subnet) + config = read_file(KEA4_CONF) + obj = loads(config) - self.assertIn(f'subnet {network} netmask {netmask}' + r' {', config) - self.assertIn(f'option routers {router};', config) - self.assertIn(f'range {range_0_start} {range_0_stop};', config) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', 'EXCLUDE-TEST') + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet) + + # Verify options + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'routers', 'data': router}) + + # Verify pools + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'], + {'pool': f'{range_0_start} - {range_0_stop}'}) # Check for running process self.assertTrue(process_named_running(PROCESS_NAME)) @@ -352,15 +482,27 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): # commit changes self.cli_commit() - # Verify - config = read_file(DHCPD_CONF) - network = address_from_cidr(subnet) - netmask = netmask_from_cidr(subnet) + config = read_file(KEA4_CONF) + obj = loads(config) + + self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', 'EXCLUDE-TEST-2') + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet) - self.assertIn(f'subnet {network} netmask {netmask}' + r' {', config) - self.assertIn(f'option routers {router};', config) - self.assertIn(f'range {range_0_start} {range_0_stop_excl};', config) - self.assertIn(f'range {range_0_start_excl} {range_0_stop};', config) + # Verify options + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'routers', 'data': router}) + + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'], + {'pool': f'{range_0_start} - {range_0_stop_excl}'}) + + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'], + {'pool': f'{range_0_start_excl} - {range_0_stop}'}) # Check for running process self.assertTrue(process_named_running(PROCESS_NAME)) @@ -384,41 +526,23 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): # commit changes self.cli_commit() - config = read_file(DHCPD_CONF) - network = address_from_cidr(subnet) - netmask = netmask_from_cidr(subnet) - # Check the relay network - self.assertIn(f'subnet {network} netmask {netmask}' + r' { }', config) - - relay_network = address_from_cidr(relay_subnet) - relay_netmask = netmask_from_cidr(relay_subnet) - self.assertIn(f'subnet {relay_network} netmask {relay_netmask}' + r' {', config) - self.assertIn(f'option routers {relay_router};', config) - self.assertIn(f'range {range_0_start} {range_0_stop};', config) - - # Check for running process - self.assertTrue(process_named_running(PROCESS_NAME)) - - def test_dhcp_invalid_raw_options(self): - shared_net_name = 'SMOKE-5' + config = read_file(KEA4_CONF) + obj = loads(config) - range_0_start = inc_ip(subnet, 10) - range_0_stop = inc_ip(subnet, 20) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', 'RELAY') + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', relay_subnet) - pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] - # we use the first subnet IP address as default gateway - self.cli_set(pool + ['default-router', router]) - self.cli_set(pool + ['range', '0', 'start', range_0_start]) - self.cli_set(pool + ['range', '0', 'stop', range_0_stop]) + # Verify options + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'routers', 'data': relay_router}) - self.cli_set(base_path + ['global-parameters', 'this-is-crap']) - # check generate() - dhcpd should not acceot this garbage config - with self.assertRaises(ConfigSessionError): - self.cli_commit() - self.cli_delete(base_path + ['global-parameters']) - - # commit changes - self.cli_commit() + # Verify pools + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'], + {'pool': f'{range_0_start} - {range_0_stop}'}) # Check for running process self.assertTrue(process_named_running(PROCESS_NAME)) @@ -449,41 +573,43 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['failover', 'remote', failover_remote]) self.cli_set(base_path + ['failover', 'status', 'primary']) - # check validate() - failover needs to be enabled for at least one subnet - with self.assertRaises(ConfigSessionError): - self.cli_commit() - self.cli_set(pool + ['enable-failover']) - # commit changes self.cli_commit() - config = read_file(DHCPD_CONF) - - self.assertIn(f'failover peer "{failover_name}"' + r' {', config) - self.assertIn(f'primary;', config) - self.assertIn(f'mclt 1800;', config) - self.assertIn(f'mclt 1800;', config) - self.assertIn(f'split 128;', config) - self.assertIn(f'port 647;', config) - self.assertIn(f'peer port 647;', config) - self.assertIn(f'max-response-delay 30;', config) - self.assertIn(f'max-unacked-updates 10;', config) - self.assertIn(f'load balance max seconds 3;', config) - self.assertIn(f'address {failover_local};', config) - self.assertIn(f'peer address {failover_remote};', config) - - network = address_from_cidr(subnet) - netmask = netmask_from_cidr(subnet) - self.assertIn(f'ddns-update-style none;', config) - self.assertIn(f'subnet {network} netmask {netmask}' + r' {', config) - self.assertIn(f'option routers {router};', config) - self.assertIn(f'range {range_0_start} {range_0_stop};', config) - self.assertIn(f'set shared-networkname = "{shared_net_name}";', config) - self.assertIn(f'failover peer "{failover_name}";', config) - self.assertIn(f'deny dynamic bootp clients;', config) + config = read_file(KEA4_CONF) + obj = loads(config) + + # Verify failover + self.verify_config_value(obj, ['Dhcp4', 'control-socket'], 'socket-name', KEA4_CTRL) + + self.verify_config_object( + obj, + ['Dhcp4', 'hooks-libraries', 0, 'parameters', 'high-availability', 0, 'peers'], + {'name': os.uname()[1], 'url': f'http://{failover_local}:647/', 'role': 'primary', 'auto-failover': True}) + + self.verify_config_object( + obj, + ['Dhcp4', 'hooks-libraries', 0, 'parameters', 'high-availability', 0, 'peers'], + {'name': failover_name, 'url': f'http://{failover_remote}:647/', 'role': 'standby', 'auto-failover': True}) + + self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet) + + # Verify options + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'routers', 'data': router}) + + # Verify pools + self.verify_config_object( + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'], + {'pool': f'{range_0_start} - {range_0_stop}'}) # Check for running process self.assertTrue(process_named_running(PROCESS_NAME)) + self.assertTrue(process_named_running(CTRL_PROCESS_NAME)) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_dhcpv6-server.py b/smoketest/scripts/cli/test_service_dhcpv6-server.py index 4d9dabc3f..175a67537 100755 --- a/smoketest/scripts/cli/test_service_dhcpv6-server.py +++ b/smoketest/scripts/cli/test_service_dhcpv6-server.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2022 VyOS maintainers and contributors +# Copyright (C) 2020-2023 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -16,6 +16,8 @@ import unittest +from json import loads + from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError @@ -23,8 +25,8 @@ from vyos.template import inc_ip from vyos.utils.process import process_named_running from vyos.utils.file import read_file -PROCESS_NAME = 'dhcpd' -DHCPD_CONF = '/run/dhcp-server/dhcpdv6.conf' +PROCESS_NAME = 'kea-dhcp6' +KEA6_CONF = '/run/kea/kea-dhcp6.conf' base_path = ['service', 'dhcpv6-server'] subnet = '2001:db8:f00::/64' @@ -52,6 +54,36 @@ class TestServiceDHCPv6Server(VyOSUnitTestSHIM.TestCase): self.cli_delete(base_path) self.cli_commit() + def walk_path(self, obj, path): + current = obj + + for i, key in enumerate(path): + if isinstance(key, str): + self.assertTrue(isinstance(current, dict), msg=f'Failed path: {path}') + self.assertTrue(key in current, msg=f'Failed path: {path}') + elif isinstance(key, int): + self.assertTrue(isinstance(current, list), msg=f'Failed path: {path}') + self.assertTrue(0 <= key < len(current), msg=f'Failed path: {path}') + else: + assert False, "Invalid type" + + current = current[key] + + return current + + def verify_config_object(self, obj, path, value): + base_obj = self.walk_path(obj, path) + self.assertTrue(isinstance(base_obj, list)) + self.assertTrue(any(True for v in base_obj if v == value)) + + def verify_config_value(self, obj, path, key, value): + base_obj = self.walk_path(obj, path) + if isinstance(base_obj, list): + self.assertTrue(any(True for v in base_obj if key in v and v[key] == value)) + elif isinstance(base_obj, dict): + self.assertTrue(key in base_obj) + self.assertEqual(base_obj[key], value) + def test_single_pool(self): shared_net_name = 'SMOKE-1' search_domains = ['foo.vyos.net', 'bar.vyos.net'] @@ -99,34 +131,66 @@ class TestServiceDHCPv6Server(VyOSUnitTestSHIM.TestCase): # commit changes self.cli_commit() - config = read_file(DHCPD_CONF) - self.assertIn(f'option dhcp6.preference {preference};', config) - - self.assertIn(f'subnet6 {subnet}' + r' {', config) - search = '"' + '", "'.join(search_domains) + '"' - nissrv = ', '.join(nis_servers) - self.assertIn(f'range6 {range_start} {range_stop};', config) - self.assertIn(f'default-lease-time {lease_time};', config) - self.assertIn(f'default-lease-time {lease_time};', config) - self.assertIn(f'max-lease-time {max_lease_time};', config) - self.assertIn(f'min-lease-time {min_lease_time};', config) - self.assertIn(f'option dhcp6.domain-search {search};', config) - self.assertIn(f'option dhcp6.name-servers {dns_1}, {dns_2};', config) - self.assertIn(f'option dhcp6.nis-domain-name "{domain}";', config) - self.assertIn(f'option dhcp6.nis-servers {nissrv};', config) - self.assertIn(f'option dhcp6.nisp-domain-name "{domain}";', config) - self.assertIn(f'option dhcp6.nisp-servers {nissrv};', config) - self.assertIn(f'set shared-networkname = "{shared_net_name}";', config) + config = read_file(KEA6_CONF) + obj = loads(config) + + self.verify_config_value(obj, ['Dhcp6', 'shared-networks'], 'name', shared_net_name) + self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'subnet', subnet) + self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'valid-lifetime', int(lease_time)) + self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'min-valid-lifetime', int(min_lease_time)) + self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'max-valid-lifetime', int(max_lease_time)) + + # Verify options + self.verify_config_object( + obj, + ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'], + {'name': 'dns-servers', 'data': f'{dns_1}, {dns_2}'}) + self.verify_config_object( + obj, + ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'], + {'name': 'domain-search', 'data': ", ".join(search_domains)}) + self.verify_config_object( + obj, + ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'], + {'name': 'nis-domain-name', 'data': domain}) + self.verify_config_object( + obj, + ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'], + {'name': 'nis-servers', 'data': ", ".join(nis_servers)}) + self.verify_config_object( + obj, + ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'], + {'name': 'nisp-domain-name', 'data': domain}) + self.verify_config_object( + obj, + ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'], + {'name': 'nisp-servers', 'data': ", ".join(nis_servers)}) + self.verify_config_object( + obj, + ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'], + {'name': 'sntp-servers', 'data': sntp_server}) + self.verify_config_object( + obj, + ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'], + {'name': 'sip-server-dns', 'data': sip_server}) + + # Verify pools + self.verify_config_object( + obj, + ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'pools'], + {'pool': f'{range_start} - {range_stop}'}) 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) ip = inc_ip(subnet, client_base) prefix = inc_ip(subnet, client_base << 64) + '/64' - self.assertIn(f'host {shared_net_name}_{client}' + ' {', config) - self.assertIn(f'fixed-address6 {ip};', config) - self.assertIn(f'fixed-prefix6 {prefix};', config) - self.assertIn(f'host-identifier option dhcp6.client-id {cid};', config) + + self.verify_config_object( + obj, + ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'reservations'], + {'duid': cid, 'ip-addresses': [ip], 'prefixes': [prefix]}) + client_base += 1 # Check for running process @@ -138,22 +202,34 @@ class TestServiceDHCPv6Server(VyOSUnitTestSHIM.TestCase): range_start = inc_ip(subnet, 256) # ::100 range_stop = inc_ip(subnet, 65535) # ::ffff delegate_start = '2001:db8:ee::' - delegate_stop = '2001:db8:ee:ff00::' - delegate_len = '56' + delegate_len = '64' + prefix_len = '56' pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] 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]) + self.cli_set(pool + ['prefix-delegation', 'prefix', delegate_start, 'delegated-length', delegate_len]) + self.cli_set(pool + ['prefix-delegation', 'prefix', delegate_start, 'prefix-length', prefix_len]) # commit changes self.cli_commit() - config = read_file(DHCPD_CONF) - self.assertIn(f'subnet6 {subnet}' + r' {', config) - self.assertIn(f'range6 {range_start} {range_stop};', config) - self.assertIn(f'prefix6 {delegate_start} {delegate_stop} /{delegate_len};', config) + config = read_file(KEA6_CONF) + obj = loads(config) + + self.verify_config_value(obj, ['Dhcp6', 'shared-networks'], 'name', shared_net_name) + self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'subnet', subnet) + + # Verify pools + self.verify_config_object( + obj, + ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'pools'], + {'pool': f'{range_start} - {range_stop}'}) + + self.verify_config_object( + obj, + ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'pd-pools'], + {'prefix': delegate_start, 'prefix-len': int(prefix_len), 'delegated-len': int(delegate_len)}) # Check for running process self.assertTrue(process_named_running(PROCESS_NAME)) @@ -170,10 +246,16 @@ class TestServiceDHCPv6Server(VyOSUnitTestSHIM.TestCase): # commit changes self.cli_commit() - config = read_file(DHCPD_CONF) - self.assertIn(f'option dhcp6.name-servers {ns_global_1}, {ns_global_2};', config) - self.assertIn(f'subnet6 {subnet}' + r' {', config) - self.assertIn(f'set shared-networkname = "{shared_net_name}";', config) + config = read_file(KEA6_CONF) + obj = loads(config) + + self.verify_config_value(obj, ['Dhcp6', 'shared-networks'], 'name', shared_net_name) + self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'subnet', subnet) + + self.verify_config_object( + obj, + ['Dhcp6', 'option-data'], + {'name': 'dns-servers', "code": 23, "space": "dhcp6", "csv-format": True, 'data': f'{ns_global_1}, {ns_global_2}'}) # Check for running process self.assertTrue(process_named_running(PROCESS_NAME)) -- cgit v1.2.3