From d1bb249b96cc03a2bd223326cefe2bc4a0dbad4a Mon Sep 17 00:00:00 2001 From: Indrajit Raychaudhuri Date: Tue, 17 Dec 2024 08:29:22 -0600 Subject: dhcp: T6948: kea: Add kea helpers and enrich leases with domain name Add helpers: - `kea_add_lease` to add a lease to the running kea server - `kea_get_domain_from_subnet_id` to get the domain name from subnet id Also, enrich leases with domain name from subnet id --- python/vyos/kea.py | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/python/vyos/kea.py b/python/vyos/kea.py index 65e2d99b4..c7947af3e 100644 --- a/python/vyos/kea.py +++ b/python/vyos/kea.py @@ -384,6 +384,41 @@ def kea_get_leases(inet): return leases['arguments']['leases'] +def kea_add_lease( + inet, + ip_address, + host_name=None, + mac_address=None, + iaid=None, + duid=None, + subnet_id=None, +): + args = {'ip-address': ip_address} + + if host_name: + args['hostname'] = host_name + + if subnet_id: + args['subnet-id'] = subnet_id + + # IPv4 requires MAC address, IPv6 requires either MAC address or DUID + if mac_address: + args['hw-address'] = mac_address + if duid: + args['duid'] = duid + + # IPv6 requires IAID + if inet == '6' and iaid: + args['iaid'] = iaid + + result = _ctrl_socket_command(inet, f'lease{inet}-add', args) + + if result and 'result' in result: + return result['result'] == 0 + + return False + + def kea_delete_lease(inet, ip_address): args = {'ip-address': ip_address} @@ -430,6 +465,32 @@ def kea_get_pool_from_subnet_id(config, inet, subnet_id): return None +def kea_get_domain_from_subnet_id(config, inet, subnet_id): + shared_networks = dict_search_args( + config, 'arguments', f'Dhcp{inet}', 'shared-networks' + ) + + if not shared_networks: + return None + + for network in shared_networks: + if f'subnet{inet}' not in network: + continue + + for subnet in network[f'subnet{inet}']: + if 'id' in subnet and int(subnet['id']) == int(subnet_id): + for option in subnet['option-data']: + if option['name'] == 'domain-name': + return option['data'] + + # domain-name is not found in subnet, fallback to shared-network pool option + for option in network['option-data']: + if option['name'] == 'domain-name': + return option['data'] + + return None + + def kea_get_static_mappings(config, inet, pools=[]) -> list: """ Get DHCP static mapping from active Kea DHCPv4 or DHCPv6 configuration @@ -491,6 +552,11 @@ def kea_get_server_leases(config, inet, pools=[], state=[], origin=None) -> list if config else '-' ) + data_lease['domain'] = ( + kea_get_domain_from_subnet_id(config, inet, lease['subnet-id']) + if config + else '' + ) data_lease['end'] = ( lease['expire_time'].timestamp() if lease['expire_time'] else None ) -- cgit v1.2.3 From aa44f17768570f9beb205ffd9ce7f17d601ed115 Mon Sep 17 00:00:00 2001 From: Indrajit Raychaudhuri Date: Fri, 13 Dec 2024 07:41:24 +0530 Subject: dhcp: T6948: Keep DHCP server leases in sync with hostd records Keep DHCP server leases in sync with vyos-hostd records via helper script invoked with `ExecStartPost` directive in kea-dhcp4-server.service. The helper script updates VyOS hostd records from DHCP server leases. This ensures that hostd records with the DHCP server leases are kept in sync with VyOS hostd records right after DHCP server is started. This is similar to the capability exposed via kea hook `libdhcp_run_script.so` which is invoked internally by kea when a single lease changes state. Since the kea hook is currently implemented for DHCPv4 only, this helper script is implemented for DHCPv4 only as well. --- src/system/sync-dhcp-lease-to-hosts.py | 112 +++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100755 src/system/sync-dhcp-lease-to-hosts.py diff --git a/src/system/sync-dhcp-lease-to-hosts.py b/src/system/sync-dhcp-lease-to-hosts.py new file mode 100755 index 000000000..5c8b18faf --- /dev/null +++ b/src/system/sync-dhcp-lease-to-hosts.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2025 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import argparse +import logging + +import vyos.opmode +import vyos.hostsd_client + +from vyos.configquery import ConfigTreeQuery + +from vyos.kea import kea_get_active_config +from vyos.kea import kea_get_dhcp_pools +from vyos.kea import kea_get_server_leases + +# Configure logging +logger = logging.getLogger(__name__) +# set stream as output +logs_handler = logging.StreamHandler() +logger.addHandler(logs_handler) + + +def _get_all_server_leases(inet_suffix='4') -> list: + mappings = [] + try: + active_config = kea_get_active_config(inet_suffix) + except Exception: + raise vyos.opmode.DataUnavailable('Cannot fetch DHCP server configuration') + + try: + pools = kea_get_dhcp_pools(active_config, inet_suffix) + mappings = kea_get_server_leases( + active_config, inet_suffix, pools, state=[], origin=None + ) + except Exception: + raise vyos.opmode.DataUnavailable('Cannot fetch DHCP server leases') + + return mappings + + +if __name__ == '__main__': + # Parse command arguments + parser = argparse.ArgumentParser() + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument('--inet', action='store_true', help='Use IPv4 DHCP leases') + group.add_argument('--inet6', action='store_true', help='Use IPv6 DHCP leases') + args = parser.parse_args() + + inet_suffix = '4' if args.inet else '6' + service_suffix = '' if args.inet else 'v6' + + if inet_suffix == '6': + raise vyos.opmode.UnsupportedOperation( + 'Syncing IPv6 DHCP leases are not supported yet' + ) + + # Load configuration + config = ConfigTreeQuery() + + # Check if DHCP server is configured + # Using warning instead of error since this check may fail during first-time + # DHCP server setup when the service is not yet configured in the config tree. + # This happens when called from systemd's ExecStartPost the first time. + if not config.exists(f'service dhcp{service_suffix}-server'): + logger.warning(f'DHCP{service_suffix} server is not configured') + + # Check if hostfile-update is enabled + if not config.exists(f'service dhcp{service_suffix}-server hostfile-update'): + logger.debug( + f'Hostfile update is disabled for DHCP{service_suffix} server, skipping hosts update' + ) + exit(0) + + lease_data = _get_all_server_leases(inet_suffix) + + try: + hc = vyos.hostsd_client.Client() + + for mapping in lease_data: + ip_addr = mapping.get('ip') + mac_addr = mapping.get('mac') + name = mapping.get('hostname') + name = name if name else f'host-{mac_addr.replace(":", "-")}' + domain = mapping.get('domain') + fqdn = f'{name}.{domain}' if domain else name + hc.add_hosts( + { + f'dhcp-server-{ip_addr}': { + fqdn: {'address': [ip_addr], 'aliases': []} + } + } + ) + + hc.apply() + + logger.debug('Hosts store updated successfully') + + except vyos.hostsd_client.VyOSHostsdError as e: + raise vyos.opmode.InternalError(str(e)) -- cgit v1.2.3 From caaa35b28df8b9ae26afd0e4b32c489aa90ca932 Mon Sep 17 00:00:00 2001 From: Indrajit Raychaudhuri Date: Wed, 18 Dec 2024 17:15:53 -0600 Subject: dhcp: T6948: systemd-service adjustment to invoke helper script The helper script updates VyOS hostd records from DHCP server leases. This ensures that hostd records with the DHCP server leases are kept in sync with VyOS hostd records right after the DHCP server is started. Note that `Restart` directive needs to be updated to `on-failure` so that the service is restarted in case of failure/timeout in interaction with hostd service. --- src/etc/systemd/system/kea-dhcp4-server.service.d/override.conf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/etc/systemd/system/kea-dhcp4-server.service.d/override.conf b/src/etc/systemd/system/kea-dhcp4-server.service.d/override.conf index 682e5bbce..4a04892c0 100644 --- a/src/etc/systemd/system/kea-dhcp4-server.service.d/override.conf +++ b/src/etc/systemd/system/kea-dhcp4-server.service.d/override.conf @@ -5,3 +5,5 @@ After=vyos-router.service [Service] ExecStart= ExecStart=/usr/sbin/kea-dhcp4 -c /run/kea/kea-dhcp4.conf +ExecStartPost=!/usr/bin/python3 /usr/libexec/vyos/system/sync-dhcp-lease-to-hosts.py --inet +Restart=on-failure -- cgit v1.2.3 From 64d758b8d39ee6c7c61803785b7c1e58c0006227 Mon Sep 17 00:00:00 2001 From: Indrajit Raychaudhuri Date: Fri, 13 Dec 2024 17:58:14 +0530 Subject: dhcp: T6948: Smoketest for DHCP server leases sync with hostd records The test `test_dhcp_hostsd_lease_sync` validates DHCP server leases in sync with hostd records. Since the DHCP server running in smoketest environment might not have actual clients to lease IP addresses, we simulate the lease creation and deletion using kea helper functions (`kea_add_lease` and `kea_delete_lease`). --- smoketest/scripts/cli/test_service_dhcp-server.py | 73 ++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/smoketest/scripts/cli/test_service_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py index f891bf295..21683c00d 100755 --- a/smoketest/scripts/cli/test_service_dhcp-server.py +++ b/smoketest/scripts/cli/test_service_dhcp-server.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2024 VyOS maintainers and contributors +# Copyright (C) 2020-2025 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -15,6 +15,7 @@ # along with this program. If not, see . import os +import re import unittest from json import loads @@ -22,6 +23,9 @@ from json import loads from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError +from vyos.kea import kea_add_lease +from vyos.kea import kea_delete_lease +from vyos.utils.process import cmd from vyos.utils.process import process_named_running from vyos.utils.file import read_file from vyos.template import inc_ip @@ -31,6 +35,7 @@ PROCESS_NAME = 'kea-dhcp4' CTRL_PROCESS_NAME = 'kea-ctrl-agent' KEA4_CONF = '/run/kea/kea-dhcp4.conf' KEA4_CTRL = '/run/kea/dhcp4-ctrl-socket' +HOSTSD_CLIENT = '/usr/bin/vyos-hostsd-client' base_path = ['service', 'dhcp-server'] interface = 'dum8765' subnet = '192.0.2.0/25' @@ -842,6 +847,72 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): self.cli_delete(['vrf', 'name', 'SMOKE-DHCP']) self.cli_commit() + def test_dhcp_hostsd_lease_sync(self): + shared_net_name = 'SMOKE-LEASE-SYNC' + domain_name = 'sync.private' + + client_range = range(1, 4) + subnet_range_start = inc_ip(subnet, 10) + subnet_range_stop = inc_ip(subnet, 20) + + def internal_cleanup(): + for seq in client_range: + ip_addr = inc_ip(subnet, seq) + kea_delete_lease(4, ip_addr) + cmd(f'{HOSTSD_CLIENT} --delete-hosts --tag dhcp-server-{ip_addr} --apply') + + self.addClassCleanup(internal_cleanup) + + pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] + self.cli_set(pool + ['subnet-id', '1']) + self.cli_set(pool + ['option', 'domain-name', domain_name]) + self.cli_set(pool + ['range', '0', 'start', subnet_range_start]) + self.cli_set(pool + ['range', '0', 'stop', subnet_range_stop]) + + # commit changes + self.cli_commit() + + 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'], 'id', 1) + + # 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, 'pools'], + {'pool': f'{subnet_range_start} - {subnet_range_stop}'}) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + # All up and running, now test vyos-hostsd store + + # 1. Inject leases into kea + for seq in client_range: + client = f'client{seq}' + mac = f'00:50:00:00:00:{seq:02}' + ip = inc_ip(subnet, seq) + kea_add_lease(4, ip, host_name=client, mac_address=mac) + + # 2. Verify that leases are not available in vyos-hostsd + tag_regex = re.escape(f'dhcp-server-{subnet.rsplit(".", 1)[0]}') + host_json = cmd(f'{HOSTSD_CLIENT} --get-hosts {tag_regex}') + self.assertFalse(host_json.strip('{}')) + + # 3. Restart the service to trigger vyos-hostsd sync and wait for it to start + self.assertTrue(process_named_running(PROCESS_NAME, timeout=30)) + + # 4. Verify that leases are synced and available in vyos-hostsd + tag_regex = re.escape(f'dhcp-server-{subnet.rsplit(".", 1)[0]}') + host_json = cmd(f'{HOSTSD_CLIENT} --get-hosts {tag_regex}') + self.assertTrue(host_json) if __name__ == '__main__': unittest.main(verbosity=2) -- cgit v1.2.3 From 6c809e5d143168e2c5264672c5e6bb8541ab4296 Mon Sep 17 00:00:00 2001 From: Indrajit Raychaudhuri Date: Mon, 20 Jan 2025 22:39:10 -0600 Subject: ruff: T6583: Reformat to comply with code style --- smoketest/scripts/cli/test_service_dhcp-server.py | 877 +++++++++++++++------- 1 file changed, 619 insertions(+), 258 deletions(-) diff --git a/smoketest/scripts/cli/test_service_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py index 21683c00d..7c2ebff89 100755 --- a/smoketest/scripts/cli/test_service_dhcp-server.py +++ b/smoketest/scripts/cli/test_service_dhcp-server.py @@ -44,15 +44,18 @@ dns_1 = inc_ip(subnet, 2) dns_2 = inc_ip(subnet, 3) domain_name = 'vyos.net' + class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): super(TestServiceDHCPServer, cls).setUpClass() - # Clear out current configuration to allow running this test on a live system + # Clear out current configuration to allow running this test on a live system cls.cli_delete(cls, base_path) cidr_mask = subnet.split('/')[-1] - cls.cli_set(cls, ['interfaces', 'dummy', interface, 'address', f'{router}/{cidr_mask}']) + cls.cli_set( + cls, ['interfaces', 'dummy', interface, 'address', f'{router}/{cidr_mask}'] + ) @classmethod def tearDownClass(cls): @@ -74,7 +77,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): 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" + assert False, 'Invalid type' current = current[key] @@ -97,9 +100,9 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): shared_net_name = 'SMOKE-1' range_0_start = inc_ip(subnet, 10) - range_0_stop = inc_ip(subnet, 20) + range_0_stop = inc_ip(subnet, 20) range_1_start = inc_ip(subnet, 40) - range_1_stop = inc_ip(subnet, 50) + range_1_stop = inc_ip(subnet, 50) self.cli_set(base_path + ['listen-interface', interface]) @@ -126,37 +129,56 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): config = read_file(KEA4_CONF) obj = loads(config) - self.verify_config_value(obj, ['Dhcp4', 'interfaces-config'], 'interfaces', [interface]) - 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'], 'id', 1) - self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'match-client-id', False) - 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) + self.verify_config_value( + obj, ['Dhcp4', 'interfaces-config'], 'interfaces', [interface] + ) + 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'], 'id', 1 + ) + self.verify_config_value( + obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'match-client-id', False + ) + 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}) + 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}'}) + 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}) + 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}'}) + 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}'}) + 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)) @@ -164,16 +186,16 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): def test_dhcp_single_pool_options(self): shared_net_name = 'SMOKE-0815' - range_0_start = inc_ip(subnet, 10) - range_0_stop = inc_ip(subnet, 20) - smtp_server = '1.2.3.4' - time_server = '4.3.2.1' - tftp_server = 'tftp.vyos.io' - search_domains = ['foo.vyos.net', 'bar.vyos.net'] - bootfile_name = 'vyos' - bootfile_server = '192.0.2.1' - wpad = 'http://wpad.vyos.io/foo/bar' - server_identifier = bootfile_server + range_0_start = inc_ip(subnet, 10) + range_0_stop = inc_ip(subnet, 20) + smtp_server = '1.2.3.4' + time_server = '4.3.2.1' + tftp_server = 'tftp.vyos.io' + search_domains = ['foo.vyos.net', 'bar.vyos.net'] + bootfile_name = 'vyos' + bootfile_server = '192.0.2.1' + wpad = 'http://wpad.vyos.io/foo/bar' + server_identifier = bootfile_server ipv6_only_preferred = '300' pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] @@ -195,7 +217,9 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): self.cli_set(pool + ['option', 'wpad-url', wpad]) self.cli_set(pool + ['option', 'server-identifier', server_identifier]) - self.cli_set(pool + ['option', 'static-route', '10.0.0.0/24', 'next-hop', '192.0.2.1']) + self.cli_set( + pool + ['option', 'static-route', '10.0.0.0/24', 'next-hop', '192.0.2.1'] + ) self.cli_set(pool + ['option', 'ipv6-only-preferred', ipv6_only_preferred]) self.cli_set(pool + ['option', 'time-zone', 'Europe/London']) @@ -208,86 +232,124 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): 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) + 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}) + 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}'}) + 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)}) + 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}) + 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}) + 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}) + 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}) + 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}) + 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}) + 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}) + 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'}) + 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'}) + 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}) + 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"}) + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'ip-forwarding', 'data': 'true'}, + ) # Time zone self.verify_config_object( - obj, - ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], - {'name': 'pcode', 'data': 'GMT0BST,M3.5.0/1,M10.5.0'}) + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'pcode', 'data': 'GMT0BST,M3.5.0/1,M10.5.0'}, + ) self.verify_config_object( - obj, - ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], - {'name': 'tcode', 'data': 'Europe/London'}) + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'tcode', 'data': 'Europe/London'}, + ) # Verify pools self.verify_config_object( - obj, - ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'], - {'pool': f'{range_0_start} - {range_0_stop}'}) + 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)) @@ -296,7 +358,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): shared_net_name = 'SMOKE-2' range_0_start = inc_ip(subnet, 10) - range_0_stop = inc_ip(subnet, 20) + range_0_stop = inc_ip(subnet, 20) range_router = inc_ip(subnet, 5) range_dns_1 = inc_ip(subnet, 6) @@ -325,37 +387,55 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): 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) + 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 shared-network options self.verify_config_object( - obj, - ['Dhcp4', 'shared-networks', 0, 'option-data'], - {'name': 'domain-name', 'data': domain_name}) + obj, + ['Dhcp4', 'shared-networks', 0, 'option-data'], + {'name': 'domain-name', 'data': domain_name}, + ) self.verify_config_object( - obj, - ['Dhcp4', 'shared-networks', 0, 'option-data'], - {'name': 'domain-name-servers', 'data': f'{dns_1}, {dns_2}'}) + obj, + ['Dhcp4', 'shared-networks', 0, 'option-data'], + {'name': 'domain-name-servers', 'data': f'{dns_1}, {dns_2}'}, + ) self.verify_config_object( - obj, - ['Dhcp4', 'shared-networks', 0, 'option-data'], - {'name': 'routers', 'data': router}) + obj, + ['Dhcp4', 'shared-networks', 0, 'option-data'], + {'name': 'routers', 'data': router}, + ) # Verify range options self.verify_config_object( - obj, - ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools', 0, 'option-data'], - {'name': 'domain-name-servers', 'data': f'{range_dns_1}, {range_dns_2}'}) + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools', 0, 'option-data'], + {'name': 'domain-name-servers', 'data': f'{range_dns_1}, {range_dns_2}'}, + ) self.verify_config_object( - obj, - ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools', 0, 'option-data'], - {'name': 'routers', 'data': range_router}) + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools', 0, 'option-data'], + {'name': 'routers', 'data': range_router}, + ) # Verify pool - self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'], 'pool', f'{range_0_start} - {range_0_stop}') + self.verify_config_value( + 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)) @@ -380,18 +460,31 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): for client in ['client1', 'client2', 'client3']: mac = '00:50:00:00:00:{}'.format(client_base) self.cli_set(pool + ['static-mapping', client, 'mac', mac]) - self.cli_set(pool + ['static-mapping', client, 'ip-address', inc_ip(subnet, client_base)]) + self.cli_set( + pool + + ['static-mapping', client, 'ip-address', inc_ip(subnet, client_base)] + ) client_base += 1 # cannot have both mac-address and duid set with self.assertRaises(ConfigSessionError): - self.cli_set(pool + ['static-mapping', 'client1', 'duid', '00:01:00:01:12:34:56:78:aa:bb:cc:dd:ee:11']) + self.cli_set( + pool + + [ + 'static-mapping', + 'client1', + 'duid', + '00:01:00:01:12:34:56:78:aa:bb:cc:dd:ee:11', + ] + ) self.cli_commit() self.cli_delete(pool + ['static-mapping', 'client1', 'duid']) # cannot have mappings with duplicate IP addresses self.cli_set(pool + ['static-mapping', 'dupe1', 'mac', '00:50:00:00:fe:ff']) - self.cli_set(pool + ['static-mapping', 'dupe1', 'ip-address', inc_ip(subnet, 10)]) + self.cli_set( + pool + ['static-mapping', 'dupe1', 'ip-address', inc_ip(subnet, 10)] + ) with self.assertRaises(ConfigSessionError): self.cli_commit() # Should allow disabled duplicate @@ -401,17 +494,38 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): # cannot have mappings with duplicate MAC addresses self.cli_set(pool + ['static-mapping', 'dupe2', 'mac', '00:50:00:00:00:10']) - self.cli_set(pool + ['static-mapping', 'dupe2', 'ip-address', inc_ip(subnet, 120)]) + self.cli_set( + pool + ['static-mapping', 'dupe2', 'ip-address', inc_ip(subnet, 120)] + ) with self.assertRaises(ConfigSessionError): self.cli_commit() self.cli_delete(pool + ['static-mapping', 'dupe2']) - # cannot have mappings with duplicate MAC addresses - self.cli_set(pool + ['static-mapping', 'dupe3', 'duid', '00:01:02:03:04:05:06:07:aa:aa:aa:aa:aa:01']) - self.cli_set(pool + ['static-mapping', 'dupe3', 'ip-address', inc_ip(subnet, 121)]) - self.cli_set(pool + ['static-mapping', 'dupe4', 'duid', '00:01:02:03:04:05:06:07:aa:aa:aa:aa:aa:01']) - self.cli_set(pool + ['static-mapping', 'dupe4', 'ip-address', inc_ip(subnet, 121)]) + self.cli_set( + pool + + [ + 'static-mapping', + 'dupe3', + 'duid', + '00:01:02:03:04:05:06:07:aa:aa:aa:aa:aa:01', + ] + ) + self.cli_set( + pool + ['static-mapping', 'dupe3', 'ip-address', inc_ip(subnet, 121)] + ) + self.cli_set( + pool + + [ + 'static-mapping', + 'dupe4', + 'duid', + '00:01:02:03:04:05:06:07:aa:aa:aa:aa:aa:01', + ] + ) + self.cli_set( + pool + ['static-mapping', 'dupe4', 'ip-address', inc_ip(subnet, 121)] + ) with self.assertRaises(ConfigSessionError): self.cli_commit() self.cli_delete(pool + ['static-mapping', 'dupe3']) @@ -423,25 +537,38 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): 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'], 'id', 1) - 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) + 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'], 'id', 1 + ) + 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}) + 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}'}) + 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}) + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'routers', 'data': router}, + ) client_base = 10 for client in ['client1', 'client2', 'client3']: @@ -449,9 +576,10 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): ip = inc_ip(subnet, client_base) self.verify_config_object( - obj, - ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'reservations'], - {'hostname': client, 'hw-address': mac, 'ip-address': ip}) + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'reservations'], + {'hostname': client, 'hw-address': mac, 'ip-address': ip}, + ) client_base += 1 @@ -468,11 +596,16 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): dns_1 = inc_ip(subnet, 2) range_0_start = inc_ip(subnet, 10) - range_0_stop = inc_ip(subnet, 20) + range_0_stop = inc_ip(subnet, 20) range_1_start = inc_ip(subnet, 30) - range_1_stop = inc_ip(subnet, 40) - - pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] + range_1_stop = inc_ip(subnet, 40) + + pool = base_path + [ + 'shared-network-name', + shared_net_name, + 'subnet', + subnet, + ] self.cli_set(pool + ['subnet-id', str(int(network) + 1)]) # we use the first subnet IP address as default gateway self.cli_set(pool + ['option', 'default-router', router]) @@ -489,7 +622,15 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): for client in ['client1', 'client2', 'client3', 'client4']: mac = '02:50:00:00:00:{}'.format(client_base) self.cli_set(pool + ['static-mapping', client, 'mac', mac]) - self.cli_set(pool + ['static-mapping', client, 'ip-address', inc_ip(subnet, client_base)]) + self.cli_set( + pool + + [ + 'static-mapping', + client, + 'ip-address', + inc_ip(subnet, client_base), + ] + ) client_base += 1 # commit changes @@ -505,37 +646,64 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): dns_1 = inc_ip(subnet, 2) range_0_start = inc_ip(subnet, 10) - range_0_stop = inc_ip(subnet, 20) + range_0_stop = inc_ip(subnet, 20) range_1_start = inc_ip(subnet, 30) - range_1_stop = inc_ip(subnet, 40) + range_1_stop = inc_ip(subnet, 40) - 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'], 'id', int(network) + 1) - 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_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'], + 'id', + int(network) + 1, + ) + 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}) + 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}) + 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}) + 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}'}) + 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}'}) + 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']: @@ -543,9 +711,17 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): ip = inc_ip(subnet, client_base) self.verify_config_object( - obj, - ['Dhcp4', 'shared-networks', int(network), 'subnet4', 0, 'reservations'], - {'hostname': client, 'hw-address': mac, 'ip-address': ip}) + obj, + [ + 'Dhcp4', + 'shared-networks', + int(network), + 'subnet4', + 0, + 'reservations', + ], + {'hostname': client, 'hw-address': mac, 'ip-address': ip}, + ) client_base += 1 @@ -556,7 +732,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): # T3180: verify else path when slicing DHCP ranges and exclude address # is not part of the DHCP range range_0_start = inc_ip(subnet, 10) - range_0_stop = inc_ip(subnet, 20) + range_0_stop = inc_ip(subnet, 20) pool = base_path + ['shared-network-name', 'EXCLUDE-TEST', 'subnet', subnet] self.cli_set(pool + ['subnet-id', '1']) @@ -572,25 +748,29 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): config = read_file(KEA4_CONF) obj = loads(config) - self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', 'EXCLUDE-TEST') - self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet) + self.verify_config_value( + obj, ['Dhcp4', 'shared-networks'], 'name', 'EXCLUDE-TEST' + ) + self.verify_config_value( + obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet + ) pool_obj = { 'pool': f'{range_0_start} - {range_0_stop}', - 'option-data': [{'name': 'routers', 'data': router}] + 'option-data': [{'name': 'routers', 'data': router}], } # Verify options self.verify_config_object( - obj, - ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], - {'name': 'routers', 'data': router}) + 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_obj) + obj, ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'], pool_obj + ) # Check for running process self.assertTrue(process_named_running(PROCESS_NAME)) @@ -599,11 +779,11 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): # T3180: verify else path when slicing DHCP ranges and exclude address # is not part of the DHCP range range_0_start = inc_ip(subnet, 10) - range_0_stop = inc_ip(subnet, 100) + range_0_stop = inc_ip(subnet, 100) # the DHCP exclude addresse is blanked out of the range which is done # by slicing one range into two ranges - exclude_addr = inc_ip(range_0_start, 20) + exclude_addr = inc_ip(range_0_start, 20) range_0_stop_excl = dec_ip(exclude_addr, 1) range_0_start_excl = inc_ip(exclude_addr, 1) @@ -621,34 +801,39 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): 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.verify_config_value( + obj, ['Dhcp4', 'shared-networks'], 'name', 'EXCLUDE-TEST-2' + ) + self.verify_config_value( + obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet + ) pool_obj = { 'pool': f'{range_0_start} - {range_0_stop_excl}', - 'option-data': [{'name': 'routers', 'data': router}] + 'option-data': [{'name': 'routers', 'data': router}], } pool_exclude_obj = { 'pool': f'{range_0_start_excl} - {range_0_stop}', - 'option-data': [{'name': 'routers', 'data': router}] + 'option-data': [{'name': 'routers', 'data': router}], } # Verify options self.verify_config_object( - obj, - ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], - {'name': 'routers', 'data': router}) + 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_obj) + obj, ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'], pool_obj + ) self.verify_config_object( - obj, - ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'], - pool_exclude_obj) + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'], + pool_exclude_obj, + ) # Check for running process self.assertTrue(process_named_running(PROCESS_NAME)) @@ -662,7 +847,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): relay_router = inc_ip(relay_subnet, 1) range_0_start = '10.0.1.0' - range_0_stop = '10.0.250.255' + range_0_stop = '10.0.250.255' pool = base_path + ['shared-network-name', 'RELAY', 'subnet', relay_subnet] self.cli_set(pool + ['subnet-id', '1']) @@ -676,21 +861,27 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): config = read_file(KEA4_CONF) obj = loads(config) - self.verify_config_value(obj, ['Dhcp4', 'interfaces-config'], 'interfaces', [f'{interface}/{router}']) + self.verify_config_value( + obj, ['Dhcp4', 'interfaces-config'], 'interfaces', [f'{interface}/{router}'] + ) self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', 'RELAY') - self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', relay_subnet) + self.verify_config_value( + obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', relay_subnet + ) # Verify options self.verify_config_object( - obj, - ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], - {'name': 'routers', 'data': relay_router}) + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], + {'name': 'routers', 'data': relay_router}, + ) # Verify pools self.verify_config_object( - obj, - ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'], - {'pool': f'{range_0_start} - {range_0_stop}'}) + 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)) @@ -700,7 +891,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): failover_name = 'VyOS-Failover' range_0_start = inc_ip(subnet, 10) - range_0_stop = inc_ip(subnet, 20) + range_0_stop = inc_ip(subnet, 20) pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] self.cli_set(pool + ['subnet-id', '1']) @@ -717,7 +908,9 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): failover_local = router failover_remote = inc_ip(router, 1) - self.cli_set(base_path + ['high-availability', 'source-address', failover_local]) + self.cli_set( + base_path + ['high-availability', 'source-address', failover_local] + ) self.cli_set(base_path + ['high-availability', 'name', failover_name]) self.cli_set(base_path + ['high-availability', 'remote', failover_remote]) self.cli_set(base_path + ['high-availability', 'status', 'primary']) @@ -730,32 +923,68 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): obj = loads(config) # Verify failover - self.verify_config_value(obj, ['Dhcp4', 'control-socket'], 'socket-name', KEA4_CTRL) + 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}) + [ + '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': 'secondary', '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) + [ + 'Dhcp4', + 'hooks-libraries', + 0, + 'parameters', + 'high-availability', + 0, + 'peers', + ], + { + 'name': failover_name, + 'url': f'http://{failover_remote}:647/', + 'role': 'secondary', + '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}) + 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}'}) + 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)) @@ -766,7 +995,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): failover_name = 'VyOS-Failover' range_0_start = inc_ip(subnet, 10) - range_0_stop = inc_ip(subnet, 20) + range_0_stop = inc_ip(subnet, 20) pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] self.cli_set(pool + ['subnet-id', '1']) @@ -779,7 +1008,9 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): failover_local = router failover_remote = inc_ip(router, 1) - self.cli_set(base_path + ['high-availability', 'source-address', failover_local]) + self.cli_set( + base_path + ['high-availability', 'source-address', failover_local] + ) self.cli_set(base_path + ['high-availability', 'name', failover_name]) self.cli_set(base_path + ['high-availability', 'remote', failover_remote]) self.cli_set(base_path + ['high-availability', 'status', 'secondary']) @@ -792,32 +1023,68 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): obj = loads(config) # Verify failover - self.verify_config_value(obj, ['Dhcp4', 'control-socket'], 'socket-name', KEA4_CTRL) + 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': 'standby', 'auto-failover': True}) + [ + 'Dhcp4', + 'hooks-libraries', + 0, + 'parameters', + 'high-availability', + 0, + 'peers', + ], + { + 'name': os.uname()[1], + 'url': f'http://{failover_local}:647/', + 'role': 'standby', + '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': 'primary', '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) + [ + 'Dhcp4', + 'hooks-libraries', + 0, + 'parameters', + 'high-availability', + 0, + 'peers', + ], + { + 'name': failover_name, + 'url': f'http://{failover_remote}:647/', + 'role': 'primary', + '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}) + 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}'}) + 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)) @@ -826,24 +1093,107 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): def test_dhcp_on_interface_with_vrf(self): self.cli_set(['interfaces', 'ethernet', 'eth1', 'address', '10.1.1.1/30']) self.cli_set(['interfaces', 'ethernet', 'eth1', 'vrf', 'SMOKE-DHCP']) - self.cli_set(['protocols', 'static', 'route', '10.1.10.0/24', 'interface', 'eth1', 'vrf', 'SMOKE-DHCP']) - self.cli_set(['vrf', 'name', 'SMOKE-DHCP', 'protocols', 'static', 'route', '10.1.10.0/24', 'next-hop', '10.1.1.2']) + self.cli_set( + [ + 'protocols', + 'static', + 'route', + '10.1.10.0/24', + 'interface', + 'eth1', + 'vrf', + 'SMOKE-DHCP', + ] + ) + self.cli_set( + [ + 'vrf', + 'name', + 'SMOKE-DHCP', + 'protocols', + 'static', + 'route', + '10.1.10.0/24', + 'next-hop', + '10.1.1.2', + ] + ) self.cli_set(['vrf', 'name', 'SMOKE-DHCP', 'table', '1000']) - self.cli_set(base_path + ['shared-network-name', 'SMOKE-DHCP-NETWORK', 'subnet', '10.1.10.0/24', 'subnet-id', '1']) - self.cli_set(base_path + ['shared-network-name', 'SMOKE-DHCP-NETWORK', 'subnet', '10.1.10.0/24', 'option', 'default-router', '10.1.10.1']) - self.cli_set(base_path + ['shared-network-name', 'SMOKE-DHCP-NETWORK', 'subnet', '10.1.10.0/24', 'option', 'name-server', '1.1.1.1']) - self.cli_set(base_path + ['shared-network-name', 'SMOKE-DHCP-NETWORK', 'subnet', '10.1.10.0/24', 'range', '1', 'start', '10.1.10.10']) - self.cli_set(base_path + ['shared-network-name', 'SMOKE-DHCP-NETWORK', 'subnet', '10.1.10.0/24', 'range', '1', 'stop', '10.1.10.20']) + self.cli_set( + base_path + + [ + 'shared-network-name', + 'SMOKE-DHCP-NETWORK', + 'subnet', + '10.1.10.0/24', + 'subnet-id', + '1', + ] + ) + self.cli_set( + base_path + + [ + 'shared-network-name', + 'SMOKE-DHCP-NETWORK', + 'subnet', + '10.1.10.0/24', + 'option', + 'default-router', + '10.1.10.1', + ] + ) + self.cli_set( + base_path + + [ + 'shared-network-name', + 'SMOKE-DHCP-NETWORK', + 'subnet', + '10.1.10.0/24', + 'option', + 'name-server', + '1.1.1.1', + ] + ) + self.cli_set( + base_path + + [ + 'shared-network-name', + 'SMOKE-DHCP-NETWORK', + 'subnet', + '10.1.10.0/24', + 'range', + '1', + 'start', + '10.1.10.10', + ] + ) + self.cli_set( + base_path + + [ + 'shared-network-name', + 'SMOKE-DHCP-NETWORK', + 'subnet', + '10.1.10.0/24', + 'range', + '1', + 'stop', + '10.1.10.20', + ] + ) self.cli_set(base_path + ['listen-address', '10.1.1.1']) self.cli_commit() config = read_file(KEA4_CONF) obj = loads(config) - self.verify_config_value(obj, ['Dhcp4', 'interfaces-config'], 'interfaces', ['eth1/10.1.1.1']) + self.verify_config_value( + obj, ['Dhcp4', 'interfaces-config'], 'interfaces', ['eth1/10.1.1.1'] + ) self.cli_delete(['interfaces', 'ethernet', 'eth1', 'vrf', 'SMOKE-DHCP']) - self.cli_delete(['protocols', 'static', 'route', '10.1.10.0/24', 'interface', 'eth1', 'vrf']) + self.cli_delete( + ['protocols', 'static', 'route', '10.1.10.0/24', 'interface', 'eth1', 'vrf'] + ) self.cli_delete(['vrf', 'name', 'SMOKE-DHCP']) self.cli_commit() @@ -853,13 +1203,15 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): client_range = range(1, 4) subnet_range_start = inc_ip(subnet, 10) - subnet_range_stop = inc_ip(subnet, 20) + subnet_range_stop = inc_ip(subnet, 20) def internal_cleanup(): for seq in client_range: ip_addr = inc_ip(subnet, seq) kea_delete_lease(4, ip_addr) - cmd(f'{HOSTSD_CLIENT} --delete-hosts --tag dhcp-server-{ip_addr} --apply') + cmd( + f'{HOSTSD_CLIENT} --delete-hosts --tag dhcp-server-{ip_addr} --apply' + ) self.addClassCleanup(internal_cleanup) @@ -875,19 +1227,27 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): 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'], 'id', 1) + 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'], 'id', 1 + ) # Verify options self.verify_config_object( - obj, - ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'], - {'name': 'domain-name', 'data': domain_name}) + 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, 'pools'], - {'pool': f'{subnet_range_start} - {subnet_range_stop}'}) + obj, + ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'], + {'pool': f'{subnet_range_start} - {subnet_range_stop}'}, + ) # Check for running process self.assertTrue(process_named_running(PROCESS_NAME)) @@ -914,5 +1274,6 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): host_json = cmd(f'{HOSTSD_CLIENT} --get-hosts {tag_regex}') self.assertTrue(host_json) + if __name__ == '__main__': unittest.main(verbosity=2) -- cgit v1.2.3 From cc45fafda413312213b0402e381774aaeb001d6e Mon Sep 17 00:00:00 2001 From: Indrajit Raychaudhuri Date: Tue, 21 Jan 2025 14:38:48 -0600 Subject: dhcp: T7052: Clean up unnecessary params in formatter methods The formatter methods are mostly `family` agnostic now. --- src/op_mode/dhcp.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/op_mode/dhcp.py b/src/op_mode/dhcp.py index 8eed2c6cd..725bfc75b 100755 --- a/src/op_mode/dhcp.py +++ b/src/op_mode/dhcp.py @@ -205,7 +205,7 @@ def _get_raw_server_pool_statistics(config, family='inet', pool=None): return stats -def _get_formatted_server_pool_statistics(pool_data, family='inet'): +def _get_formatted_server_pool_statistics(pool_data): data_entries = [] for entry in pool_data: pool = entry.get('pool') @@ -235,7 +235,7 @@ def _get_raw_server_static_mappings(config, family='inet', pool=None, sorted=Non return mappings -def _get_formatted_server_static_mappings(raw_data, family='inet'): +def _get_formatted_server_static_mappings(raw_data): data_entries = [] for entry in raw_data: @@ -245,10 +245,8 @@ def _get_formatted_server_static_mappings(raw_data, family='inet'): ip_addr = entry.get('ip', 'N/A') mac_addr = entry.get('mac', 'N/A') duid = entry.get('duid', 'N/A') - description = entry.get('description', 'N/A') - data_entries.append( - [pool, subnet, hostname, ip_addr, mac_addr, duid, description] - ) + desc = entry.get('description', 'N/A') + data_entries.append([pool, subnet, hostname, ip_addr, mac_addr, duid, desc]) headers = [ 'Pool', @@ -327,7 +325,7 @@ def show_server_pool_statistics( if raw: return pool_data else: - return _get_formatted_server_pool_statistics(pool_data, family=family) + return _get_formatted_server_pool_statistics(pool_data) @_verify_server @@ -408,7 +406,7 @@ def show_server_static_mappings( if raw: return static_mappings else: - return _get_formatted_server_static_mappings(static_mappings, family=family) + return _get_formatted_server_static_mappings(static_mappings) def _lease_valid(inet, address): @@ -482,7 +480,7 @@ def _get_raw_client_leases(family='inet', interface=None): return lease_data -def _get_formatted_client_leases(lease_data, family): +def _get_formatted_client_leases(lease_data): from time import localtime from time import strftime @@ -534,7 +532,7 @@ def show_client_leases(raw: bool, family: ArgFamily, interface: typing.Optional[ if raw: return lease_data else: - return _get_formatted_client_leases(lease_data, family=family) + return _get_formatted_client_leases(lease_data) @_verify_client -- cgit v1.2.3