From 5cb8e78626988b9bad6dfe9122101c36d116afbf Mon Sep 17 00:00:00 2001 From: Nicolas Fort Date: Wed, 27 Mar 2024 17:16:53 +0000 Subject: T6068: dhcp-server: add command so user can define what type of ha use: active-active or active-passive --- interface-definitions/service_dhcp-server.xml.in | 21 ++++++++ python/vyos/template.py | 15 ++++-- smoketest/scripts/cli/test_service_dhcp-server.py | 65 ++++++++++++++++++++++- src/conf_mode/service_dhcp-server.py | 8 ++- 4 files changed, 103 insertions(+), 6 deletions(-) diff --git a/interface-definitions/service_dhcp-server.xml.in b/interface-definitions/service_dhcp-server.xml.in index 2afa05a8a..cb5f9a804 100644 --- a/interface-definitions/service_dhcp-server.xml.in +++ b/interface-definitions/service_dhcp-server.xml.in @@ -22,6 +22,27 @@ #include + + + Configure high availability mode + + active-active active-passive + + + active-active + Both server attend DHCP requests + + + active-passive + Only primary server attends DHCP requests + + + (active-active|active-passive) + + Invalid DHCP high availability mode + + active-active + IPv4 remote address used for connection diff --git a/python/vyos/template.py b/python/vyos/template.py index bde8e3554..ab06c7752 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -809,10 +809,19 @@ def kea_high_availability_json(config): source_addr = config['source_address'] remote_addr = config['remote'] + ha_mode = 'hot-standby' if config['mode'] == 'active-passive' else 'load-balancing' + ha_role = config['status'] + + if ha_role == 'primary': + peer1_role = 'primary' + peer2_role = 'standby' if ha_mode == 'hot-standby' else 'secondary' + else: + peer1_role = 'standby' if ha_mode == 'hot-standby' else 'secondary' + peer2_role = 'primary' data = { 'this-server-name': os.uname()[1], - 'mode': 'hot-standby', + 'mode': ha_mode, 'heartbeat-delay': 10000, 'max-response-delay': 10000, 'max-ack-delay': 5000, @@ -821,13 +830,13 @@ def kea_high_availability_json(config): { 'name': os.uname()[1], 'url': f'http://{source_addr}:647/', - 'role': 'standby' if config['status'] == 'secondary' else 'primary', + 'role': peer1_role, 'auto-failover': True }, { 'name': config['name'], 'url': f'http://{remote_addr}:647/', - 'role': 'primary' if config['status'] == 'secondary' else 'standby', + 'role': peer2_role, 'auto-failover': True }] } diff --git a/smoketest/scripts/cli/test_service_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py index 24bd14af2..69260e412 100755 --- a/smoketest/scripts/cli/test_service_dhcp-server.py +++ b/smoketest/scripts/cli/test_service_dhcp-server.py @@ -699,6 +699,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): 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']) + ## No mode defined -> its active-active mode by default # commit changes self.cli_commit() @@ -717,7 +718,69 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): 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}) + {'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}) + + # 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)) + + def test_dhcp_high_availability_standby(self): + shared_net_name = 'FAILOVER' + failover_name = 'VyOS-Failover' + + range_0_start = inc_ip(subnet, 10) + 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']) + # we use the first subnet IP address as default gateway + self.cli_set(pool + ['option', 'default-router', router]) + 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.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']) + self.cli_set(base_path + ['high-availability', 'mode', 'active-passive']) + + # commit changes + self.cli_commit() + + 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': '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) diff --git a/src/conf_mode/service_dhcp-server.py b/src/conf_mode/service_dhcp-server.py index ba3d69b07..518625ffc 100755 --- a/src/conf_mode/service_dhcp-server.py +++ b/src/conf_mode/service_dhcp-server.py @@ -143,8 +143,12 @@ def get_config(config=None): dhcp['shared_network_name'][network]['subnet'][subnet].update( {'range' : new_range_dict}) - if dict_search('high_availability.certificate', dhcp): - dhcp['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) + if len(dhcp['high_availability']) == 1: + ## only default value for mode is set, need to remove ha node + del dhcp['high_availability'] + else: + if dict_search('high_availability.certificate', dhcp): + dhcp['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) return dhcp -- cgit v1.2.3