diff options
-rw-r--r-- | interface-definitions/service_dhcp-server.xml.in | 21 | ||||
-rw-r--r-- | python/vyos/template.py | 15 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_protocols_bgp.py | 71 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_service_dhcp-server.py | 65 | ||||
-rwxr-xr-x | src/conf_mode/protocols_bgp.py | 13 | ||||
-rwxr-xr-x | src/conf_mode/service_dhcp-server.py | 8 |
6 files changed, 187 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 @@ </properties> <children> #include <include/source-address-ipv4.xml.i> + <leafNode name="mode"> + <properties> + <help>Configure high availability mode</help> + <completionHelp> + <list>active-active active-passive</list> + </completionHelp> + <valueHelp> + <format>active-active</format> + <description>Both server attend DHCP requests</description> + </valueHelp> + <valueHelp> + <format>active-passive</format> + <description>Only primary server attends DHCP requests</description> + </valueHelp> + <constraint> + <regex>(active-active|active-passive)</regex> + </constraint> + <constraintErrorMessage>Invalid DHCP high availability mode</constraintErrorMessage> + </properties> + <defaultValue>active-active</defaultValue> + </leafNode> <leafNode name="remote"> <properties> <help>IPv4 remote address used for connection</help> diff --git a/python/vyos/template.py b/python/vyos/template.py index 3e468eb82..ac77e8a3d 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -823,10 +823,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, @@ -835,13 +844,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_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index 60c49b8b4..03daa34aa 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -1259,6 +1259,77 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn('neighbor peer1 route-reflector-client', conf) + def test_bgp_28_peer_group_member_all_internal_or_external(self): + def _common_config_check(conf, include_ras=True): + if include_ras: + self.assertIn(f'neighbor {int_neighbors[0]} remote-as {ASN}', conf) + self.assertIn(f'neighbor {int_neighbors[1]} remote-as {ASN}', conf) + self.assertIn(f'neighbor {ext_neighbors[0]} remote-as {int(ASN) + 1}',conf) + + self.assertIn(f'neighbor {int_neighbors[0]} peer-group {int_pg_name}', conf) + self.assertIn(f'neighbor {int_neighbors[1]} peer-group {int_pg_name}', conf) + self.assertIn(f'neighbor {ext_neighbors[0]} peer-group {ext_pg_name}', conf) + + int_neighbors = ['192.0.2.2', '192.0.2.3'] + ext_neighbors = ['192.122.2.2', '192.122.2.3'] + int_pg_name, ext_pg_name = 'SMOKETESTINT', 'SMOKETESTEXT' + + self.cli_set(base_path + ['neighbor', int_neighbors[0], 'peer-group', int_pg_name]) + self.cli_set(base_path + ['neighbor', int_neighbors[0], 'remote-as', ASN]) + self.cli_set(base_path + ['peer-group', int_pg_name, 'address-family', 'ipv4-unicast']) + self.cli_set(base_path + ['neighbor', ext_neighbors[0], 'peer-group', ext_pg_name]) + self.cli_set(base_path + ['neighbor', ext_neighbors[0], 'remote-as', f'{int(ASN) + 1}']) + self.cli_set(base_path + ['peer-group', ext_pg_name, 'address-family', 'ipv4-unicast']) + self.cli_commit() + + # test add external remote-as to internal group + self.cli_set(base_path + ['neighbor', int_neighbors[1], 'peer-group', int_pg_name]) + self.cli_set(base_path + ['neighbor', int_neighbors[1], 'remote-as', f'{int(ASN) + 1}']) + + with self.assertRaises(ConfigSessionError) as e: + self.cli_commit() + # self.assertIn('\nPeer-group members must be all internal or all external\n', str(e.exception)) + + # test add internal remote-as to internal group + self.cli_set(base_path + ['neighbor', int_neighbors[1], 'remote-as', ASN]) + self.cli_commit() + + conf = self.getFRRconfig(f'router bgp {ASN}') + _common_config_check(conf) + + # test add internal remote-as to external group + self.cli_set(base_path + ['neighbor', ext_neighbors[1], 'peer-group', ext_pg_name]) + self.cli_set(base_path + ['neighbor', ext_neighbors[1], 'remote-as', ASN]) + + with self.assertRaises(ConfigSessionError) as e: + self.cli_commit() + # self.assertIn('\nPeer-group members must be all internal or all external\n', str(e.exception)) + + # test add external remote-as to external group + self.cli_set(base_path + ['neighbor', ext_neighbors[1], 'remote-as', f'{int(ASN) + 2}']) + self.cli_commit() + + conf = self.getFRRconfig(f'router bgp {ASN}') + _common_config_check(conf) + self.assertIn(f'neighbor {ext_neighbors[1]} remote-as {int(ASN) + 2}', conf) + self.assertIn(f'neighbor {ext_neighbors[1]} peer-group {ext_pg_name}', conf) + + # test named remote-as + self.cli_set(base_path + ['neighbor', int_neighbors[0], 'remote-as', 'internal']) + self.cli_set(base_path + ['neighbor', int_neighbors[1], 'remote-as', 'internal']) + self.cli_set(base_path + ['neighbor', ext_neighbors[0], 'remote-as', 'external']) + self.cli_set(base_path + ['neighbor', ext_neighbors[1], 'remote-as', 'external']) + self.cli_commit() + + conf = self.getFRRconfig(f'router bgp {ASN}') + _common_config_check(conf, include_ras=False) + + self.assertIn(f'neighbor {int_neighbors[0]} remote-as internal', conf) + self.assertIn(f'neighbor {int_neighbors[1]} remote-as internal', conf) + self.assertIn(f'neighbor {ext_neighbors[0]} remote-as external', conf) + self.assertIn(f'neighbor {ext_neighbors[1]} remote-as external', conf) + self.assertIn(f'neighbor {ext_neighbors[1]} peer-group {ext_pg_name}', conf) + def test_bgp_99_bmp(self): target_name = 'instance-bmp' target_address = '127.0.0.1' diff --git a/smoketest/scripts/cli/test_service_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py index abf40cd3b..46c4e25a1 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/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index 512fa26e9..2b16de775 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -285,6 +285,7 @@ def verify(bgp): elif tmp != 'default': raise ConfigError(f'{error_msg} "{tmp}"!') + peer_groups_context = dict() # Common verification for both peer-group and neighbor statements for neighbor in ['neighbor', 'peer_group']: # bail out early if there is no neighbor or peer-group statement @@ -301,6 +302,18 @@ def verify(bgp): raise ConfigError(f'Specified peer-group "{peer_group}" for '\ f'neighbor "{neighbor}" does not exist!') + if 'remote_as' in peer_config: + is_ibgp = True + if peer_config['remote_as'] != 'internal' and \ + peer_config['remote_as'] != bgp['system_as']: + is_ibgp = False + + if peer_group not in peer_groups_context: + peer_groups_context[peer_group] = is_ibgp + elif peer_groups_context[peer_group] != is_ibgp: + raise ConfigError(f'Peer-group members must be ' + f'all internal or all external') + if 'local_role' in peer_config: #Ensure Local Role has only one value. if len(peer_config['local_role']) > 1: diff --git a/src/conf_mode/service_dhcp-server.py b/src/conf_mode/service_dhcp-server.py index bf4454fda..f4fb78f57 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 |