From 2fcd45e225370a508d7bc0c560cc0fe95656c26f Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sun, 6 Oct 2024 15:03:52 +0200 Subject: [PATCH 01/28] Debian: T6746: bump required FRR version to >= 10.2 --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index a194614121..76ca83dcd4 100644 --- a/debian/control +++ b/debian/control @@ -166,7 +166,7 @@ Depends: sstp-client, # End "interfaces sstpc" # For "protocols *" - frr (>= 9.1), + frr (>= 10.2), frr-pythontools, frr-rpki-rtrlib, frr-snmp, From 7538fd4c518271a517b6753dd664fd07cd86bddd Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sun, 6 Oct 2024 15:05:14 +0200 Subject: [PATCH 02/28] rpki: T6747: adjust to new FRR cli interface --- data/templates/frr/rpki.frr.j2 | 4 ++-- smoketest/scripts/cli/test_protocols_rpki.py | 9 ++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/data/templates/frr/rpki.frr.j2 b/data/templates/frr/rpki.frr.j2 index 59724102cb..59d5bf0ac0 100644 --- a/data/templates/frr/rpki.frr.j2 +++ b/data/templates/frr/rpki.frr.j2 @@ -5,9 +5,9 @@ rpki {% for peer, peer_config in cache.items() %} {# port is mandatory and preference uses a default value #} {% if peer_config.ssh.username is vyos_defined %} - rpki cache {{ peer | replace('_', '-') }} {{ peer_config.port }} {{ peer_config.ssh.username }} {{ peer_config.ssh.private_key_file }} {{ peer_config.ssh.public_key_file }} preference {{ peer_config.preference }} + rpki cache ssh {{ peer | replace('_', '-') }} {{ peer_config.port }} {{ peer_config.ssh.username }} {{ peer_config.ssh.private_key_file }} {{ peer_config.ssh.public_key_file }} preference {{ peer_config.preference }} {% else %} - rpki cache {{ peer | replace('_', '-') }} {{ peer_config.port }} preference {{ peer_config.preference }} + rpki cache tcp {{ peer | replace('_', '-') }} {{ peer_config.port }} preference {{ peer_config.preference }} {% endif %} {% endfor %} {% endif %} diff --git a/smoketest/scripts/cli/test_protocols_rpki.py b/smoketest/scripts/cli/test_protocols_rpki.py index 29f03a26a5..c6a6adf58e 100755 --- a/smoketest/scripts/cli/test_protocols_rpki.py +++ b/smoketest/scripts/cli/test_protocols_rpki.py @@ -117,6 +117,9 @@ def tearDown(self): self.cli_delete(base_path) self.cli_commit() + frrconfig = self.getFRRconfig('rpki') + self.assertNotIn(f'rpki', frrconfig) + # check process health and continuity self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) @@ -159,7 +162,7 @@ def test_rpki(self): for peer, peer_config in cache.items(): port = peer_config['port'] preference = peer_config['preference'] - self.assertIn(f'rpki cache {peer} {port} preference {preference}', frrconfig) + self.assertIn(f'rpki cache tcp {peer} {port} preference {preference}', frrconfig) def test_rpki_ssh(self): polling = '7200' @@ -195,7 +198,7 @@ def test_rpki_ssh(self): port = cache_config['port'] preference = cache_config['preference'] username = cache_config['username'] - self.assertIn(f'rpki cache {cache_name} {port} {username} /run/frr/id_rpki_{cache_name} /run/frr/id_rpki_{cache_name}.pub preference {preference}', frrconfig) + self.assertIn(f'rpki cache ssh {cache_name} {port} {username} /run/frr/id_rpki_{cache_name} /run/frr/id_rpki_{cache_name}.pub preference {preference}', frrconfig) # Verify content of SSH keys tmp = read_file(f'/run/frr/id_rpki_{cache_name}') @@ -213,7 +216,7 @@ def test_rpki_ssh(self): port = cache_config['port'] preference = cache_config['preference'] username = cache_config['username'] - self.assertIn(f'rpki cache {cache_name} {port} {username} /run/frr/id_rpki_{cache_name} /run/frr/id_rpki_{cache_name}.pub preference {preference}', frrconfig) + self.assertIn(f'rpki cache ssh {cache_name} {port} {username} /run/frr/id_rpki_{cache_name} /run/frr/id_rpki_{cache_name}.pub preference {preference}', frrconfig) # Verify content of SSH keys tmp = read_file(f'/run/frr/id_rpki_{cache_name}') From 81923562a8a164f7ff9761e976c20420a585907a Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sun, 6 Oct 2024 15:17:17 +0200 Subject: [PATCH 03/28] frr: T6747: make daemon definitions re-usable for both conf-mode and smoketests --- python/vyos/frr.py | 21 ++++++++++- .../scripts/cli/test_interfaces_bonding.py | 5 ++- .../scripts/cli/test_interfaces_ethernet.py | 3 +- smoketest/scripts/cli/test_protocols_bgp.py | 5 ++- smoketest/scripts/cli/test_protocols_isis.py | 37 ++++++++++--------- .../scripts/cli/test_protocols_openfabric.py | 21 ++++++----- .../cli/test_protocols_segment-routing.py | 3 +- smoketest/scripts/cli/test_system_ip.py | 9 +++-- smoketest/scripts/cli/test_system_ipv6.py | 9 +++-- smoketest/scripts/cli/test_vrf.py | 35 +++++++++--------- src/conf_mode/interfaces_bonding.py | 5 +-- src/conf_mode/interfaces_ethernet.py | 7 +--- src/conf_mode/policy.py | 11 ++---- src/conf_mode/protocols_babel.py | 6 +-- src/conf_mode/protocols_bfd.py | 6 +-- src/conf_mode/protocols_bgp.py | 6 +-- src/conf_mode/protocols_eigrp.py | 8 ++-- src/conf_mode/protocols_isis.py | 6 +-- src/conf_mode/protocols_mpls.py | 6 +-- src/conf_mode/protocols_openfabric.py | 6 +-- src/conf_mode/protocols_ospf.py | 6 +-- src/conf_mode/protocols_ospfv3.py | 6 +-- src/conf_mode/protocols_pim.py | 7 ++-- src/conf_mode/protocols_pim6.py | 7 +--- src/conf_mode/protocols_rip.py | 12 ++---- src/conf_mode/protocols_ripng.py | 13 +------ src/conf_mode/protocols_rpki.py | 6 +-- src/conf_mode/protocols_segment-routing.py | 6 +-- src/conf_mode/protocols_static.py | 6 +-- src/conf_mode/protocols_static_multicast.py | 6 +-- src/conf_mode/service_snmp.py | 4 +- src/conf_mode/system_ip.py | 5 +-- src/conf_mode/system_ipv6.py | 9 ++--- src/conf_mode/vrf.py | 8 +--- 34 files changed, 142 insertions(+), 174 deletions(-) diff --git a/python/vyos/frr.py b/python/vyos/frr.py index 6fb81803f8..183805e135 100644 --- a/python/vyos/frr.py +++ b/python/vyos/frr.py @@ -86,8 +86,25 @@ LOG.addHandler(ch) LOG.addHandler(ch2) -_frr_daemons = ['zebra', 'staticd', 'bgpd', 'ospfd', 'ospf6d', 'ripd', 'ripngd', - 'isisd', 'pimd', 'pim6d', 'ldpd', 'eigrpd', 'babeld', 'bfdd', 'fabricd'] +babel_daemon = 'babeld' +bfd_daemon = 'bfdd' +bgp_daemon = 'bgpd' +eigrp_daemon = 'eigrpd' +isis_daemon = 'isisd' +ldpd_daemon = 'ldpd' +mgmt_daemon = 'mgmtd' +openfabric_daemon = 'fabricd' +ospf_daemon = 'ospfd' +ospf6_daemon = 'ospf6d' +pim_daemon = 'pimd' +pim6_daemon = 'pim6d' +rip_daemon = 'ripd' +ripng_daemon = 'ripngd' +static_daemon = 'staticd' +zebra_daemon = 'zebra' + +_frr_daemons = [zebra_daemon, static_daemon, bgp_daemon, ospf_daemon, ospf6_daemon, rip_daemon, ripng_daemon, mgmt_daemon, + isis_daemon, pim_daemon, pim6_daemon, ldpd_daemon, eigrp_daemon, babel_daemon, bfd_daemon, openfabric_daemon] path_vtysh = '/usr/bin/vtysh' path_frr_reload = '/usr/lib/frr/frr-reload.py' diff --git a/smoketest/scripts/cli/test_interfaces_bonding.py b/smoketest/scripts/cli/test_interfaces_bonding.py index f436424b87..4187447127 100755 --- a/smoketest/scripts/cli/test_interfaces_bonding.py +++ b/smoketest/scripts/cli/test_interfaces_bonding.py @@ -24,6 +24,7 @@ from vyos.configsession import ConfigSessionError from vyos.utils.network import get_interface_config from vyos.utils.file import read_file +from vyos.frr import mgmt_daemon class BondingInterfaceTest(BasicInterfaceTest.TestCase): @classmethod @@ -286,7 +287,7 @@ def test_bonding_evpn_multihoming(self): id = '5' for interface in self._interfaces: - frrconfig = self.getFRRconfig(f'interface {interface}', daemon='zebra') + frrconfig = self.getFRRconfig(f'interface {interface}', daemon=mgmt_daemon) self.assertIn(f' evpn mh es-id {id}', frrconfig) self.assertIn(f' evpn mh es-df-pref {id}', frrconfig) @@ -303,7 +304,7 @@ def test_bonding_evpn_multihoming(self): id = '5' for interface in self._interfaces: - frrconfig = self.getFRRconfig(f'interface {interface}', daemon='zebra') + frrconfig = self.getFRRconfig(f'interface {interface}', daemon=mgmt_daemon) self.assertIn(f' evpn mh es-sys-mac 00:12:34:56:78:0{id}', frrconfig) self.assertIn(f' evpn mh uplink', frrconfig) diff --git a/smoketest/scripts/cli/test_interfaces_ethernet.py b/smoketest/scripts/cli/test_interfaces_ethernet.py index 3d12364f70..218fa0759c 100755 --- a/smoketest/scripts/cli/test_interfaces_ethernet.py +++ b/smoketest/scripts/cli/test_interfaces_ethernet.py @@ -27,6 +27,7 @@ from base_interfaces_test import BasicInterfaceTest from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section +from vyos.frr import mgmt_daemon from vyos.utils.process import cmd from vyos.utils.process import popen from vyos.utils.file import read_file @@ -219,7 +220,7 @@ def test_ethtool_evpn_uplink_tarcking(self): self.cli_commit() for interface in self._interfaces: - frrconfig = self.getFRRconfig(f'interface {interface}', daemon='zebra') + frrconfig = self.getFRRconfig(f'interface {interface}', daemon=mgmt_daemon) self.assertIn(f' evpn mh uplink', frrconfig) if __name__ == '__main__': diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index ea2f561a4d..ad9ce36760 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021-2023 VyOS maintainers and contributors +# Copyright (C) 2021-2024 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 @@ -25,6 +25,7 @@ from vyos.template import is_ipv6 from vyos.utils.process import process_named_running from vyos.utils.process import cmd +from vyos.frr import bgp_daemon PROCESS_NAME = 'bgpd' ASN = '64512' @@ -960,7 +961,7 @@ def test_bgp_13_vpn(self): self.assertIn(f'router bgp {ASN}', frrconfig) for afi in ['ipv4', 'ipv6']: - afi_config = self.getFRRconfig(f' address-family {afi} unicast', endsection='exit-address-family', daemon='bgpd') + afi_config = self.getFRRconfig(f' address-family {afi} unicast', endsection='exit-address-family', daemon=bgp_daemon) self.assertIn(f'address-family {afi} unicast', afi_config) self.assertIn(f' export vpn', afi_config) self.assertIn(f' import vpn', afi_config) diff --git a/smoketest/scripts/cli/test_protocols_isis.py b/smoketest/scripts/cli/test_protocols_isis.py index 769f3dd33a..5b86dd53af 100755 --- a/smoketest/scripts/cli/test_protocols_isis.py +++ b/smoketest/scripts/cli/test_protocols_isis.py @@ -20,6 +20,7 @@ from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section from vyos.utils.process import process_named_running +from vyos.frr import isis_daemon PROCESS_NAME = 'isisd' base_path = ['protocols', 'isis'] @@ -88,14 +89,14 @@ def test_isis_01_redistribute(self): self.cli_commit() # Verify all changes - tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') + tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon) self.assertIn(f' net {net}', tmp) self.assertIn(f' metric-style {metric_style}', tmp) self.assertIn(f' log-adjacency-changes', tmp) self.assertIn(f' redistribute ipv4 connected level-2 route-map {route_map}', tmp) for interface in self._interfaces: - tmp = self.getFRRconfig(f'interface {interface}', daemon='isisd') + tmp = self.getFRRconfig(f'interface {interface}', daemon=isis_daemon) self.assertIn(f' ip router isis {domain}', tmp) self.assertIn(f' ipv6 router isis {domain}', tmp) @@ -124,11 +125,11 @@ def test_isis_02_vrfs(self): self.cli_commit() # Verify FRR isisd configuration - tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') + tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon) self.assertIn(f'router isis {domain}', tmp) self.assertIn(f' net {net}', tmp) - tmp = self.getFRRconfig(f'router isis {domain} vrf {vrf}', daemon='isisd') + tmp = self.getFRRconfig(f'router isis {domain} vrf {vrf}', daemon=isis_daemon) self.assertIn(f'router isis {domain} vrf {vrf}', tmp) self.assertIn(f' net {net}', tmp) self.assertIn(f' advertise-high-metrics', tmp) @@ -152,7 +153,7 @@ def test_isis_04_default_information(self): self.cli_commit() # Verify all changes - tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') + tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon) self.assertIn(f' net {net}', tmp) for afi in ['ipv4', 'ipv6']: @@ -187,13 +188,13 @@ def test_isis_05_password(self): self.cli_commit() # Verify all changes - tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') + tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon) self.assertIn(f' net {net}', tmp) self.assertIn(f' domain-password clear {password}', tmp) self.assertIn(f' area-password clear {password}', tmp) for interface in self._interfaces: - tmp = self.getFRRconfig(f'interface {interface}', daemon='isisd') + tmp = self.getFRRconfig(f'interface {interface}', daemon=isis_daemon) self.assertIn(f' isis password clear {password}-{interface}', tmp) def test_isis_06_spf_delay_bfd(self): @@ -235,12 +236,12 @@ def test_isis_06_spf_delay_bfd(self): self.cli_commit() # Verify all changes - tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') + tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon) self.assertIn(f' net {net}', tmp) self.assertIn(f' spf-delay-ietf init-delay {init_delay} short-delay {short_delay} long-delay {long_delay} holddown {holddown} time-to-learn {time_to_learn}', tmp) for interface in self._interfaces: - tmp = self.getFRRconfig(f'interface {interface}', daemon='isisd') + tmp = self.getFRRconfig(f'interface {interface}', daemon=isis_daemon) self.assertIn(f' ip router isis {domain}', tmp) self.assertIn(f' ipv6 router isis {domain}', tmp) self.assertIn(f' isis network {network}', tmp) @@ -283,7 +284,7 @@ def test_isis_07_segment_routing_configuration(self): self.cli_commit() # Verify all changes - tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') + tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon) self.assertIn(f' net {net}', tmp) self.assertIn(f' segment-routing on', tmp) self.assertIn(f' segment-routing global-block {global_block_low} {global_block_high} local-block {local_block_low} {local_block_high}', tmp) @@ -305,7 +306,7 @@ def test_isis_08_ldp_sync(self): self.cli_commit() # Verify main ISIS changes - tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') + tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon) self.assertIn(f' net {net}', tmp) self.assertIn(f' mpls ldp-sync', tmp) self.assertIn(f' mpls ldp-sync holddown {holddown}', tmp) @@ -318,7 +319,7 @@ def test_isis_08_ldp_sync(self): for interface in self._interfaces: # Verify interface changes for holddown - tmp = self.getFRRconfig(f'interface {interface}', daemon='isisd') + tmp = self.getFRRconfig(f'interface {interface}', daemon=isis_daemon) self.assertIn(f'interface {interface}', tmp) self.assertIn(f' ip router isis {domain}', tmp) self.assertIn(f' ipv6 router isis {domain}', tmp) @@ -332,7 +333,7 @@ def test_isis_08_ldp_sync(self): for interface in self._interfaces: # Verify interface changes for disable - tmp = self.getFRRconfig(f'interface {interface}', daemon='isisd') + tmp = self.getFRRconfig(f'interface {interface}', daemon=isis_daemon) self.assertIn(f'interface {interface}', tmp) self.assertIn(f' ip router isis {domain}', tmp) self.assertIn(f' ipv6 router isis {domain}', tmp) @@ -355,7 +356,7 @@ def test_isis_09_lfa(self): for level in ['level-1', 'level-2']: self.cli_set(base_path + ['fast-reroute', 'lfa', 'remote', 'prefix-list', prefix_list, level]) self.cli_commit() - tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') + tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon) self.assertIn(f' net {net}', tmp) self.assertIn(f' fast-reroute remote-lfa prefix-list {prefix_list} {level}', tmp) self.cli_delete(base_path + ['fast-reroute']) @@ -365,7 +366,7 @@ def test_isis_09_lfa(self): for level in ['level-1', 'level-2']: self.cli_set(base_path + ['fast-reroute', 'lfa', 'local', 'load-sharing', 'disable', level]) self.cli_commit() - tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') + tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon) self.assertIn(f' net {net}', tmp) self.assertIn(f' fast-reroute load-sharing disable {level}', tmp) self.cli_delete(base_path + ['fast-reroute']) @@ -376,7 +377,7 @@ def test_isis_09_lfa(self): for level in ['level-1', 'level-2']: self.cli_set(base_path + ['fast-reroute', 'lfa', 'local', 'priority-limit', priority, level]) self.cli_commit() - tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') + tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon) self.assertIn(f' net {net}', tmp) self.assertIn(f' fast-reroute priority-limit {priority} {level}', tmp) self.cli_delete(base_path + ['fast-reroute']) @@ -388,7 +389,7 @@ def test_isis_09_lfa(self): for level in ['level-1', 'level-2']: self.cli_set(base_path + ['fast-reroute', 'lfa', 'local', 'tiebreaker', tiebreaker, 'index', index, level]) self.cli_commit() - tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') + tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon) self.assertIn(f' net {net}', tmp) self.assertIn(f' fast-reroute lfa tiebreaker {tiebreaker} index {index} {level}', tmp) self.cli_delete(base_path + ['fast-reroute']) @@ -408,7 +409,7 @@ def test_isis_10_topology(self): for topology in topologies: self.cli_set(base_path + ['topology', topology]) self.cli_commit() - tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') + tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon) self.assertIn(f' net {net}', tmp) self.assertIn(f' topology {topology}', tmp) diff --git a/smoketest/scripts/cli/test_protocols_openfabric.py b/smoketest/scripts/cli/test_protocols_openfabric.py index e37aed456a..889cba1358 100644 --- a/smoketest/scripts/cli/test_protocols_openfabric.py +++ b/smoketest/scripts/cli/test_protocols_openfabric.py @@ -19,6 +19,7 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError from vyos.utils.process import process_named_running +from vyos.frr import openfabric_daemon PROCESS_NAME = 'fabricd' base_path = ['protocols', 'openfabric'] @@ -75,14 +76,14 @@ def test_openfabric_01_router_params(self): self.cli_commit() # Verify all changes - tmp = self.getFRRconfig(f'router openfabric {domain}', daemon='fabricd') + tmp = self.getFRRconfig(f'router openfabric {domain}', daemon=openfabric_daemon) self.assertIn(f' net {net}', tmp) self.assertIn(f' log-adjacency-changes', tmp) self.assertIn(f' set-overload-bit', tmp) self.assertIn(f' fabric-tier {fabric_tier}', tmp) self.assertIn(f' lsp-gen-interval {lsp_gen_interval}', tmp) - tmp = self.getFRRconfig(f'interface {dummy_if}', daemon='fabricd') + tmp = self.getFRRconfig(f'interface {dummy_if}', daemon=openfabric_daemon) self.assertIn(f' ip router openfabric {domain}', tmp) self.assertIn(f' ipv6 router openfabric {domain}', tmp) @@ -101,12 +102,12 @@ def test_openfabric_02_loopback_interface(self): self.cli_commit() # Verify FRR openfabric configuration - tmp = self.getFRRconfig(f'router openfabric {domain}', daemon='fabricd') + tmp = self.getFRRconfig(f'router openfabric {domain}', daemon=openfabric_daemon) self.assertIn(f'router openfabric {domain}', tmp) self.assertIn(f' net {net}', tmp) # Verify interface configuration - tmp = self.getFRRconfig(f'interface {interface}', daemon='fabricd') + tmp = self.getFRRconfig(f'interface {interface}', daemon=openfabric_daemon) self.assertIn(f' ip router openfabric {domain}', tmp) # for lo interface 'openfabric passive' is implied self.assertIn(f' openfabric passive', tmp) @@ -137,11 +138,11 @@ def test_openfabric_03_password(self): self.cli_commit() # Verify all changes - tmp = self.getFRRconfig(f'router openfabric {domain}', daemon='fabricd') + tmp = self.getFRRconfig(f'router openfabric {domain}', daemon=openfabric_daemon) self.assertIn(f' net {net}', tmp) self.assertIn(f' domain-password clear {password}', tmp) - tmp = self.getFRRconfig(f'interface {dummy_if}', daemon='fabricd') + tmp = self.getFRRconfig(f'interface {dummy_if}', daemon=openfabric_daemon) self.assertIn(f' openfabric password clear {password}-{dummy_if}', tmp) def test_openfabric_multiple_domains(self): @@ -165,20 +166,20 @@ def test_openfabric_multiple_domains(self): self.cli_commit() # Verify FRR openfabric configuration - tmp = self.getFRRconfig(f'router openfabric {domain}', daemon='fabricd') + tmp = self.getFRRconfig(f'router openfabric {domain}', daemon=openfabric_daemon) self.assertIn(f'router openfabric {domain}', tmp) self.assertIn(f' net {net}', tmp) - tmp = self.getFRRconfig(f'router openfabric {domain_2}', daemon='fabricd') + tmp = self.getFRRconfig(f'router openfabric {domain_2}', daemon=openfabric_daemon) self.assertIn(f'router openfabric {domain_2}', tmp) self.assertIn(f' net {net}', tmp) # Verify interface configuration - tmp = self.getFRRconfig(f'interface {dummy_if}', daemon='fabricd') + tmp = self.getFRRconfig(f'interface {dummy_if}', daemon=openfabric_daemon) self.assertIn(f' ip router openfabric {domain}', tmp) self.assertIn(f' ipv6 router openfabric {domain}', tmp) - tmp = self.getFRRconfig(f'interface {interface}', daemon='fabricd') + tmp = self.getFRRconfig(f'interface {interface}', daemon=openfabric_daemon) self.assertIn(f' ip router openfabric {domain_2}', tmp) diff --git a/smoketest/scripts/cli/test_protocols_segment-routing.py b/smoketest/scripts/cli/test_protocols_segment-routing.py index daa7f088f2..eb563db930 100755 --- a/smoketest/scripts/cli/test_protocols_segment-routing.py +++ b/smoketest/scripts/cli/test_protocols_segment-routing.py @@ -20,6 +20,7 @@ from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section +from vyos.frr import zebra_daemon from vyos.utils.process import process_named_running from vyos.utils.system import sysctl_read @@ -68,7 +69,7 @@ def test_srv6(self): self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_enabled'), '1') self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_require_hmac'), '0') # default - frrconfig = self.getFRRconfig(f'segment-routing', daemon='zebra') + frrconfig = self.getFRRconfig(f'segment-routing', daemon=zebra_daemon) self.assertIn(f'segment-routing', frrconfig) self.assertIn(f' srv6', frrconfig) self.assertIn(f' locators', frrconfig) diff --git a/smoketest/scripts/cli/test_system_ip.py b/smoketest/scripts/cli/test_system_ip.py index 5b00902370..4ab5e81812 100755 --- a/smoketest/scripts/cli/test_system_ip.py +++ b/smoketest/scripts/cli/test_system_ip.py @@ -19,6 +19,7 @@ from vyos.configsession import ConfigSessionError from vyos.utils.file import read_file +from vyos.frr import mgmt_daemon base_path = ['system', 'ip'] @@ -83,7 +84,7 @@ def test_system_ip_protocol_route_map(self): self.cli_commit() # Verify route-map properly applied to FRR - frrconfig = self.getFRRconfig('ip protocol', end='', daemon='zebra') + frrconfig = self.getFRRconfig('ip protocol', end='', daemon=mgmt_daemon) for protocol in protocols: self.assertIn(f'ip protocol {protocol} route-map route-map-{protocol}', frrconfig) @@ -94,7 +95,7 @@ def test_system_ip_protocol_route_map(self): self.cli_commit() # Verify route-map properly applied to FRR - frrconfig = self.getFRRconfig('ip protocol', end='', daemon='zebra') + frrconfig = self.getFRRconfig('ip protocol', end='', daemon=mgmt_daemon) self.assertNotIn(f'ip protocol', frrconfig) def test_system_ip_protocol_non_existing_route_map(self): @@ -113,13 +114,13 @@ def test_system_ip_nht(self): self.cli_set(base_path + ['nht', 'no-resolve-via-default']) self.cli_commit() # Verify CLI config applied to FRR - frrconfig = self.getFRRconfig('', end='', daemon='zebra') + frrconfig = self.getFRRconfig('', end='', daemon=mgmt_daemon) self.assertIn(f'no ip nht resolve-via-default', frrconfig) self.cli_delete(base_path + ['nht', 'no-resolve-via-default']) self.cli_commit() # Verify CLI config removed to FRR - frrconfig = self.getFRRconfig('', end='', daemon='zebra') + frrconfig = self.getFRRconfig('', end='', daemon=mgmt_daemon) self.assertNotIn(f'no ip nht resolve-via-default', frrconfig) if __name__ == '__main__': diff --git a/smoketest/scripts/cli/test_system_ipv6.py b/smoketest/scripts/cli/test_system_ipv6.py index 0c77c1dd48..1c778a11dd 100755 --- a/smoketest/scripts/cli/test_system_ipv6.py +++ b/smoketest/scripts/cli/test_system_ipv6.py @@ -20,6 +20,7 @@ from vyos.configsession import ConfigSessionError from vyos.utils.file import read_file +from vyos.frr import mgmt_daemon base_path = ['system', 'ipv6'] @@ -99,7 +100,7 @@ def test_system_ipv6_protocol_route_map(self): self.cli_commit() # Verify route-map properly applied to FRR - frrconfig = self.getFRRconfig('ipv6 protocol', end='', daemon='zebra') + frrconfig = self.getFRRconfig('ipv6 protocol', end='', daemon=mgmt_daemon) for protocol in protocols: # VyOS and FRR use a different name for OSPFv3 (IPv6) if protocol == 'ospfv3': @@ -113,7 +114,7 @@ def test_system_ipv6_protocol_route_map(self): self.cli_commit() # Verify route-map properly applied to FRR - frrconfig = self.getFRRconfig('ipv6 protocol', end='', daemon='zebra') + frrconfig = self.getFRRconfig('ipv6 protocol', end='', daemon=mgmt_daemon) self.assertNotIn(f'ipv6 protocol', frrconfig) def test_system_ipv6_protocol_non_existing_route_map(self): @@ -132,13 +133,13 @@ def test_system_ipv6_nht(self): self.cli_set(base_path + ['nht', 'no-resolve-via-default']) self.cli_commit() # Verify CLI config applied to FRR - frrconfig = self.getFRRconfig('', end='', daemon='zebra') + frrconfig = self.getFRRconfig('', end='', daemon=mgmt_daemon) self.assertIn(f'no ipv6 nht resolve-via-default', frrconfig) self.cli_delete(base_path + ['nht', 'no-resolve-via-default']) self.cli_commit() # Verify CLI config removed to FRR - frrconfig = self.getFRRconfig('', end='', daemon='zebra') + frrconfig = self.getFRRconfig('', end='', daemon=mgmt_daemon) self.assertNotIn(f'no ipv6 nht resolve-via-default', frrconfig) if __name__ == '__main__': diff --git a/smoketest/scripts/cli/test_vrf.py b/smoketest/scripts/cli/test_vrf.py index 2bb6c91c14..3cab5248e7 100755 --- a/smoketest/scripts/cli/test_vrf.py +++ b/smoketest/scripts/cli/test_vrf.py @@ -23,6 +23,7 @@ from jmespath import search from vyos.configsession import ConfigSessionError +from vyos.frr import mgmt_daemon from vyos.ifconfig import Interface from vyos.ifconfig import Section from vyos.utils.file import read_file @@ -112,7 +113,7 @@ def test_vrf_vni_and_table_id(self): regex = f'{table}\s+{vrf}\s+#\s+{description}' self.assertTrue(re.findall(regex, iproute2_config)) - frrconfig = self.getFRRconfig(f'vrf {vrf}') + frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon) self.assertIn(f' vni {table}', frrconfig) self.assertEqual(int(table), get_vrf_tableid(vrf)) @@ -233,7 +234,7 @@ def test_vrf_static_route(self): self.assertTrue(interface_exists(vrf)) - frrconfig = self.getFRRconfig(f'vrf {vrf}') + frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon) self.assertIn(f' vni {table}', frrconfig) self.assertIn(f' ip route {prefix} {next_hop}', frrconfig) @@ -317,7 +318,7 @@ def test_vrf_ip_protocol_route_map(self): # Verify route-map properly applied to FRR for vrf in vrfs: - frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon='zebra') + frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon) self.assertIn(f'vrf {vrf}', frrconfig) for protocol in v4_protocols: self.assertIn(f' ip protocol {protocol} route-map route-map-{vrf}-{protocol}', frrconfig) @@ -332,8 +333,8 @@ def test_vrf_ip_protocol_route_map(self): # Verify route-map properly is removed from FRR for vrf in vrfs: - frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon='zebra') - self.assertNotIn(f'vrf {vrf}', frrconfig) + frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon) + self.assertNotIn(f' ip protocol', frrconfig) def test_vrf_ip_ipv6_protocol_non_existing_route_map(self): table = '6100' @@ -380,7 +381,7 @@ def test_vrf_ipv6_protocol_route_map(self): # Verify route-map properly applied to FRR for vrf in vrfs: - frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon='zebra') + frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon) self.assertIn(f'vrf {vrf}', frrconfig) for protocol in v6_protocols: # VyOS and FRR use a different name for OSPFv3 (IPv6) @@ -399,8 +400,8 @@ def test_vrf_ipv6_protocol_route_map(self): # Verify route-map properly is removed from FRR for vrf in vrfs: - frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon='zebra') - self.assertNotIn(f'vrf {vrf}', frrconfig) + frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon) + self.assertNotIn(f' ipv6 protocol', frrconfig) def test_vrf_vni_duplicates(self): base_table = '6300' @@ -429,7 +430,7 @@ def test_vrf_vni_duplicates(self): for vrf in vrfs: self.assertTrue(interface_exists(vrf)) - frrconfig = self.getFRRconfig(f'vrf {vrf}') + frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon) self.assertIn(f' vni {table}', frrconfig) # Increment table ID for the next run table = str(int(table) + 1) @@ -451,7 +452,7 @@ def test_vrf_vni_add_change_remove(self): for vrf in vrfs: self.assertTrue(interface_exists(vrf)) - frrconfig = self.getFRRconfig(f'vrf {vrf}') + frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon) self.assertIn(f' vni {table}', frrconfig) # Increment table ID for the next run table = str(int(table) + 1) @@ -474,7 +475,7 @@ def test_vrf_vni_add_change_remove(self): for vrf in vrfs: self.assertTrue(interface_exists(vrf)) - frrconfig = self.getFRRconfig(f'vrf {vrf}') + frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon) self.assertIn(f' vni {table}', frrconfig) # Increment table ID for the next run table = str(int(table) + 2) @@ -494,7 +495,7 @@ def test_vrf_vni_add_change_remove(self): for vrf in vrfs: self.assertTrue(interface_exists(vrf)) - frrconfig = self.getFRRconfig(f'vrf {vrf}') + frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon) self.assertIn(f' vni {table}', frrconfig) # Increment table ID for the next run table = str(int(table) + 2) @@ -502,7 +503,7 @@ def test_vrf_vni_add_change_remove(self): # Verify purple VRF/VNI self.assertTrue(interface_exists(purple)) table = str(int(table) + 10) - frrconfig = self.getFRRconfig(f'vrf {purple}') + frrconfig = self.getFRRconfig(f'vrf {purple}', daemon=mgmt_daemon) self.assertIn(f' vni {table}', frrconfig) # Now delete all the VNIs @@ -517,12 +518,12 @@ def test_vrf_vni_add_change_remove(self): for vrf in vrfs: self.assertTrue(interface_exists(vrf)) - frrconfig = self.getFRRconfig(f'vrf {vrf}') + frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon) self.assertNotIn('vni', frrconfig) # Verify purple VNI remains self.assertTrue(interface_exists(purple)) - frrconfig = self.getFRRconfig(f'vrf {purple}') + frrconfig = self.getFRRconfig(f'vrf {purple}', daemon=mgmt_daemon) self.assertIn(f' vni {table}', frrconfig) def test_vrf_ip_ipv6_nht(self): @@ -540,7 +541,7 @@ def test_vrf_ip_ipv6_nht(self): # Verify route-map properly applied to FRR for vrf in vrfs: - frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon='zebra') + frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon) self.assertIn(f'vrf {vrf}', frrconfig) self.assertIn(f' no ip nht resolve-via-default', frrconfig) self.assertIn(f' no ipv6 nht resolve-via-default', frrconfig) @@ -555,7 +556,7 @@ def test_vrf_ip_ipv6_nht(self): # Verify route-map properly is removed from FRR for vrf in vrfs: - frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon='zebra') + frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon) self.assertNotIn(f' no ip nht resolve-via-default', frrconfig) self.assertNotIn(f' no ipv6 nht resolve-via-default', frrconfig) diff --git a/src/conf_mode/interfaces_bonding.py b/src/conf_mode/interfaces_bonding.py index bbbfb03853..633fb797c9 100755 --- a/src/conf_mode/interfaces_bonding.py +++ b/src/conf_mode/interfaces_bonding.py @@ -281,16 +281,15 @@ def apply(bond): raise ConfigError('Error in updating ethernet interface ' 'after deleting it from bond') - zebra_daemon = 'zebra' # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() # The route-map used for the FIB (zebra) is part of the zebra daemon - frr_cfg.load_configuration(zebra_daemon) + frr_cfg.load_configuration(frr.mgmt_daemon) frr_cfg.modify_section(f'^interface {ifname}', stop_pattern='^exit', remove_stop_mark=True) if 'frr_zebra_config' in bond: frr_cfg.add_before(frr.default_add_before, bond['frr_zebra_config']) - frr_cfg.commit_configuration(zebra_daemon) + frr_cfg.commit_configuration() return None diff --git a/src/conf_mode/interfaces_ethernet.py b/src/conf_mode/interfaces_ethernet.py index 34ce7bc47b..edbbb00c91 100755 --- a/src/conf_mode/interfaces_ethernet.py +++ b/src/conf_mode/interfaces_ethernet.py @@ -337,16 +337,13 @@ def apply(ethernet): else: e.update(ethernet) - zebra_daemon = 'zebra' # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() - - # The route-map used for the FIB (zebra) is part of the zebra daemon - frr_cfg.load_configuration(zebra_daemon) + frr_cfg.load_configuration(frr.mgmt_daemon) frr_cfg.modify_section(f'^interface {ifname}', stop_pattern='^exit', remove_stop_mark=True) if 'frr_zebra_config' in ethernet: frr_cfg.add_before(frr.default_add_before, ethernet['frr_zebra_config']) - frr_cfg.commit_configuration(zebra_daemon) + frr_cfg.commit_configuration() if __name__ == '__main__': try: diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py index a5963e72c3..aef9b96c4a 100755 --- a/src/conf_mode/policy.py +++ b/src/conf_mode/policy.py @@ -279,14 +279,11 @@ def generate(policy): def apply(policy): - bgp_daemon = 'bgpd' - zebra_daemon = 'zebra' - # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() # The route-map used for the FIB (zebra) is part of the zebra daemon - frr_cfg.load_configuration(bgp_daemon) + frr_cfg.load_configuration(frr.bgp_daemon) frr_cfg.modify_section(r'^bgp as-path access-list .*') frr_cfg.modify_section(r'^bgp community-list .*') frr_cfg.modify_section(r'^bgp extcommunity-list .*') @@ -295,10 +292,10 @@ def apply(policy): remove_stop_mark=True) if 'new_frr_config' in policy: frr_cfg.add_before(frr.default_add_before, policy['new_frr_config']) - frr_cfg.commit_configuration(bgp_daemon) + frr_cfg.commit_configuration(frr.bgp_daemon) # The route-map used for the FIB (zebra) is part of the zebra daemon - frr_cfg.load_configuration(zebra_daemon) + frr_cfg.load_configuration(frr.zebra_daemon) frr_cfg.modify_section(r'^access-list .*') frr_cfg.modify_section(r'^ipv6 access-list .*') frr_cfg.modify_section(r'^ip prefix-list .*') @@ -307,7 +304,7 @@ def apply(policy): remove_stop_mark=True) if 'new_frr_config' in policy: frr_cfg.add_before(frr.default_add_before, policy['new_frr_config']) - frr_cfg.commit_configuration(zebra_daemon) + frr_cfg.commit_configuration(frr.zebra_daemon) return None diff --git a/src/conf_mode/protocols_babel.py b/src/conf_mode/protocols_babel.py index 90b6e4a318..06fd9b9b64 100755 --- a/src/conf_mode/protocols_babel.py +++ b/src/conf_mode/protocols_babel.py @@ -128,12 +128,10 @@ def generate(babel): return None def apply(babel): - babel_daemon = 'babeld' - # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(babel_daemon) + frr_cfg.load_configuration(frr.babel_daemon) frr_cfg.modify_section('^router babel', stop_pattern='^exit', remove_stop_mark=True) for key in ['interface', 'interface_removed']: @@ -144,7 +142,7 @@ def apply(babel): if 'new_frr_config' in babel: frr_cfg.add_before(frr.default_add_before, babel['new_frr_config']) - frr_cfg.commit_configuration(babel_daemon) + frr_cfg.commit_configuration(frr.babel_daemon) return None diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index 1361bb1a95..d94ec6a0d4 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -89,15 +89,13 @@ def generate(bfd): bfd['new_frr_config'] = render_to_string('frr/bfdd.frr.j2', bfd) def apply(bfd): - bfd_daemon = 'bfdd' - # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(bfd_daemon) + frr_cfg.load_configuration(frr.bfd_daemon) frr_cfg.modify_section('^bfd', stop_pattern='^exit', remove_stop_mark=True) if 'new_frr_config' in bfd: frr_cfg.add_before(frr.default_add_before, bfd['new_frr_config']) - frr_cfg.commit_configuration(bfd_daemon) + frr_cfg.commit_configuration(frr.bfd_daemon) return None diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index 22f0200992..e5c46aee66 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -617,8 +617,6 @@ def apply(bgp): if {'vrf', 'vni'} <= set(bgp): call('vtysh -c "conf t" -c "vrf {vrf}" -c "no vni {vni}"'.format(**bgp)) - bgp_daemon = 'bgpd' - # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() @@ -628,7 +626,7 @@ def apply(bgp): if 'vrf' in bgp: vrf = ' vrf ' + bgp['vrf'] - frr_cfg.load_configuration(bgp_daemon) + frr_cfg.load_configuration(frr.bgp_daemon) # Remove interface specific config for key in ['interface', 'interface_removed']: @@ -640,7 +638,7 @@ def apply(bgp): frr_cfg.modify_section(f'^router bgp \d+{vrf}', stop_pattern='^exit', remove_stop_mark=True) if 'frr_bgpd_config' in bgp: frr_cfg.add_before(frr.default_add_before, bgp['frr_bgpd_config']) - frr_cfg.commit_configuration(bgp_daemon) + frr_cfg.commit_configuration(frr.bgp_daemon) return None diff --git a/src/conf_mode/protocols_eigrp.py b/src/conf_mode/protocols_eigrp.py index c13e52a3d2..a948b47da2 100755 --- a/src/conf_mode/protocols_eigrp.py +++ b/src/conf_mode/protocols_eigrp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022 VyOS maintainers and contributors +# Copyright (C) 2022-2024 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 @@ -89,8 +89,6 @@ def generate(eigrp): eigrp['frr_eigrpd_config'] = render_to_string('frr/eigrpd.frr.j2', eigrp) def apply(eigrp): - eigrp_daemon = 'eigrpd' - # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() @@ -100,11 +98,11 @@ def apply(eigrp): if 'vrf' in eigrp: vrf = ' vrf ' + eigrp['vrf'] - frr_cfg.load_configuration(eigrp_daemon) + frr_cfg.load_configuration(frr.eigrp_daemon) frr_cfg.modify_section(f'^router eigrp \d+{vrf}', stop_pattern='^exit', remove_stop_mark=True) if 'frr_eigrpd_config' in eigrp: frr_cfg.add_before(frr.default_add_before, eigrp['frr_eigrpd_config']) - frr_cfg.commit_configuration(eigrp_daemon) + frr_cfg.commit_configuration(frr.eigrp_daemon) return None diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py index ba2f3cf0d7..9b70c73295 100755 --- a/src/conf_mode/protocols_isis.py +++ b/src/conf_mode/protocols_isis.py @@ -274,8 +274,6 @@ def generate(isis): return None def apply(isis): - isis_daemon = 'isisd' - # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() @@ -285,7 +283,7 @@ def apply(isis): if 'vrf' in isis: vrf = ' vrf ' + isis['vrf'] - frr_cfg.load_configuration(isis_daemon) + frr_cfg.load_configuration(frr.isis_daemon) frr_cfg.modify_section(f'^router isis VyOS{vrf}', stop_pattern='^exit', remove_stop_mark=True) for key in ['interface', 'interface_removed']: @@ -297,7 +295,7 @@ def apply(isis): if 'frr_isisd_config' in isis: frr_cfg.add_before(frr.default_add_before, isis['frr_isisd_config']) - frr_cfg.commit_configuration(isis_daemon) + frr_cfg.commit_configuration(frr.isis_daemon) return None diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py index ad164db9f8..4a05b80448 100755 --- a/src/conf_mode/protocols_mpls.py +++ b/src/conf_mode/protocols_mpls.py @@ -77,17 +77,15 @@ def generate(mpls): return None def apply(mpls): - ldpd_damon = 'ldpd' - # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(ldpd_damon) + frr_cfg.load_configuration(frr.ldpd_daemon) frr_cfg.modify_section(f'^mpls ldp', stop_pattern='^exit', remove_stop_mark=True) if 'frr_ldpd_config' in mpls: frr_cfg.add_before(frr.default_add_before, mpls['frr_ldpd_config']) - frr_cfg.commit_configuration(ldpd_damon) + frr_cfg.commit_configuration(frr.ldpd_daemon) # Set number of entries in the platform label tables labels = '0' diff --git a/src/conf_mode/protocols_openfabric.py b/src/conf_mode/protocols_openfabric.py index 8e8c50c067..b60117a024 100644 --- a/src/conf_mode/protocols_openfabric.py +++ b/src/conf_mode/protocols_openfabric.py @@ -115,12 +115,10 @@ def generate(openfabric): return None def apply(openfabric): - openfabric_daemon = 'fabricd' - # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(openfabric_daemon) + frr_cfg.load_configuration(frr.openfabric_daemon) for domain in openfabric['domains_all']: frr_cfg.modify_section(f'^router openfabric {domain}', stop_pattern='^exit', remove_stop_mark=True) @@ -130,7 +128,7 @@ def apply(openfabric): if 'frr_fabricd_config' in openfabric: frr_cfg.add_before(frr.default_add_before, openfabric['frr_fabricd_config']) - frr_cfg.commit_configuration(openfabric_daemon) + frr_cfg.commit_configuration(frr.openfabric_daemon) return None diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py index 7347c4faac..44817b9b7c 100755 --- a/src/conf_mode/protocols_ospf.py +++ b/src/conf_mode/protocols_ospf.py @@ -252,8 +252,6 @@ def generate(ospf): return None def apply(ospf): - ospf_daemon = 'ospfd' - # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() @@ -263,7 +261,7 @@ def apply(ospf): if 'vrf' in ospf: vrf = ' vrf ' + ospf['vrf'] - frr_cfg.load_configuration(ospf_daemon) + frr_cfg.load_configuration(frr.ospf_daemon) frr_cfg.modify_section(f'^router ospf{vrf}', stop_pattern='^exit', remove_stop_mark=True) for key in ['interface', 'interface_removed']: @@ -275,7 +273,7 @@ def apply(ospf): if 'frr_ospfd_config' in ospf: frr_cfg.add_before(frr.default_add_before, ospf['frr_ospfd_config']) - frr_cfg.commit_configuration(ospf_daemon) + frr_cfg.commit_configuration(frr.ospf_daemon) return None diff --git a/src/conf_mode/protocols_ospfv3.py b/src/conf_mode/protocols_ospfv3.py index 60c2a9b16b..7bdab30172 100755 --- a/src/conf_mode/protocols_ospfv3.py +++ b/src/conf_mode/protocols_ospfv3.py @@ -153,8 +153,6 @@ def generate(ospfv3): return None def apply(ospfv3): - ospf6_daemon = 'ospf6d' - # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() @@ -164,7 +162,7 @@ def apply(ospfv3): if 'vrf' in ospfv3: vrf = ' vrf ' + ospfv3['vrf'] - frr_cfg.load_configuration(ospf6_daemon) + frr_cfg.load_configuration(frr.ospf6_daemon) frr_cfg.modify_section(f'^router ospf6{vrf}', stop_pattern='^exit', remove_stop_mark=True) for key in ['interface', 'interface_removed']: @@ -176,7 +174,7 @@ def apply(ospfv3): if 'new_frr_config' in ospfv3: frr_cfg.add_before(frr.default_add_before, ospfv3['new_frr_config']) - frr_cfg.commit_configuration(ospf6_daemon) + frr_cfg.commit_configuration(frr.ospf6_daemon) return None diff --git a/src/conf_mode/protocols_pim.py b/src/conf_mode/protocols_pim.py index 79294a1f02..9ef734effd 100755 --- a/src/conf_mode/protocols_pim.py +++ b/src/conf_mode/protocols_pim.py @@ -131,8 +131,7 @@ def generate(pim): return None def apply(pim): - pim_daemon = 'pimd' - pim_pid = process_named_running(pim_daemon) + pim_pid = process_named_running(frr.pim_daemon) if not pim or 'deleted' in pim: if 'deleted' in pim: @@ -146,7 +145,7 @@ def apply(pim): # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(pim_daemon) + frr_cfg.load_configuration(frr.pim_daemon) frr_cfg.modify_section(f'^ip pim') frr_cfg.modify_section(f'^ip igmp') @@ -158,7 +157,7 @@ def apply(pim): if 'frr_pimd_config' in pim: frr_cfg.add_before(frr.default_add_before, pim['frr_pimd_config']) - frr_cfg.commit_configuration(pim_daemon) + frr_cfg.commit_configuration(frr.pim_daemon) return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_pim6.py b/src/conf_mode/protocols_pim6.py index 581ffe238a..1abc256fe9 100755 --- a/src/conf_mode/protocols_pim6.py +++ b/src/conf_mode/protocols_pim6.py @@ -104,12 +104,9 @@ def apply(pim6): if pim6 is None: return - pim6_daemon = 'pim6d' - # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() - - frr_cfg.load_configuration(pim6_daemon) + frr_cfg.load_configuration(frr.pim6_daemon) for key in ['interface', 'interface_removed']: if key not in pim6: @@ -119,7 +116,7 @@ def apply(pim6): if 'new_frr_config' in pim6: frr_cfg.add_before(frr.default_add_before, pim6['new_frr_config']) - frr_cfg.commit_configuration(pim6_daemon) + frr_cfg.commit_configuration(frr.pim6_daemon) return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_rip.py b/src/conf_mode/protocols_rip.py index 9afac544d3..9940071377 100755 --- a/src/conf_mode/protocols_rip.py +++ b/src/conf_mode/protocols_rip.py @@ -101,18 +101,12 @@ def generate(rip): return None def apply(rip): - rip_daemon = 'ripd' - zebra_daemon = 'zebra' - # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() - # The route-map used for the FIB (zebra) is part of the zebra daemon - frr_cfg.load_configuration(zebra_daemon) + # The route-map used for the FIB (zebra) is part of the mgmt daemon as of FRR 10.1 + frr_cfg.load_configuration(frr.mgmt_daemon) frr_cfg.modify_section('^ip protocol rip route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') - frr_cfg.commit_configuration(zebra_daemon) - - frr_cfg.load_configuration(rip_daemon) frr_cfg.modify_section('^key chain \S+', stop_pattern='^exit', remove_stop_mark=True) frr_cfg.modify_section('^router rip', stop_pattern='^exit', remove_stop_mark=True) @@ -124,7 +118,7 @@ def apply(rip): if 'new_frr_config' in rip: frr_cfg.add_before(frr.default_add_before, rip['new_frr_config']) - frr_cfg.commit_configuration(rip_daemon) + frr_cfg.commit_configuration() return None diff --git a/src/conf_mode/protocols_ripng.py b/src/conf_mode/protocols_ripng.py index 23416ff965..9d4447d1ff 100755 --- a/src/conf_mode/protocols_ripng.py +++ b/src/conf_mode/protocols_ripng.py @@ -85,31 +85,22 @@ def verify(ripng): def generate(ripng): if not ripng: - ripng['new_frr_config'] = '' return None - ripng['new_frr_config'] = render_to_string('frr/ripngd.frr.j2', ripng) - return None def apply(ripng): - ripng_daemon = 'ripngd' - zebra_daemon = 'zebra' - # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() # The route-map used for the FIB (zebra) is part of the zebra daemon - frr_cfg.load_configuration(zebra_daemon) + frr_cfg.load_configuration(frr.mgmt_daemon) frr_cfg.modify_section('^ipv6 protocol ripng route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') - frr_cfg.commit_configuration(zebra_daemon) - - frr_cfg.load_configuration(ripng_daemon) frr_cfg.modify_section('key chain \S+', stop_pattern='^exit', remove_stop_mark=True) frr_cfg.modify_section('interface \S+', stop_pattern='^exit', remove_stop_mark=True) frr_cfg.modify_section('^router ripng', stop_pattern='^exit', remove_stop_mark=True) if 'new_frr_config' in ripng: frr_cfg.add_before(frr.default_add_before, ripng['new_frr_config']) - frr_cfg.commit_configuration(ripng_daemon) + frr_cfg.commit_configuration() return None diff --git a/src/conf_mode/protocols_rpki.py b/src/conf_mode/protocols_rpki.py index a59ecf3e42..bec0cda91e 100755 --- a/src/conf_mode/protocols_rpki.py +++ b/src/conf_mode/protocols_rpki.py @@ -107,16 +107,14 @@ def generate(rpki): return None def apply(rpki): - bgp_daemon = 'bgpd' - # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(bgp_daemon) + frr_cfg.load_configuration(frr.bgp_daemon) frr_cfg.modify_section('^rpki', stop_pattern='^exit', remove_stop_mark=True) if 'new_frr_config' in rpki: frr_cfg.add_before(frr.default_add_before, rpki['new_frr_config']) - frr_cfg.commit_configuration(bgp_daemon) + frr_cfg.commit_configuration(frr.bgp_daemon) return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_segment-routing.py b/src/conf_mode/protocols_segment-routing.py index b36c2ca117..67f8005ef7 100755 --- a/src/conf_mode/protocols_segment-routing.py +++ b/src/conf_mode/protocols_segment-routing.py @@ -70,8 +70,6 @@ def generate(sr): return None def apply(sr): - zebra_daemon = 'zebra' - if 'interface_removed' in sr: for interface in sr['interface_removed']: # Disable processing of IPv6-SR packets @@ -97,11 +95,11 @@ def apply(sr): # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(zebra_daemon) + frr_cfg.load_configuration(frr.zebra_daemon) frr_cfg.modify_section(r'^segment-routing') if 'new_frr_config' in sr: frr_cfg.add_before(frr.default_add_before, sr['new_frr_config']) - frr_cfg.commit_configuration(zebra_daemon) + frr_cfg.commit_configuration(frr.zebra_daemon) return None diff --git a/src/conf_mode/protocols_static.py b/src/conf_mode/protocols_static.py index 430cc69d4f..c5dc77fd21 100755 --- a/src/conf_mode/protocols_static.py +++ b/src/conf_mode/protocols_static.py @@ -102,11 +102,9 @@ def generate(static): return None def apply(static): - static_daemon = 'staticd' - # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(static_daemon) + frr_cfg.load_configuration(frr.mgmt_daemon) if 'vrf' in static: vrf = static['vrf'] @@ -117,7 +115,7 @@ def apply(static): if 'new_frr_config' in static: frr_cfg.add_before(frr.default_add_before, static['new_frr_config']) - frr_cfg.commit_configuration(static_daemon) + frr_cfg.commit_configuration() return None diff --git a/src/conf_mode/protocols_static_multicast.py b/src/conf_mode/protocols_static_multicast.py index c8894fd417..4393f3ed38 100755 --- a/src/conf_mode/protocols_static_multicast.py +++ b/src/conf_mode/protocols_static_multicast.py @@ -103,10 +103,9 @@ def generate(mroute): def apply(mroute): if mroute is None: return None - static_daemon = 'staticd' frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(static_daemon) + frr_cfg.load_configuration(frr.mgmt_daemon) if 'old_mroute' in mroute: for route_gr in mroute['old_mroute']: @@ -119,8 +118,7 @@ def apply(mroute): if 'new_frr_config' in mroute: frr_cfg.add_before(frr.default_add_before, mroute['new_frr_config']) - frr_cfg.commit_configuration(static_daemon) - + frr_cfg.commit_configuration() return None diff --git a/src/conf_mode/service_snmp.py b/src/conf_mode/service_snmp.py index c9c0ed9a04..134662f850 100755 --- a/src/conf_mode/service_snmp.py +++ b/src/conf_mode/service_snmp.py @@ -34,6 +34,7 @@ from vyos.utils.permission import chmod_755 from vyos.version import get_version_data from vyos import ConfigError +from vyos import frr from vyos import airbag airbag.enable() @@ -265,7 +266,8 @@ def apply(snmp): # This should be done for each daemon individually because common command # works only if all the daemons started with SNMP support # Following daemons from FRR 9.0/stable have SNMP module compiled in VyOS - frr_daemons_list = ['zebra', 'bgpd', 'ospf6d', 'ospfd', 'ripd', 'isisd', 'ldpd'] + frr_daemons_list = [frr.zebra_daemon, frr.bgp_daemon, frr.ospf_daemon, frr.ospf6_daemon, + frr.rip_daemon, frr.isis_daemon, frr.ldpd_daemon] for frr_daemon in frr_daemons_list: call(f'vtysh -c "configure terminal" -d {frr_daemon} -c "agentx" >/dev/null') diff --git a/src/conf_mode/system_ip.py b/src/conf_mode/system_ip.py index c8a91fd2f1..5afb574049 100755 --- a/src/conf_mode/system_ip.py +++ b/src/conf_mode/system_ip.py @@ -121,17 +121,16 @@ def apply(opt): # running when this script is called first. Skip this part and wait for initial # commit of the configuration to trigger this statement if is_systemd_service_active('frr.service'): - zebra_daemon = 'zebra' # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() # The route-map used for the FIB (zebra) is part of the zebra daemon - frr_cfg.load_configuration(zebra_daemon) + frr_cfg.load_configuration(frr.mgmt_daemon) frr_cfg.modify_section(r'no ip nht resolve-via-default') frr_cfg.modify_section(r'ip protocol \w+ route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') if 'frr_zebra_config' in opt: frr_cfg.add_before(frr.default_add_before, opt['frr_zebra_config']) - frr_cfg.commit_configuration(zebra_daemon) + frr_cfg.commit_configuration() call_dependents() diff --git a/src/conf_mode/system_ipv6.py b/src/conf_mode/system_ipv6.py index a2442d0099..90d5100d74 100755 --- a/src/conf_mode/system_ipv6.py +++ b/src/conf_mode/system_ipv6.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2023 VyOS maintainers and contributors +# Copyright (C) 2019-2024 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 @@ -105,17 +105,14 @@ def apply(opt): # running when this script is called first. Skip this part and wait for initial # commit of the configuration to trigger this statement if is_systemd_service_active('frr.service'): - zebra_daemon = 'zebra' # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() - - # The route-map used for the FIB (zebra) is part of the zebra daemon - frr_cfg.load_configuration(zebra_daemon) + frr_cfg.load_configuration(frr.mgmt_daemon) frr_cfg.modify_section(r'no ipv6 nht resolve-via-default') frr_cfg.modify_section(r'ipv6 protocol \w+ route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') if 'frr_zebra_config' in opt: frr_cfg.add_before(frr.default_add_before, opt['frr_zebra_config']) - frr_cfg.commit_configuration(zebra_daemon) + frr_cfg.commit_configuration() call_dependents() diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index 72b178c899..6d17c192c4 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -339,17 +339,13 @@ def apply(vrf): if has_rule(afi, 2000, 'l3mdev'): call(f'ip {afi} rule del pref 2000 l3mdev unreachable') - # Apply FRR filters - zebra_daemon = 'zebra' # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() - - # The route-map used for the FIB (zebra) is part of the zebra daemon - frr_cfg.load_configuration(zebra_daemon) + frr_cfg.load_configuration(frr.mgmt_daemon) frr_cfg.modify_section(f'^vrf .+', stop_pattern='^exit-vrf', remove_stop_mark=True) if 'frr_zebra_config' in vrf: frr_cfg.add_before(frr.default_add_before, vrf['frr_zebra_config']) - frr_cfg.commit_configuration(zebra_daemon) + frr_cfg.commit_configuration() return None From 6544973ae66bbecaea8b6194a8bf6cd3eadf4fa8 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sun, 6 Oct 2024 15:19:09 +0200 Subject: [PATCH 04/28] frr: T6747: ensure there are no daemon leftovers in vtysh CLI after smoketests --- smoketest/scripts/cli/test_protocols_bgp.py | 3 +++ smoketest/scripts/cli/test_protocols_ospf.py | 3 +++ smoketest/scripts/cli/test_protocols_ospfv3.py | 3 +++ smoketest/scripts/cli/test_protocols_rip.py | 3 +++ smoketest/scripts/cli/test_protocols_ripng.py | 3 +++ 5 files changed, 15 insertions(+) diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index ad9ce36760..6eeac37a71 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -218,6 +218,9 @@ def tearDown(self): self.cli_delete(base_path) self.cli_commit() + frrconfig = self.getFRRconfig('router bgp') + self.assertNotIn(f'router bgp', frrconfig) + # check process health and continuity self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py index c3ae54e121..5da352300e 100755 --- a/smoketest/scripts/cli/test_protocols_ospf.py +++ b/smoketest/scripts/cli/test_protocols_ospf.py @@ -55,6 +55,9 @@ def tearDown(self): self.cli_delete(base_path) self.cli_commit() + frrconfig = self.getFRRconfig('router ospf') + self.assertNotIn(f'router ospf', frrconfig) + # check process health and continuity self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) diff --git a/smoketest/scripts/cli/test_protocols_ospfv3.py b/smoketest/scripts/cli/test_protocols_ospfv3.py index 989e1552d6..f0892d5765 100755 --- a/smoketest/scripts/cli/test_protocols_ospfv3.py +++ b/smoketest/scripts/cli/test_protocols_ospfv3.py @@ -54,6 +54,9 @@ def tearDown(self): self.cli_delete(base_path) self.cli_commit() + frrconfig = self.getFRRconfig('router ospf6') + self.assertNotIn(f'router ospf6', frrconfig) + # check process health and continuity self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) diff --git a/smoketest/scripts/cli/test_protocols_rip.py b/smoketest/scripts/cli/test_protocols_rip.py index bfc327fd4e..48a1f877b9 100755 --- a/smoketest/scripts/cli/test_protocols_rip.py +++ b/smoketest/scripts/cli/test_protocols_rip.py @@ -66,6 +66,9 @@ def tearDown(self): self.cli_delete(base_path) self.cli_commit() + frrconfig = self.getFRRconfig('router rip') + self.assertNotIn(f'router rip', frrconfig) + # check process health and continuity self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) diff --git a/smoketest/scripts/cli/test_protocols_ripng.py b/smoketest/scripts/cli/test_protocols_ripng.py index 0cfb065c65..c266bdfd27 100755 --- a/smoketest/scripts/cli/test_protocols_ripng.py +++ b/smoketest/scripts/cli/test_protocols_ripng.py @@ -66,6 +66,9 @@ def tearDown(self): self.cli_delete(base_path) self.cli_commit() + frrconfig = self.getFRRconfig('router ripng') + self.assertNotIn(f'router ripng', frrconfig) + # check process health and continuity self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) From 8b21fb0aecf46b6da4bac091d380d3c4927100bd Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sun, 6 Oct 2024 15:19:28 +0200 Subject: [PATCH 05/28] vrf: T6747: ensure VNIs are unique on the system --- src/conf_mode/vrf.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index 6d17c192c4..3d3845fdf3 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -158,6 +158,7 @@ def verify(vrf): reserved_names = ["add", "all", "broadcast", "default", "delete", "dev", "get", "inet", "mtu", "link", "type", "vrf"] table_ids = [] + vnis = [] for name, vrf_config in vrf['name'].items(): # Reserved VRF names if name in reserved_names: @@ -178,6 +179,13 @@ def verify(vrf): raise ConfigError(f'VRF "{name}" table id is not unique!') table_ids.append(vrf_config['table']) + # VRF VNIs must be unique on the system + if 'vni' in vrf_config: + vni = vrf_config['vni'] + if vni in vnis: + raise ConfigError(f'VRF "{name}" VNI "{vni}" is not unique!') + vnis.append(vni) + tmp = dict_search('ip.protocol', vrf_config) if tmp != None: for protocol, protocol_options in tmp.items(): From 43ec27e6340779706dff0c6cbd601296c532d535 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Mon, 14 Oct 2024 16:04:30 +0200 Subject: [PATCH 06/28] ospf: T6747: fix deferred shutdown handling Honor ospfd deferred shutdown when "max-metric router-lsa on-shutdown" is defined. https://github.com/FRRouting/frr/issues/17011 --- smoketest/scripts/cli/test_protocols_ospf.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py index 5da352300e..24cc24da42 100755 --- a/smoketest/scripts/cli/test_protocols_ospf.py +++ b/smoketest/scripts/cli/test_protocols_ospf.py @@ -15,8 +15,8 @@ # along with this program. If not, see . import unittest -import time +from time import sleep from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError @@ -226,6 +226,13 @@ def test_ospf_05_options(self): frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) self.assertIn(f' distance ospf intra-area {intra_area} inter-area {inter_area} external {external}', frrconfig) + # https://github.com/FRRouting/frr/issues/17011 + # We need to wait on_shutdown time, until the OSPF process is removed from the CLI + # otherwise the test in tearDown() will fail + self.cli_delete(base_path) + self.cli_commit() + + sleep(int(on_shutdown) + 5) # additional grace period of 5 seconds def test_ospf_06_neighbor(self): priority = '10' @@ -572,7 +579,7 @@ def test_ospf_17_duplicate_area_network(self): print(f"Attempt {retry_count}: FRR config is still empty. Retrying...") retry_count += 1 - time.sleep(1) + sleep(1) frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) if not frrconfig: From 69540c75594a692fd72e3f75b1608628a94366ff Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sun, 8 Dec 2024 08:28:08 +0100 Subject: [PATCH 07/28] pim: T6747: adjust template to new "router pim" FRR CLI --- data/templates/frr/pimd.frr.j2 | 36 +++---- smoketest/scripts/cli/test_protocols_pim.py | 35 +++---- src/conf_mode/protocols_pim.py | 105 +++++--------------- 3 files changed, 62 insertions(+), 114 deletions(-) diff --git a/data/templates/frr/pimd.frr.j2 b/data/templates/frr/pimd.frr.j2 index 68edf4a5c5..d474d8495f 100644 --- a/data/templates/frr/pimd.frr.j2 +++ b/data/templates/frr/pimd.frr.j2 @@ -39,10 +39,10 @@ interface {{ iface }} {% for join, join_config in iface_config.igmp.join.items() %} {% if join_config.source_address is vyos_defined %} {% for source_address in join_config.source_address %} - ip igmp join {{ join }} {{ source_address }} + ip igmp join-group {{ join }} {{ source_address }} {% endfor %} {% else %} - ip igmp join {{ join }} + ip igmp join-group {{ join }} {% endif %} {% endfor %} {% endif %} @@ -51,45 +51,47 @@ exit {% endfor %} {% endif %} ! +{% if igmp.watermark_warning is vyos_defined %} +ip igmp watermark-warn {{ igmp.watermark_warning }} +{% endif %} +! +router pim {% if ecmp is vyos_defined %} -ip pim ecmp {{ 'rebalance' if ecmp.rebalance is vyos_defined }} + ecmp {{ 'rebalance' if ecmp.rebalance is vyos_defined }} {% endif %} {% if join_prune_interval is vyos_defined %} -ip pim join-prune-interval {{ join_prune_interval }} + join-prune-interval {{ join_prune_interval }} {% endif %} {% if keep_alive_timer is vyos_defined %} -ip pim keep-alive-timer {{ keep_alive_timer }} + keep-alive-timer {{ keep_alive_timer }} {% endif %} {% if packets is vyos_defined %} -ip pim packets {{ packets }} + packets {{ packets }} {% endif %} {% if register_accept_list.prefix_list is vyos_defined %} -ip pim register-accept-list {{ register_accept_list.prefix_list }} + register-accept-list {{ register_accept_list.prefix_list }} {% endif %} {% if register_suppress_time is vyos_defined %} -ip pim register-suppress-time {{ register_suppress_time }} + register-suppress-time {{ register_suppress_time }} {% endif %} {% if rp.address is vyos_defined %} {% for address, address_config in rp.address.items() %} {% for group in address_config.group %} -ip pim rp {{ address }} {{ group }} + rp {{ address }} {{ group }} {% endfor %} {% endfor %} {% endif %} {% if rp.keep_alive_timer is vyos_defined %} -ip pim rp keep-alive-timer {{ rp.keep_alive_timer }} + rp keep-alive-timer {{ rp.keep_alive_timer }} {% endif %} {% if no_v6_secondary is vyos_defined %} -no ip pim send-v6-secondary + no send-v6-secondary {% endif %} {% if spt_switchover.infinity_and_beyond is vyos_defined %} -ip pim spt-switchover infinity-and-beyond {{ 'prefix-list ' ~ spt_switchover.infinity_and_beyond.prefix_list if spt_switchover.infinity_and_beyond.prefix_list is defined }} + spt-switchover infinity-and-beyond {{ 'prefix-list ' ~ spt_switchover.infinity_and_beyond.prefix_list if spt_switchover.infinity_and_beyond.prefix_list is defined }} {% endif %} {% if ssm.prefix_list is vyos_defined %} -ip pim ssm prefix-list {{ ssm.prefix_list }} -{% endif %} -! -{% if igmp.watermark_warning is vyos_defined %} -ip igmp watermark-warn {{ igmp.watermark_warning }} + ssm prefix-list {{ ssm.prefix_list }} {% endif %} +exit ! diff --git a/smoketest/scripts/cli/test_protocols_pim.py b/smoketest/scripts/cli/test_protocols_pim.py index ccfced1384..a2cc4f1ed8 100755 --- a/smoketest/scripts/cli/test_protocols_pim.py +++ b/smoketest/scripts/cli/test_protocols_pim.py @@ -19,10 +19,11 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError +from vyos.frrender import pim_daemon from vyos.ifconfig import Section from vyos.utils.process import process_named_running -PROCESS_NAME = 'pimd' +PROCESS_NAME = pim_daemon base_path = ['protocols', 'pim'] class TestProtocolsPIM(VyOSUnitTestSHIM.TestCase): @@ -57,8 +58,8 @@ def test_01_pim_basic(self): self.cli_commit() # Verify FRR pimd configuration - frrconfig = self.getFRRconfig(daemon=PROCESS_NAME) - self.assertIn(f'ip pim rp {rp} {group}', frrconfig) + frrconfig = self.getFRRconfig('router pim', endsection='^exit', daemon=PROCESS_NAME) + self.assertIn(f' rp {rp} {group}', frrconfig) for interface in interfaces: frrconfig = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) @@ -108,18 +109,18 @@ def test_02_pim_advanced(self): self.cli_commit() # Verify FRR pimd configuration - frrconfig = self.getFRRconfig(daemon=PROCESS_NAME) - self.assertIn(f'ip pim rp {rp} {group}', frrconfig) - self.assertIn(f'ip pim rp keep-alive-timer {rp_keep_alive_timer}', frrconfig) - self.assertIn(f'ip pim ecmp rebalance', frrconfig) - self.assertIn(f'ip pim join-prune-interval {join_prune_interval}', frrconfig) - self.assertIn(f'ip pim keep-alive-timer {keep_alive_timer}', frrconfig) - self.assertIn(f'ip pim packets {packets}', frrconfig) - self.assertIn(f'ip pim register-accept-list {prefix_list}', frrconfig) - self.assertIn(f'ip pim register-suppress-time {register_suppress_time}', frrconfig) - self.assertIn(f'no ip pim send-v6-secondary', frrconfig) - self.assertIn(f'ip pim spt-switchover infinity-and-beyond prefix-list {prefix_list}', frrconfig) - self.assertIn(f'ip pim ssm prefix-list {prefix_list}', frrconfig) + frrconfig = self.getFRRconfig('router pim', endsection='^exit', daemon=PROCESS_NAME) + self.assertIn(f' no send-v6-secondary', frrconfig) + self.assertIn(f' rp {rp} {group}', frrconfig) + self.assertIn(f' register-suppress-time {register_suppress_time}', frrconfig) + self.assertIn(f' join-prune-interval {join_prune_interval}', frrconfig) + self.assertIn(f' packets {packets}', frrconfig) + self.assertIn(f' keep-alive-timer {keep_alive_timer}', frrconfig) + self.assertIn(f' rp keep-alive-timer {rp_keep_alive_timer}', frrconfig) + self.assertIn(f' ssm prefix-list {prefix_list}', frrconfig) + self.assertIn(f' register-accept-list {prefix_list}', frrconfig) + self.assertIn(f' spt-switchover infinity-and-beyond prefix-list {prefix_list}', frrconfig) + self.assertIn(f' ecmp rebalance', frrconfig) def test_03_pim_igmp_proxy(self): igmp_proxy = ['protocols', 'igmp-proxy'] @@ -184,9 +185,9 @@ def test_04_igmp(self): for join, join_config in igmp_join.items(): if 'source' in join_config: for source in join_config['source']: - self.assertIn(f' ip igmp join {join} {source}', frrconfig) + self.assertIn(f' ip igmp join-group {join} {source}', frrconfig) else: - self.assertIn(f' ip igmp join {join}', frrconfig) + self.assertIn(f' ip igmp join-group {join}', frrconfig) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/protocols_pim.py b/src/conf_mode/protocols_pim.py index 9ef734effd..1ff7203b28 100755 --- a/src/conf_mode/protocols_pim.py +++ b/src/conf_mode/protocols_pim.py @@ -22,19 +22,17 @@ from sys import exit from vyos.config import Config -from vyos.config import config_dict_merge -from vyos.configdict import node_changed +from vyos.configdict import get_frrender_dict from vyos.configverify import verify_interface_exists +from vyos.configverify import has_frr_protocol_in_dict +from vyos.frrender import FRRender +from vyos.frrender import pim_daemon from vyos.utils.process import process_named_running from vyos.utils.process import call -from vyos.template import render_to_string from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() - -RESERVED_MC_NET = '224.0.0.0/24' - +frrender = FRRender() def get_config(config=None): if config: @@ -42,52 +40,15 @@ def get_config(config=None): else: conf = Config() - base = ['protocols', 'pim'] - - pim = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) - - # We can not run both IGMP proxy and PIM at the same time - get IGMP - # proxy status - if conf.exists(['protocols', 'igmp-proxy']): - pim.update({'igmp_proxy_enabled' : {}}) - - # FRR has VRF support for different routing daemons. As interfaces belong - # to VRFs - or the global VRF, we need to check for changed interfaces so - # that they will be properly rendered for the FRR config. Also this eases - # removal of interfaces from the running configuration. - interfaces_removed = node_changed(conf, base + ['interface']) - if interfaces_removed: - pim['interface_removed'] = list(interfaces_removed) - - # Bail out early if configuration tree does no longer exist. this must - # be done after retrieving the list of interfaces to be removed. - if not conf.exists(base): - pim.update({'deleted' : ''}) - return pim - - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = conf.get_config_defaults(**pim.kwargs, recursive=True) - - # We have to cleanup the default dict, as default values could enable features - # which are not explicitly enabled on the CLI. Example: default-information - # originate comes with a default metric-type of 2, which will enable the - # entire default-information originate tree, even when not set via CLI so we - # need to check this first and probably drop that key. - for interface in pim.get('interface', []): - # We need to reload the defaults on every pass b/c of - # hello-multiplier dependency on dead-interval - # If hello-multiplier is set, we need to remove the default from - # dead-interval. - if 'igmp' not in pim['interface'][interface]: - del default_values['interface'][interface]['igmp'] - - pim = config_dict_merge(default_values, pim) - return pim - -def verify(pim): - if not pim or 'deleted' in pim: + return get_frrender_dict(conf) + +def verify(config_dict): + if not has_frr_protocol_in_dict(config_dict, 'pim'): + return None + + pim = config_dict['pim'] + + if 'deleted' in pim: return None if 'igmp_proxy_enabled' in pim: @@ -96,6 +57,7 @@ def verify(pim): if 'interface' not in pim: raise ConfigError('PIM require defined interfaces!') + RESERVED_MC_NET = '224.0.0.0/24' for interface, interface_config in pim['interface'].items(): verify_interface_exists(pim, interface) @@ -124,40 +86,23 @@ def verify(pim): raise ConfigError(f'{pim_base_error} must be unique!') unique.append(gr_addr) -def generate(pim): - if not pim or 'deleted' in pim: - return None - pim['frr_pimd_config'] = render_to_string('frr/pimd.frr.j2', pim) - return None - -def apply(pim): - pim_pid = process_named_running(frr.pim_daemon) +def generate(config_dict): + frrender.generate(config_dict) - if not pim or 'deleted' in pim: - if 'deleted' in pim: - os.kill(int(pim_pid), SIGTERM) +def apply(config_dict): + if not has_frr_protocol_in_dict(config_dict, 'pim'): + return None + pim_pid = process_named_running(pim_daemon) + pim = config_dict['pim'] + if 'deleted' in pim: + os.kill(int(pim_pid), SIGTERM) return None if not pim_pid: call('/usr/lib/frr/pimd -d -F traditional --daemon -A 127.0.0.1') - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - - frr_cfg.load_configuration(frr.pim_daemon) - frr_cfg.modify_section(f'^ip pim') - frr_cfg.modify_section(f'^ip igmp') - - for key in ['interface', 'interface_removed']: - if key not in pim: - continue - for interface in pim[key]: - frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True) - - if 'frr_pimd_config' in pim: - frr_cfg.add_before(frr.default_add_before, pim['frr_pimd_config']) - frr_cfg.commit_configuration(frr.pim_daemon) + frrender.apply() return None if __name__ == '__main__': From 655ecf7715688de3463c258ea5a635dcf00c7150 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sun, 8 Dec 2024 08:28:25 +0100 Subject: [PATCH 08/28] pim6: T6747: adjust template to new "router pim6" FRR CLI --- data/templates/frr/pim6d.frr.j2 | 21 +++--- smoketest/scripts/cli/test_protocols_pim6.py | 14 ++-- src/conf_mode/protocols_pim6.py | 68 +++++--------------- 3 files changed, 34 insertions(+), 69 deletions(-) diff --git a/data/templates/frr/pim6d.frr.j2 b/data/templates/frr/pim6d.frr.j2 index bac716fcc3..d4144a2f9b 100644 --- a/data/templates/frr/pim6d.frr.j2 +++ b/data/templates/frr/pim6d.frr.j2 @@ -40,10 +40,10 @@ interface {{ iface }} {% for group, group_config in iface_config.mld.join.items() %} {% if group_config.source is vyos_defined %} {% for source in group_config.source %} - ipv6 mld join {{ group }} {{ source }} + ipv6 mld join-group {{ group }} {{ source }} {% endfor %} {% else %} - ipv6 mld join {{ group }} + ipv6 mld join-group {{ group }} {% endif %} {% endfor %} {% endif %} @@ -52,30 +52,33 @@ exit {% endfor %} {% endif %} ! +router pim6 {% if join_prune_interval is vyos_defined %} -ipv6 pim join-prune-interval {{ join_prune_interval }} + join-prune-interval {{ join_prune_interval }} {% endif %} {% if keep_alive_timer is vyos_defined %} -ipv6 pim keep-alive-timer {{ keep_alive_timer }} + keep-alive-timer {{ keep_alive_timer }} {% endif %} {% if packets is vyos_defined %} -ipv6 pim packets {{ packets }} + packets {{ packets }} {% endif %} {% if register_suppress_time is vyos_defined %} -ipv6 pim register-suppress-time {{ register_suppress_time }} + register-suppress-time {{ register_suppress_time }} {% endif %} {% if rp.address is vyos_defined %} {% for address, address_config in rp.address.items() %} {% if address_config.group is vyos_defined %} {% for group in address_config.group %} -ipv6 pim rp {{ address }} {{ group }} + rp {{ address }} {{ group }} {% endfor %} {% endif %} {% if address_config.prefix_list6 is vyos_defined %} -ipv6 pim rp {{ address }} prefix-list {{ address_config.prefix_list6 }} + rp {{ address }} prefix-list {{ address_config.prefix_list6 }} {% endif %} {% endfor %} {% endif %} {% if rp.keep_alive_timer is vyos_defined %} -ipv6 pim rp keep-alive-timer {{ rp.keep_alive_timer }} + rp keep-alive-timer {{ rp.keep_alive_timer }} {% endif %} +exit +! diff --git a/smoketest/scripts/cli/test_protocols_pim6.py b/smoketest/scripts/cli/test_protocols_pim6.py index ba24edca25..4b9ae6161f 100755 --- a/smoketest/scripts/cli/test_protocols_pim6.py +++ b/smoketest/scripts/cli/test_protocols_pim6.py @@ -90,7 +90,7 @@ def test_pim6_02_mld_join(self): for interface in interfaces: config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) self.assertIn(f'interface {interface}', config) - self.assertIn(f' ipv6 mld join ff18::1234', config) + self.assertIn(f' ipv6 mld join-group ff18::1234', config) # Join a source-specific multicast group for interface in interfaces: @@ -102,7 +102,7 @@ def test_pim6_02_mld_join(self): for interface in interfaces: config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) self.assertIn(f'interface {interface}', config) - self.assertIn(f' ipv6 mld join ff38::5678 2001:db8::5678', config) + self.assertIn(f' ipv6 mld join-group ff38::5678 2001:db8::5678', config) def test_pim6_03_basic(self): interfaces = Section.interfaces('ethernet') @@ -128,11 +128,11 @@ def test_pim6_03_basic(self): self.cli_commit() # Verify FRR pim6d configuration - config = self.getFRRconfig(daemon=PROCESS_NAME) - self.assertIn(f'ipv6 pim join-prune-interval {join_prune_interval}', config) - self.assertIn(f'ipv6 pim keep-alive-timer {keep_alive_timer}', config) - self.assertIn(f'ipv6 pim packets {packets}', config) - self.assertIn(f'ipv6 pim register-suppress-time {register_suppress_time}', config) + config = self.getFRRconfig('router pim6', endsection='^exit', daemon=PROCESS_NAME) + self.assertIn(f' join-prune-interval {join_prune_interval}', config) + self.assertIn(f' keep-alive-timer {keep_alive_timer}', config) + self.assertIn(f' packets {packets}', config) + self.assertIn(f' register-suppress-time {register_suppress_time}', config) for interface in interfaces: config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) diff --git a/src/conf_mode/protocols_pim6.py b/src/conf_mode/protocols_pim6.py index 1abc256fe9..b3a4099d2a 100755 --- a/src/conf_mode/protocols_pim6.py +++ b/src/conf_mode/protocols_pim6.py @@ -19,48 +19,29 @@ from sys import exit from vyos.config import Config -from vyos.config import config_dict_merge -from vyos.configdict import node_changed +from vyos.configdict import get_frrender_dict +from vyos.configverify import has_frr_protocol_in_dict from vyos.configverify import verify_interface_exists -from vyos.template import render_to_string +from vyos.frrender import FRRender from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() +frrender = FRRender() def get_config(config=None): if config: conf = config else: conf = Config() - base = ['protocols', 'pim6'] - pim6 = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, with_recursive_defaults=True) + return get_frrender_dict(conf) - # FRR has VRF support for different routing daemons. As interfaces belong - # to VRFs - or the global VRF, we need to check for changed interfaces so - # that they will be properly rendered for the FRR config. Also this eases - # removal of interfaces from the running configuration. - interfaces_removed = node_changed(conf, base + ['interface']) - if interfaces_removed: - pim6['interface_removed'] = list(interfaces_removed) +def verify(config_dict): + if not has_frr_protocol_in_dict(config_dict, 'pim6'): + return None - # Bail out early if configuration tree does no longer exist. this must - # be done after retrieving the list of interfaces to be removed. - if not conf.exists(base): - pim6.update({'deleted' : ''}) - return pim6 - - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = conf.get_config_defaults(**pim6.kwargs, recursive=True) - - pim6 = config_dict_merge(default_values, pim6) - return pim6 - -def verify(pim6): - if not pim6 or 'deleted' in pim6: - return + pim6 = config_dict['pim6'] + if 'deleted' in pim6: + return None for interface, interface_config in pim6.get('interface', {}).items(): verify_interface_exists(pim6, interface) @@ -94,30 +75,11 @@ def verify(pim6): raise ConfigError(f'{pim_base_error} must be unique!') unique.append(gr_addr) -def generate(pim6): - if not pim6 or 'deleted' in pim6: - return - pim6['new_frr_config'] = render_to_string('frr/pim6d.frr.j2', pim6) - return None - -def apply(pim6): - if pim6 is None: - return - - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(frr.pim6_daemon) - - for key in ['interface', 'interface_removed']: - if key not in pim6: - continue - for interface in pim6[key]: - frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True) +def generate(config_dict): + frrender.generate(config_dict) - if 'new_frr_config' in pim6: - frr_cfg.add_before(frr.default_add_before, pim6['new_frr_config']) - frr_cfg.commit_configuration(frr.pim6_daemon) - return None +def apply(config_dict): + frrender.apply() if __name__ == '__main__': try: From 4f93cfbc6e66c9a8312bebee1b54d03021ec2072 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sun, 8 Dec 2024 08:29:50 +0100 Subject: [PATCH 09/28] ospf: T6747: add retransmit-window CLI option --- data/templates/frr/ospfd.frr.j2 | 7 +++++-- .../include/ospf/protocol-common-config.xml.i | 2 ++ .../include/ospf/retransmit-window.xml.i | 15 +++++++++++++++ smoketest/scripts/cli/test_protocols_ospf.py | 4 +++- 4 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 interface-definitions/include/ospf/retransmit-window.xml.i diff --git a/data/templates/frr/ospfd.frr.j2 b/data/templates/frr/ospfd.frr.j2 index ab074b6a26..bc2c74b101 100644 --- a/data/templates/frr/ospfd.frr.j2 +++ b/data/templates/frr/ospfd.frr.j2 @@ -30,6 +30,9 @@ interface {{ iface }} {% if iface_config.retransmit_interval is vyos_defined %} ip ospf retransmit-interval {{ iface_config.retransmit_interval }} {% endif %} +{% if iface_config.retransmit_window is vyos_defined %} + ip ospf retransmit-window {{ iface_config.retransmit_window }} +{% endif %} {% if iface_config.transmit_delay is vyos_defined %} ip ospf transmit-delay {{ iface_config.transmit_delay }} {% endif %} @@ -125,7 +128,7 @@ router ospf {{ 'vrf ' ~ vrf if vrf is vyos_defined }} {% endfor %} {% endif %} {# The following values are default values #} - area {{ area_id }} virtual-link {{ link }} hello-interval {{ link_config.hello_interval }} retransmit-interval {{ link_config.retransmit_interval }} transmit-delay {{ link_config.transmit_delay }} dead-interval {{ link_config.dead_interval }} + area {{ area_id }} virtual-link {{ link }} hello-interval {{ link_config.hello_interval }} retransmit-interval {{ link_config.retransmit_interval }} retransmit-window {{ link_config.retransmit_window }} transmit-delay {{ link_config.transmit_delay }} dead-interval {{ link_config.dead_interval }} {% endfor %} {% endif %} {% endfor %} @@ -233,6 +236,7 @@ router ospf {{ 'vrf ' ~ vrf if vrf is vyos_defined }} {% endfor %} {% endif %} {% if segment_routing is vyos_defined %} + segment-routing on {% if segment_routing.maximum_label_depth is vyos_defined %} segment-routing node-msd {{ segment_routing.maximum_label_depth }} {% endif %} @@ -252,7 +256,6 @@ router ospf {{ 'vrf ' ~ vrf if vrf is vyos_defined }} {% endif %} {% endfor %} {% endif %} - segment-routing on {% endif %} {% if timers.throttle.spf.delay is vyos_defined and timers.throttle.spf.initial_holdtime is vyos_defined and timers.throttle.spf.max_holdtime is vyos_defined %} {# Timer values have default values #} diff --git a/interface-definitions/include/ospf/protocol-common-config.xml.i b/interface-definitions/include/ospf/protocol-common-config.xml.i index c4778e1268..cef832381e 100644 --- a/interface-definitions/include/ospf/protocol-common-config.xml.i +++ b/interface-definitions/include/ospf/protocol-common-config.xml.i @@ -321,6 +321,7 @@ #include #include + #include @@ -433,6 +434,7 @@ #include #include + #include #include #include diff --git a/interface-definitions/include/ospf/retransmit-window.xml.i b/interface-definitions/include/ospf/retransmit-window.xml.i new file mode 100644 index 0000000000..a5e20f5227 --- /dev/null +++ b/interface-definitions/include/ospf/retransmit-window.xml.i @@ -0,0 +1,15 @@ + + + + Window for LSA retransmit + + u32:20-1000 + Retransmit LSAs expiring in this window (milliseconds) + + + + + + 50 + + diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py index 24cc24da42..97543048fb 100755 --- a/smoketest/scripts/cli/test_protocols_ospf.py +++ b/smoketest/scripts/cli/test_protocols_ospf.py @@ -22,6 +22,7 @@ from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section from vyos.utils.process import process_named_running +from vyos.xml_ref import default_value PROCESS_NAME = 'ospfd' base_path = ['protocols', 'ospf'] @@ -279,6 +280,7 @@ def test_ospf_08_virtual_link(self): retransmit = '5' transmit = '5' dead = '40' + window_default = default_value(base_path + ['area', area, 'virtual-link', virtual_link, 'retransmit-window']) self.cli_set(base_path + ['area', area, 'shortcut', shortcut]) self.cli_set(base_path + ['area', area, 'virtual-link', virtual_link, 'hello-interval', hello]) @@ -295,7 +297,7 @@ def test_ospf_08_virtual_link(self): frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) self.assertIn(f'router ospf', frrconfig) self.assertIn(f' area {area} shortcut {shortcut}', frrconfig) - self.assertIn(f' area {area} virtual-link {virtual_link} hello-interval {hello} retransmit-interval {retransmit} transmit-delay {transmit} dead-interval {dead}', frrconfig) + self.assertIn(f' area {area} virtual-link {virtual_link} hello-interval {hello} retransmit-interval {retransmit} retransmit-window {window_default} transmit-delay {transmit} dead-interval {dead}', frrconfig) for network in networks: self.assertIn(f' network {network} area {area}', frrconfig) From 328f354677a4fd2f60b95473698816e99aaa20a7 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sun, 8 Dec 2024 08:33:31 +0100 Subject: [PATCH 10/28] frr: T6747: remove superfluous newlines from rendered templates Drop newlines added by macro statement and Jinja2 comments. Jinja2 comments will be removed during package build on the shipped files. --- data/templates/frr/bgpd.frr.j2 | 10 +++++++--- data/templates/frr/distribute_list_macro.j2 | 3 ++- data/templates/frr/ipv6_distribute_list_macro.j2 | 3 ++- data/templates/frr/staticd.frr.j2 | 8 +++++--- debian/rules | 2 ++ 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/data/templates/frr/bgpd.frr.j2 b/data/templates/frr/bgpd.frr.j2 index e5bfad59d0..71716250d7 100644 --- a/data/templates/frr/bgpd.frr.j2 +++ b/data/templates/frr/bgpd.frr.j2 @@ -245,9 +245,11 @@ neighbor {{ neighbor }} activate exit-address-family ! +{# j2lint: disable=jinja-statements-delimeter #} {% endfor %} {% endif %} -{% endmacro %} +{# j2lint: disable=jinja-statements-delimeter #} +{%- endmacro -%} ! router bgp {{ system_as }} {{ 'vrf ' ~ vrf if vrf is vyos_defined }} {% if parameters.ebgp_requires_policy is vyos_defined %} @@ -512,13 +514,15 @@ router bgp {{ system_as }} {{ 'vrf ' ~ vrf if vrf is vyos_defined }} {% if peer_group is vyos_defined %} {% for peer, config in peer_group.items() %} {{ bgp_neighbor(peer, config, true) }} -{% endfor %} +{# j2lint: disable=jinja-statements-delimeter #} +{%- endfor %} {% endif %} ! {% if neighbor is vyos_defined %} {% for peer, config in neighbor.items() %} {{ bgp_neighbor(peer, config) }} -{% endfor %} +{# j2lint: disable=jinja-statements-delimeter #} +{%- endfor %} {% endif %} ! {% if listen.limit is vyos_defined %} diff --git a/data/templates/frr/distribute_list_macro.j2 b/data/templates/frr/distribute_list_macro.j2 index c10bf732d9..3e15ef100e 100644 --- a/data/templates/frr/distribute_list_macro.j2 +++ b/data/templates/frr/distribute_list_macro.j2 @@ -27,4 +27,5 @@ {% if distribute_list.prefix_list.out is vyos_defined %} distribute-list prefix {{ distribute_list.prefix_list.out }} out {% endif %} -{% endmacro %} +{# j2lint: disable=jinja-statements-delimeter #} +{%- endmacro -%} diff --git a/data/templates/frr/ipv6_distribute_list_macro.j2 b/data/templates/frr/ipv6_distribute_list_macro.j2 index c365fbdae6..2f483b7d4c 100644 --- a/data/templates/frr/ipv6_distribute_list_macro.j2 +++ b/data/templates/frr/ipv6_distribute_list_macro.j2 @@ -27,4 +27,5 @@ {% if distribute_list.prefix_list.out is vyos_defined %} ipv6 distribute-list prefix {{ distribute_list.prefix_list.out }} out {% endif %} -{% endmacro %} +{# j2lint: disable=jinja-statements-delimeter #} +{%- endmacro -%} diff --git a/data/templates/frr/staticd.frr.j2 b/data/templates/frr/staticd.frr.j2 index 992a0435c5..68e3f21f9e 100644 --- a/data/templates/frr/staticd.frr.j2 +++ b/data/templates/frr/staticd.frr.j2 @@ -13,7 +13,8 @@ vrf {{ vrf }} {% if route is vyos_defined %} {% for prefix, prefix_config in route.items() %} {{ static_routes(ip_prefix, prefix, prefix_config) }} -{% endfor %} +{# j2lint: disable=jinja-statements-delimeter #} +{%- endfor %} {% endif %} {# IPv4 default routes from DHCP interfaces #} {% if dhcp is vyos_defined %} @@ -28,13 +29,14 @@ vrf {{ vrf }} {% if pppoe is vyos_defined %} {% for interface, interface_config in pppoe.items() if interface_config.no_default_route is not vyos_defined %} {{ ip_prefix }} route 0.0.0.0/0 {{ interface }} tag 210 {{ interface_config.default_route_distance if interface_config.default_route_distance is vyos_defined }} -{% endfor %} +{%- endfor %} {% endif %} {# IPv6 routing #} {% if route6 is vyos_defined %} {% for prefix, prefix_config in route6.items() %} {{ static_routes(ipv6_prefix, prefix, prefix_config) }} -{% endfor %} +{# j2lint: disable=jinja-statements-delimeter #} +{%- endfor %} {% endif %} {% if vrf is vyos_defined %} exit-vrf diff --git a/debian/rules b/debian/rules index c15fcab115..d7c427b0d2 100755 --- a/debian/rules +++ b/debian/rules @@ -94,6 +94,8 @@ override_dh_auto_install: cp -r data/reftree.cache $(DIR)/$(VYCONF_CONFIG_DIR) mkdir -p $(DIR)/$(VYOS_DATA_DIR) cp -r data/* $(DIR)/$(VYOS_DATA_DIR) + # Remove j2lint comments / linter configuration which would insert additional new-lines + find $(DIR)/$(VYOS_DATA_DIR) -name "*.j2" -type f | xargs sed -i -e '/^{#.*#}/d' # Create localui dir mkdir -p $(DIR)/$(VYOS_LOCALUI_DIR) From 3c79477adf3cd4f4efb302b58542ddd668b562ac Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sun, 8 Dec 2024 08:34:41 +0100 Subject: [PATCH 11/28] frr: T6747: migrate protocols to unified FRRender class With FRR 10.0 daemons started to be migrated to integrated FRR mgmtd and a northbound interface. This led to some drawbacks in the current state how changes to FRR are handled. The current implementation will use frr-reload.py and specifies excatly WHICH daemon needs a config update and will only replace this part inside FRR. With FRR10 and mgmtd when a partial configuration is sent to mgmtd, it will remove configuration parts from other daemons like bgpd or ospfd which have not yet been migrated to mgmtd. It's not possible to call frr-reload.py with daemon mgmtd - it will error out. This commit will also change the CLI for static routes: CLI command "set protocols static route 10.0.0.0/8 next-hop 1.2.3.4 bfd multi-hop source 1.1.1.1" will be split into: * set protocols static route 10.0.0.0/8 next-hop 1.2.3.4 bfd source-address 1.1.1.1 * set protocols static route 10.0.0.0/8 next-hop 1.2.3.4 bfd multi-hop To make the XML blocks reusable, and comply with the FRR CLI - this was actually a wrong implementation from the beginning as you can not have multiple BFD source addresses. CLI command "set protocols static route 10.0.0.0/8 next-hop 1.2.3.4 bfd multi-hop source 1.1.1.1 profile bar" is changed to: * set protocols static route 10.0.0.0/8 next-hop 1.2.3.4 bfd profile bar CLI commands "set protocols static multicast interface-route" is moved to: * set protocols static multicast route interface To have an identical look and feel with regular static routes. --- data/templates/frr/bgpd.frr.j2 | 14 +- data/templates/frr/evpn.mh.frr.j2 | 28 +- data/templates/frr/fabricd.frr.j2 | 1 + data/templates/frr/static_mcast.frr.j2 | 11 - data/templates/frr/static_routes_macro.j2 | 31 -- data/templates/frr/staticd.frr.j2 | 97 +++- data/templates/frr/zebra.vrf.route-map.frr.j2 | 2 +- .../include/source-address-ipv4.xml.i | 2 +- .../include/source-address-ipv6.xml.i | 17 + .../include/static/bfd-multi-hop.xml.i | 8 + .../include/static/static-route-bfd.xml.i | 36 -- .../include/static/static-route.xml.i | 12 +- .../include/static/static-route6.xml.i | 11 +- interface-definitions/protocols_static.xml.in | 56 +++ .../protocols_static_multicast.xml.in | 95 ---- python/vyos/configdict.py | 465 ++++++++++++++++++ python/vyos/configverify.py | 15 +- python/vyos/frrender.py | 156 ++++++ smoketest/scripts/cli/test_policy.py | 8 +- smoketest/scripts/cli/test_protocols_bfd.py | 12 +- .../scripts/cli/test_protocols_static.py | 110 ++++- .../cli/test_protocols_static_multicast.py | 49 -- src/conf_mode/interfaces_bonding.py | 34 +- src/conf_mode/interfaces_ethernet.py | 32 +- src/conf_mode/policy.py | 129 ++--- src/conf_mode/protocols_babel.py | 79 +-- src/conf_mode/protocols_bfd.py | 40 +- src/conf_mode/protocols_bgp.py | 136 ++--- src/conf_mode/protocols_eigrp.py | 81 +-- src/conf_mode/protocols_isis.py | 142 +++--- src/conf_mode/protocols_mpls.py | 42 +- src/conf_mode/protocols_openfabric.py | 63 +-- src/conf_mode/protocols_ospf.py | 133 +---- src/conf_mode/protocols_ospfv3.py | 124 +---- src/conf_mode/protocols_rip.py | 78 +-- src/conf_mode/protocols_ripng.py | 63 +-- src/conf_mode/protocols_rpki.py | 49 +- src/conf_mode/protocols_segment-routing.py | 92 ++-- src/conf_mode/protocols_static.py | 82 ++- src/conf_mode/protocols_static_multicast.py | 133 ----- src/conf_mode/service_snmp.py | 11 - src/conf_mode/vrf.py | 23 +- 42 files changed, 1378 insertions(+), 1424 deletions(-) delete mode 100644 data/templates/frr/static_mcast.frr.j2 delete mode 100644 data/templates/frr/static_routes_macro.j2 create mode 100644 interface-definitions/include/source-address-ipv6.xml.i create mode 100644 interface-definitions/include/static/bfd-multi-hop.xml.i delete mode 100644 interface-definitions/include/static/static-route-bfd.xml.i delete mode 100644 interface-definitions/protocols_static_multicast.xml.in create mode 100644 python/vyos/frrender.py delete mode 100755 smoketest/scripts/cli/test_protocols_static_multicast.py delete mode 100755 src/conf_mode/protocols_static_multicast.py diff --git a/data/templates/frr/bgpd.frr.j2 b/data/templates/frr/bgpd.frr.j2 index 71716250d7..51a3f25640 100644 --- a/data/templates/frr/bgpd.frr.j2 +++ b/data/templates/frr/bgpd.frr.j2 @@ -1,13 +1,19 @@ {### MACRO definition for recurring peer patter, this can be either fed by a ###} {### peer-group or an individual BGP neighbor ###} {% macro bgp_neighbor(neighbor, config, peer_group=false) %} +{# BGP order of peer-group and remote-as placement must be honored #} {% if peer_group == true %} neighbor {{ neighbor }} peer-group -{% elif config.peer_group is vyos_defined %} - neighbor {{ neighbor }} peer-group {{ config.peer_group }} -{% endif %} -{% if config.remote_as is vyos_defined %} +{% if config.remote_as is vyos_defined %} + neighbor {{ neighbor }} remote-as {{ config.remote_as }} +{% endif %} +{% else %} +{% if config.remote_as is vyos_defined %} neighbor {{ neighbor }} remote-as {{ config.remote_as }} +{% endif %} +{% if config.peer_group is vyos_defined %} + neighbor {{ neighbor }} peer-group {{ config.peer_group }} +{% endif %} {% endif %} {% if config.local_role is vyos_defined %} {% for role, strict in config.local_role.items() %} diff --git a/data/templates/frr/evpn.mh.frr.j2 b/data/templates/frr/evpn.mh.frr.j2 index 03aaac44b8..2fd7b7c09b 100644 --- a/data/templates/frr/evpn.mh.frr.j2 +++ b/data/templates/frr/evpn.mh.frr.j2 @@ -1,16 +1,20 @@ ! -interface {{ ifname }} -{% if evpn.es_df_pref is vyos_defined %} - evpn mh es-df-pref {{ evpn.es_df_pref }} -{% endif %} -{% if evpn.es_id is vyos_defined %} - evpn mh es-id {{ evpn.es_id }} -{% endif %} -{% if evpn.es_sys_mac is vyos_defined %} - evpn mh es-sys-mac {{ evpn.es_sys_mac }} -{% endif %} -{% if evpn.uplink is vyos_defined %} +{% if interfaces is vyos_defined %} +{% for if_name, if_config in interfaces.items() %} +interface {{ if_name }} +{% if if_config.evpn.es_df_pref is vyos_defined %} + evpn mh es-df-pref {{ if_config.evpn.es_df_pref }} +{% endif %} +{% if if_config.evpn.es_id is vyos_defined %} + evpn mh es-id {{ if_config.evpn.es_id }} +{% endif %} +{% if if_config.evpn.es_sys_mac is vyos_defined %} + evpn mh es-sys-mac {{ if_config.evpn.es_sys_mac }} +{% endif %} +{% if if_config.evpn.uplink is vyos_defined %} evpn mh uplink -{% endif %} +{% endif %} exit ! +{% endfor %} +{% endif %} diff --git a/data/templates/frr/fabricd.frr.j2 b/data/templates/frr/fabricd.frr.j2 index 8f2ae64669..3a0646eb8b 100644 --- a/data/templates/frr/fabricd.frr.j2 +++ b/data/templates/frr/fabricd.frr.j2 @@ -70,3 +70,4 @@ router openfabric {{ name }} exit ! {% endfor %} +! diff --git a/data/templates/frr/static_mcast.frr.j2 b/data/templates/frr/static_mcast.frr.j2 deleted file mode 100644 index 54b2790b07..0000000000 --- a/data/templates/frr/static_mcast.frr.j2 +++ /dev/null @@ -1,11 +0,0 @@ -! -{% for route_gr in mroute %} -{% for nh in mroute[route_gr] %} -{% if mroute[route_gr][nh] %} -ip mroute {{ route_gr }} {{ nh }} {{ mroute[route_gr][nh] }} -{% else %} -ip mroute {{ route_gr }} {{ nh }} -{% endif %} -{% endfor %} -{% endfor %} -! diff --git a/data/templates/frr/static_routes_macro.j2 b/data/templates/frr/static_routes_macro.j2 deleted file mode 100644 index db31700c51..0000000000 --- a/data/templates/frr/static_routes_macro.j2 +++ /dev/null @@ -1,31 +0,0 @@ -{% macro static_routes(ip_ipv6, prefix, prefix_config, table=None) %} -{% if prefix_config.blackhole is vyos_defined %} -{{ ip_ipv6 }} route {{ prefix }} blackhole {{ prefix_config.blackhole.distance if prefix_config.blackhole.distance is vyos_defined }} {{ 'tag ' ~ prefix_config.blackhole.tag if prefix_config.blackhole.tag is vyos_defined }} {{ 'table ' ~ table if table is vyos_defined and table is not none }} -{% endif %} -{% if prefix_config.reject is vyos_defined %} -{{ ip_ipv6 }} route {{ prefix }} reject {{ prefix_config.reject.distance if prefix_config.reject.distance is vyos_defined }} {{ 'tag ' ~ prefix_config.reject.tag if prefix_config.reject.tag is vyos_defined }} {{ 'table ' ~ table if table is vyos_defined }} -{% endif %} -{% if prefix_config.dhcp_interface is vyos_defined %} -{% for dhcp_interface in prefix_config.dhcp_interface %} -{% set next_hop = dhcp_interface | get_dhcp_router %} -{% if next_hop is vyos_defined %} -{{ ip_ipv6 }} route {{ prefix }} {{ next_hop }} {{ dhcp_interface }} {{ 'table ' ~ table if table is vyos_defined }} -{% endif %} -{% endfor %} -{% endif %} -{% if prefix_config.interface is vyos_defined %} -{% for interface, interface_config in prefix_config.interface.items() if interface_config.disable is not defined %} -{{ ip_ipv6 }} route {{ prefix }} {{ interface }} {{ interface_config.distance if interface_config.distance is vyos_defined }} {{ 'nexthop-vrf ' ~ interface_config.vrf if interface_config.vrf is vyos_defined }} {{ 'segments ' ~ interface_config.segments if interface_config.segments is vyos_defined }} {{ 'table ' ~ table if table is vyos_defined }} -{% endfor %} -{% endif %} -{% if prefix_config.next_hop is vyos_defined and prefix_config.next_hop is not none %} -{% for next_hop, next_hop_config in prefix_config.next_hop.items() if next_hop_config.disable is not defined %} -{{ ip_ipv6 }} route {{ prefix }} {{ next_hop }} {{ next_hop_config.interface if next_hop_config.interface is vyos_defined }} {{ next_hop_config.distance if next_hop_config.distance is vyos_defined }} {{ 'nexthop-vrf ' ~ next_hop_config.vrf if next_hop_config.vrf is vyos_defined }} {{ 'bfd profile ' ~ next_hop_config.bfd.profile if next_hop_config.bfd.profile is vyos_defined }} {{ 'segments ' ~ next_hop_config.segments if next_hop_config.segments is vyos_defined }} {{ 'table ' ~ table if table is vyos_defined }} -{% if next_hop_config.bfd.multi_hop.source is vyos_defined %} -{% for source, source_config in next_hop_config.bfd.multi_hop.source.items() %} -{{ ip_ipv6 }} route {{ prefix }} {{ next_hop }} bfd multi-hop source {{ source }} profile {{ source_config.profile }} -{% endfor %} -{% endif %} -{% endfor %} -{% endif %} -{% endmacro %} diff --git a/data/templates/frr/staticd.frr.j2 b/data/templates/frr/staticd.frr.j2 index 68e3f21f9e..c662b76505 100644 --- a/data/templates/frr/staticd.frr.j2 +++ b/data/templates/frr/staticd.frr.j2 @@ -1,12 +1,77 @@ -{% from 'frr/static_routes_macro.j2' import static_routes %} +{# Common macro for recurroiing options for a static route #} +{% macro route_options(route, interface_or_next_hop, config, table) %} +{# j2lint: disable=jinja-statements-delimeter #} +{% set ip_route = route ~ ' ' ~ interface_or_next_hop %} +{% if config.interface is vyos_defined %} +{% set ip_route = ip_route ~ ' ' ~ config.interface %} +{% endif %} +{% if config.tag is vyos_defined %} +{% set ip_route = ip_route ~ ' tag ' ~ config.tag %} +{% endif %} +{% if config.distance is vyos_defined %} +{% set ip_route = ip_route ~ ' ' ~ config.distance %} +{% endif %} +{% if config.bfd is vyos_defined %} +{% set ip_route = ip_route ~ ' bfd' %} +{% if config.bfd.multi_hop is vyos_defined %} +{% set ip_route = ip_route ~ ' multi-hop' %} +{% if config.bfd.source_address is vyos_defined %} +{% set ip_route = ip_route ~ ' source ' ~ config.bfd.source_address %} +{% endif %} +{% endif %} +{% if config.bfd.profile is vyos_defined %} +{% set ip_route = ip_route ~ ' profile ' ~ config.bfd.profile %} +{% endif %} +{% endif %} +{% if config.vrf is vyos_defined %} +{% set ip_route = ip_route ~ ' nexthop-vrf ' ~ config.vrf %} +{% endif %} +{% if config.segments is vyos_defined %} +{# Segments used in/for SRv6 #} +{% set ip_route = ip_route ~ ' segments ' ~ config.segments %} +{% endif %} +{# Routing table to configure #} +{% if table is vyos_defined %} +{% set ip_route = ip_route ~ ' table ' ~ table %} +{% endif %} +{{ ip_route }} +{%- endmacro -%} +{# Build static IPv4/IPv6 route #} +{% macro static_routes(ip_ipv6, prefix, prefix_config, table=None) %} +{% set route = ip_ipv6 ~ 'route ' ~ prefix %} +{% if prefix_config.interface is vyos_defined %} +{% for interface, interface_config in prefix_config.interface.items() if interface_config.disable is not defined %} +{{ route_options(route, interface, interface_config, table) }} +{% endfor %} +{% endif %} +{% if prefix_config.next_hop is vyos_defined and prefix_config.next_hop is not none %} +{% for next_hop, next_hop_config in prefix_config.next_hop.items() if next_hop_config.disable is not defined %} +{{ route_options(route, next_hop, next_hop_config, table) }} +{% endfor %} +{% endif %} +{% if prefix_config.dhcp_interface is vyos_defined %} +{% for dhcp_interface in prefix_config.dhcp_interface %} +{% set next_hop = dhcp_interface | get_dhcp_router %} +{% if next_hop is vyos_defined %} +{{ ip_ipv6 }} route {{ prefix }} {{ next_hop }} {{ dhcp_interface }} {{ 'table ' ~ table if table is vyos_defined }} +{% endif %} +{% endfor %} +{% endif %} +{% if prefix_config.blackhole is vyos_defined %} +{{ route_options(route, 'blackhole', prefix_config.blackhole, table) }} +{% elif prefix_config.reject is vyos_defined %} +{{ route_options(route, 'reject', prefix_config.reject, table) }} +{% endif %} +{# j2lint: disable=jinja-statements-delimeter #} +{%- endmacro -%} ! -{% set ip_prefix = 'ip' %} -{% set ipv6_prefix = 'ipv6' %} +{% set ip_prefix = 'ip ' %} +{% set ipv6_prefix = 'ipv6 ' %} {% if vrf is vyos_defined %} {# We need to add an additional whitespace in front of the prefix #} {# when VRFs are in use, thus we use a variable for prefix handling #} -{% set ip_prefix = ' ip' %} -{% set ipv6_prefix = ' ipv6' %} +{% set ip_prefix = ' ip ' %} +{% set ipv6_prefix = ' ipv6 ' %} vrf {{ vrf }} {% endif %} {# IPv4 routing #} @@ -47,19 +112,33 @@ exit-vrf {% for table_id, table_config in table.items() %} {% if table_config.route is vyos_defined %} {% for prefix, prefix_config in table_config.route.items() %} -{{ static_routes('ip', prefix, prefix_config, table_id) }} -{% endfor %} +{{ static_routes('ip ', prefix, prefix_config, table_id) }} +{# j2lint: disable=jinja-statements-delimeter #} +{%- endfor %} {% endif %} ! {% if table_config.route6 is vyos_defined %} {% for prefix, prefix_config in table_config.route6.items() %} -{{ static_routes('ipv6', prefix, prefix_config, table_id) }} -{% endfor %} +{{ static_routes('ipv6 ', prefix, prefix_config, table_id) }} +{# j2lint: disable=jinja-statements-delimeter #} +{%- endfor %} {% endif %} ! {% endfor %} {% endif %} ! +{# Multicast route #} +{% if multicast is vyos_defined %} +{% set ip_prefix = 'ip m' %} +{# IPv4 multicast routing #} +{% if multicast.route is vyos_defined %} +{% for prefix, prefix_config in multicast.route.items() %} +{{ static_routes(ip_prefix, prefix, prefix_config) }} +{# j2lint: disable=jinja-statements-delimeter #} +{%- endfor %} +{% endif %} +{% endif %} +! {% if route_map is vyos_defined %} ip protocol static route-map {{ route_map }} ! diff --git a/data/templates/frr/zebra.vrf.route-map.frr.j2 b/data/templates/frr/zebra.vrf.route-map.frr.j2 index 8ebb825119..656b31deb3 100644 --- a/data/templates/frr/zebra.vrf.route-map.frr.j2 +++ b/data/templates/frr/zebra.vrf.route-map.frr.j2 @@ -25,6 +25,6 @@ vrf {{ vrf }} vni {{ vrf_config.vni }} {% endif %} exit-vrf -{% endfor %} ! +{% endfor %} {% endif %} diff --git a/interface-definitions/include/source-address-ipv4.xml.i b/interface-definitions/include/source-address-ipv4.xml.i index 0526781138..aa0b083c72 100644 --- a/interface-definitions/include/source-address-ipv4.xml.i +++ b/interface-definitions/include/source-address-ipv4.xml.i @@ -1,7 +1,7 @@ - IPv4 source address used to initiate connection + IPv4 address used to initiate connection diff --git a/interface-definitions/include/source-address-ipv6.xml.i b/interface-definitions/include/source-address-ipv6.xml.i new file mode 100644 index 0000000000..a27955b0cf --- /dev/null +++ b/interface-definitions/include/source-address-ipv6.xml.i @@ -0,0 +1,17 @@ + + + + IPv6 address used to initiate connection + + + + + ipv6 + IPv6 source address + + + + + + + diff --git a/interface-definitions/include/static/bfd-multi-hop.xml.i b/interface-definitions/include/static/bfd-multi-hop.xml.i new file mode 100644 index 0000000000..e53994191d --- /dev/null +++ b/interface-definitions/include/static/bfd-multi-hop.xml.i @@ -0,0 +1,8 @@ + + + + Enable BFD multi-hop session (requires source-address) + + + + diff --git a/interface-definitions/include/static/static-route-bfd.xml.i b/interface-definitions/include/static/static-route-bfd.xml.i deleted file mode 100644 index d588b369f9..0000000000 --- a/interface-definitions/include/static/static-route-bfd.xml.i +++ /dev/null @@ -1,36 +0,0 @@ - - - - BFD monitoring - - - #include - - - Use BFD multi hop session - - - - - Use source for BFD session - - ipv4 - IPv4 source address - - - ipv6 - IPv6 source address - - - - - - - #include - - - - - - - diff --git a/interface-definitions/include/static/static-route.xml.i b/interface-definitions/include/static/static-route.xml.i index 51e45c6f7b..fa11311185 100644 --- a/interface-definitions/include/static/static-route.xml.i +++ b/interface-definitions/include/static/static-route.xml.i @@ -51,10 +51,18 @@ #include #include #include - #include + + + BFD monitoring + + + #include + #include + #include + + - diff --git a/interface-definitions/include/static/static-route6.xml.i b/interface-definitions/include/static/static-route6.xml.i index 4468c80259..e75385dc73 100644 --- a/interface-definitions/include/static/static-route6.xml.i +++ b/interface-definitions/include/static/static-route6.xml.i @@ -48,11 +48,20 @@ #include - #include #include #include #include #include + + + BFD monitoring + + + #include + #include + #include + + diff --git a/interface-definitions/protocols_static.xml.in b/interface-definitions/protocols_static.xml.in index ca4ca2d747..407e565533 100644 --- a/interface-definitions/protocols_static.xml.in +++ b/interface-definitions/protocols_static.xml.in @@ -11,6 +11,62 @@ 480 + + + Multicast static route + + + + + Configure static unicast route into MRIB for multicast RPF lookup + + ipv4net + Network + + + + + + + + + Next-hop IPv4 router address + + ipv4 + Next-hop router address + + + + + + + #include + #include + + + + + Next-hop IPv4 router interface + + + + + txt + Gateway interface name + + + #include + + + + #include + #include + + + + + + #include #include #include diff --git a/interface-definitions/protocols_static_multicast.xml.in b/interface-definitions/protocols_static_multicast.xml.in deleted file mode 100644 index caf95ed7c2..0000000000 --- a/interface-definitions/protocols_static_multicast.xml.in +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - - Multicast static route - 481 - - - - - Configure static unicast route into MRIB for multicast RPF lookup - - ipv4net - Network - - - - - - - - - Nexthop IPv4 address - - ipv4 - Nexthop IPv4 address - - - - - - - - - Distance value for this route - - u32:1-255 - Distance for this route - - - - - - - - - - - - - Multicast interface based route - - ipv4net - Network - - - - - - - - - Next-hop interface - - - - - - - - Distance value for this route - - u32:1-255 - Distance for this route - - - - - - - - - - - - - - - - - diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 5a353b1106..88d131573d 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -664,3 +664,468 @@ def get_accel_dict(config, base, chap_secrets, with_pki=False): dict['authentication']['radius']['server'][server]['acct_port'] = '0' return dict + +def get_frrender_dict(conf) -> dict: + from copy import deepcopy + from vyos.config import config_dict_merge + from vyos.frrender import frr_protocols + + # Create an empty dictionary which will be filled down the code path and + # returned to the caller + dict = {} + + def dict_helper_ospf_defaults(ospf, path): + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + default_values = conf.get_config_defaults(path, key_mangling=('-', '_'), + get_first_key=True, recursive=True) + + # We have to cleanup the default dict, as default values could enable features + # which are not explicitly enabled on the CLI. Example: default-information + # originate comes with a default metric-type of 2, which will enable the + # entire default-information originate tree, even when not set via CLI so we + # need to check this first and probably drop that key. + if dict_search('default_information.originate', ospf) is None: + del default_values['default_information'] + if 'mpls_te' not in ospf: + del default_values['mpls_te'] + if 'graceful_restart' not in ospf: + del default_values['graceful_restart'] + for area_num in default_values.get('area', []): + if dict_search(f'area.{area_num}.area_type.nssa', ospf) is None: + del default_values['area'][area_num]['area_type']['nssa'] + + for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'rip', 'static']: + if dict_search(f'redistribute.{protocol}', ospf) is None: + del default_values['redistribute'][protocol] + if not bool(default_values['redistribute']): + del default_values['redistribute'] + + for interface in ospf.get('interface', []): + # We need to reload the defaults on every pass b/c of + # hello-multiplier dependency on dead-interval + # If hello-multiplier is set, we need to remove the default from + # dead-interval. + if 'hello_multiplier' in ospf['interface'][interface]: + del default_values['interface'][interface]['dead_interval'] + + ospf = config_dict_merge(default_values, ospf) + return ospf + + def dict_helper_ospfv3_defaults(ospfv3, path): + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + default_values = conf.get_config_defaults(path, key_mangling=('-', '_'), + get_first_key=True, recursive=True) + + # We have to cleanup the default dict, as default values could enable features + # which are not explicitly enabled on the CLI. Example: default-information + # originate comes with a default metric-type of 2, which will enable the + # entire default-information originate tree, even when not set via CLI so we + # need to check this first and probably drop that key. + if dict_search('default_information.originate', ospfv3) is None: + del default_values['default_information'] + if 'graceful_restart' not in ospfv3: + del default_values['graceful_restart'] + + for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'ripng', 'static']: + if dict_search(f'redistribute.{protocol}', ospfv3) is None: + del default_values['redistribute'][protocol] + if not bool(default_values['redistribute']): + del default_values['redistribute'] + + default_values.pop('interface', {}) + + # merge in remaining default values + ospfv3 = config_dict_merge(default_values, ospfv3) + return ospfv3 + + def dict_helper_pim_defaults(pim, path): + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + default_values = conf.get_config_defaults(path, key_mangling=('-', '_'), + get_first_key=True, recursive=True) + + # We have to cleanup the default dict, as default values could enable features + # which are not explicitly enabled on the CLI. + for interface in pim.get('interface', []): + if 'igmp' not in pim['interface'][interface]: + del default_values['interface'][interface]['igmp'] + + pim = config_dict_merge(default_values, pim) + return pim + + # Ethernet and bonding interfaces can participate in EVPN which is configured via FRR + tmp = {} + for if_type in ['ethernet', 'bonding']: + interface_path = ['interfaces', if_type] + if not conf.exists(interface_path): + continue + for interface in conf.list_nodes(interface_path): + evpn_path = interface_path + [interface, 'evpn'] + if not conf.exists(evpn_path): + continue + + evpn = conf.get_config_dict(evpn_path, key_mangling=('-', '_')) + tmp.update({interface : evpn}) + # At least one participating EVPN interface found, add to result dict + if tmp: dict['interfaces'] = tmp + + # Enable SNMP agentx support + # SNMP AgentX support cannot be disabled once enabled + if conf.exists(['service', 'snmp']): + dict['snmp'] = {} + + # We will always need the policy key + dict['policy'] = conf.get_config_dict(['policy'], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + # We need to check the CLI if the BABEL node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + babel_cli_path = ['protocols', 'babel'] + if conf.exists(babel_cli_path): + babel = conf.get_config_dict(babel_cli_path, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + dict.update({'babel' : babel}) + + # We need to check the CLI if the BFD node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + bfd_cli_path = ['protocols', 'bfd'] + if conf.exists(bfd_cli_path): + bfd = conf.get_config_dict(bfd_cli_path, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + with_recursive_defaults=True) + dict.update({'bfd' : bfd}) + + # We need to check the CLI if the BGP node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + bgp_cli_path = ['protocols', 'bgp'] + if conf.exists(bgp_cli_path): + bgp = conf.get_config_dict(bgp_cli_path, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + with_recursive_defaults=True) + bgp['dependent_vrfs'] = {} + dict.update({'bgp' : bgp}) + elif conf.exists_effective(bgp_cli_path): + dict.update({'bgp' : {'deleted' : '', 'dependent_vrfs' : {}}}) + + # We need to check the CLI if the EIGRP node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + eigrp_cli_path = ['protocols', 'eigrp'] + if conf.exists(eigrp_cli_path): + isis = conf.get_config_dict(eigrp_cli_path, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + with_recursive_defaults=True) + dict.update({'eigrp' : isis}) + elif conf.exists_effective(eigrp_cli_path): + dict.update({'eigrp' : {'deleted' : ''}}) + + # We need to check the CLI if the ISIS node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + isis_cli_path = ['protocols', 'isis'] + if conf.exists(isis_cli_path): + isis = conf.get_config_dict(isis_cli_path, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + with_recursive_defaults=True) + dict.update({'isis' : isis}) + elif conf.exists_effective(isis_cli_path): + dict.update({'isis' : {'deleted' : ''}}) + + # We need to check the CLI if the MPLS node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + mpls_cli_path = ['protocols', 'mpls'] + if conf.exists(mpls_cli_path): + mpls = conf.get_config_dict(mpls_cli_path, key_mangling=('-', '_'), + get_first_key=True) + dict.update({'mpls' : mpls}) + elif conf.exists_effective(mpls_cli_path): + dict.update({'mpls' : {'deleted' : ''}}) + + # We need to check the CLI if the OPENFABRIC node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + openfabric_cli_path = ['protocols', 'openfabric'] + if conf.exists(openfabric_cli_path): + openfabric = conf.get_config_dict(openfabric_cli_path, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + dict.update({'openfabric' : openfabric}) + elif conf.exists_effective(openfabric_cli_path): + dict.update({'openfabric' : {'deleted' : ''}}) + + # We need to check the CLI if the OSPF node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + ospf_cli_path = ['protocols', 'ospf'] + if conf.exists(ospf_cli_path): + ospf = conf.get_config_dict(ospf_cli_path, key_mangling=('-', '_'), + get_first_key=True) + ospf = dict_helper_ospf_defaults(ospf, ospf_cli_path) + dict.update({'ospf' : ospf}) + elif conf.exists_effective(ospf_cli_path): + dict.update({'ospf' : {'deleted' : ''}}) + + # We need to check the CLI if the OSPFv3 node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + ospfv3_cli_path = ['protocols', 'ospfv3'] + if conf.exists(ospfv3_cli_path): + ospfv3 = conf.get_config_dict(ospfv3_cli_path, key_mangling=('-', '_'), + get_first_key=True) + ospfv3 = dict_helper_ospfv3_defaults(ospfv3, ospfv3_cli_path) + dict.update({'ospfv3' : ospfv3}) + elif conf.exists_effective(ospfv3_cli_path): + dict.update({'ospfv3' : {'deleted' : ''}}) + + # We need to check the CLI if the PIM node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + pim_cli_path = ['protocols', 'pim'] + if conf.exists(pim_cli_path): + pim = conf.get_config_dict(pim_cli_path, key_mangling=('-', '_'), + get_first_key=True) + pim = dict_helper_pim_defaults(pim, pim_cli_path) + dict.update({'pim' : pim}) + elif conf.exists_effective(pim_cli_path): + dict.update({'pim' : {'deleted' : ''}}) + + # We need to check the CLI if the PIM6 node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + pim6_cli_path = ['protocols', 'pim6'] + if conf.exists(pim6_cli_path): + pim6 = conf.get_config_dict(pim6_cli_path, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + dict.update({'pim6' : pim6}) + elif conf.exists_effective(pim6_cli_path): + dict.update({'pim6' : {'deleted' : ''}}) + + # We need to check the CLI if the RIP node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + rip_cli_path = ['protocols', 'rip'] + if conf.exists(rip_cli_path): + rip = conf.get_config_dict(rip_cli_path, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + dict.update({'rip' : rip}) + elif conf.exists_effective(rip_cli_path): + dict.update({'rip' : {'deleted' : ''}}) + + # We need to check the CLI if the RIPng node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + ripng_cli_path = ['protocols', 'ripng'] + if conf.exists(ripng_cli_path): + ripng = conf.get_config_dict(ripng_cli_path, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + dict.update({'ripng' : ripng}) + elif conf.exists_effective(ripng_cli_path): + dict.update({'ripng' : {'deleted' : ''}}) + + # We need to check the CLI if the RPKI node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + rpki_cli_path = ['protocols', 'rpki'] + if conf.exists(rpki_cli_path): + rpki = conf.get_config_dict(rpki_cli_path, key_mangling=('-', '_'), + get_first_key=True, with_pki=True, + with_recursive_defaults=True) + dict.update({'rpki' : rpki}) + elif conf.exists_effective(rpki_cli_path): + dict.update({'rpki' : {'deleted' : ''}}) + + # We need to check the CLI if the Segment Routing node is present and thus load in + # all the default values present on the CLI - that's why we have if conf.exists() + sr_cli_path = ['protocols', 'segment-routing'] + if conf.exists(sr_cli_path): + sr = conf.get_config_dict(sr_cli_path, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + with_recursive_defaults=True) + dict.update({'segment_routing' : sr}) + elif conf.exists_effective(sr_cli_path): + dict.update({'segment_routing' : {'deleted' : ''}}) + + # We need to check the CLI if the static node is present and thus load in + # all the default values present on the CLI - that's why we have if conf.exists() + static_cli_path = ['protocols', 'static'] + if conf.exists(static_cli_path): + static = conf.get_config_dict(static_cli_path, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + # T3680 - get a list of all interfaces currently configured to use DHCP + tmp = get_dhcp_interfaces(conf) + if tmp: static.update({'dhcp' : tmp}) + tmp = get_pppoe_interfaces(conf) + if tmp: static.update({'pppoe' : tmp}) + + dict.update({'static' : static}) + elif conf.exists_effective(static_cli_path): + dict.update({'static' : {'deleted' : ''}}) + + # keep a re-usable list of dependent VRFs + dependent_vrfs_default = {} + if 'bgp' in dict: + dependent_vrfs_default = deepcopy(dict['bgp']) + # we do not need to nest the 'dependent_vrfs' key - simply remove it + if 'dependent_vrfs' in dependent_vrfs_default: + del dependent_vrfs_default['dependent_vrfs'] + + vrf_cli_path = ['vrf', 'name'] + if conf.exists(vrf_cli_path): + vrf = conf.get_config_dict(vrf_cli_path, key_mangling=('-', '_'), + get_first_key=False, + no_tag_node_value_mangle=True) + # We do not have any VRF related default values on the CLI. The defaults will only + # come into place under the protocols tree, thus we can safely merge them with the + # appropriate routing protocols + for vrf_name, vrf_config in vrf['name'].items(): + bgp_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'bgp'] + if 'bgp' in vrf_config.get('protocols', []): + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + default_values = conf.get_config_defaults(bgp_vrf_path, key_mangling=('-', '_'), + get_first_key=True, recursive=True) + + # merge in remaining default values + vrf_config['protocols']['bgp'] = config_dict_merge(default_values, + vrf_config['protocols']['bgp']) + + # Add this BGP VRF instance as dependency into the default VRF + if 'bgp' in dict: + dict['bgp']['dependent_vrfs'].update({vrf_name : deepcopy(vrf_config)}) + + vrf_config['protocols']['bgp']['dependent_vrfs'] = conf.get_config_dict( + vrf_cli_path, key_mangling=('-', '_'), get_first_key=True, + no_tag_node_value_mangle=True) + + # We can safely delete ourself from the dependent VRF list + if vrf_name in vrf_config['protocols']['bgp']['dependent_vrfs']: + del vrf_config['protocols']['bgp']['dependent_vrfs'][vrf_name] + + # Add dependency on possible existing default VRF to this VRF + if 'bgp' in dict: + vrf_config['protocols']['bgp']['dependent_vrfs'].update({'default': {'protocols': { + 'bgp': dependent_vrfs_default}}}) + elif conf.exists_effective(bgp_vrf_path): + # Add this BGP VRF instance as dependency into the default VRF + tmp = {'deleted' : '', 'dependent_vrfs': deepcopy(vrf['name'])} + # We can safely delete ourself from the dependent VRF list + if vrf_name in tmp['dependent_vrfs']: + del tmp['dependent_vrfs'][vrf_name] + + # Add dependency on possible existing default VRF to this VRF + if 'bgp' in dict: + tmp['dependent_vrfs'].update({'default': {'protocols': { + 'bgp': dependent_vrfs_default}}}) + + if 'bgp' in dict: + dict['bgp']['dependent_vrfs'].update({vrf_name : {'protocols': tmp} }) + vrf['name'][vrf_name]['protocols'].update({'bgp' : tmp}) + + # We need to check the CLI if the EIGRP node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + eigrp_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'eigrp'] + if 'eigrp' in vrf_config.get('protocols', []): + eigrp = conf.get_config_dict(eigrp_vrf_path, key_mangling=('-', '_'), get_first_key=True, + no_tag_node_value_mangle=True) + vrf['name'][vrf_name]['protocols'].update({'eigrp' : isis}) + elif conf.exists_effective(eigrp_vrf_path): + vrf['name'][vrf_name]['protocols'].update({'eigrp' : {'deleted' : ''}}) + + # We need to check the CLI if the ISIS node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + isis_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'isis'] + if 'isis' in vrf_config.get('protocols', []): + isis = conf.get_config_dict(isis_vrf_path, key_mangling=('-', '_'), get_first_key=True, + no_tag_node_value_mangle=True, with_recursive_defaults=True) + vrf['name'][vrf_name]['protocols'].update({'isis' : isis}) + elif conf.exists_effective(isis_vrf_path): + vrf['name'][vrf_name]['protocols'].update({'isis' : {'deleted' : ''}}) + + # We need to check the CLI if the OSPF node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + ospf_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'ospf'] + if 'ospf' in vrf_config.get('protocols', []): + ospf = conf.get_config_dict(ospf_vrf_path, key_mangling=('-', '_'), get_first_key=True) + ospf = dict_helper_ospf_defaults(vrf_config['protocols']['ospf'], ospf_vrf_path) + vrf['name'][vrf_name]['protocols'].update({'ospf' : ospf}) + elif conf.exists_effective(ospf_vrf_path): + vrf['name'][vrf_name]['protocols'].update({'ospf' : {'deleted' : ''}}) + + # We need to check the CLI if the OSPFv3 node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + ospfv3_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'ospfv3'] + if 'ospfv3' in vrf_config.get('protocols', []): + ospfv3 = conf.get_config_dict(ospfv3_vrf_path, key_mangling=('-', '_'), get_first_key=True) + ospfv3 = dict_helper_ospfv3_defaults(vrf_config['protocols']['ospfv3'], ospfv3_vrf_path) + vrf['name'][vrf_name]['protocols'].update({'ospfv3' : ospfv3}) + elif conf.exists_effective(ospfv3_vrf_path): + vrf['name'][vrf_name]['protocols'].update({'ospfv3' : {'deleted' : ''}}) + + # We need to check the CLI if the static node is present and thus load in all the default + # values present on the CLI - that's why we have if conf.exists() + static_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'static'] + if 'static' in vrf_config.get('protocols', []): + static = conf.get_config_dict(static_vrf_path, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + # T3680 - get a list of all interfaces currently configured to use DHCP + tmp = get_dhcp_interfaces(conf, vrf_name) + if tmp: static.update({'dhcp' : tmp}) + tmp = get_pppoe_interfaces(conf, vrf_name) + if tmp: static.update({'pppoe' : tmp}) + + vrf['name'][vrf_name]['protocols'].update({'static': static}) + elif conf.exists_effective(static_vrf_path): + vrf['name'][vrf_name]['protocols'].update({'static': {'deleted' : ''}}) + + vrf_vni_path = ['vrf', 'name', vrf_name, 'vni'] + if conf.exists_effective(vrf_vni_path): + vrf_config.update({'vni': conf.return_effective_value(vrf_vni_path)}) + + dict.update({'vrf' : vrf}) + elif conf.exists_effective(vrf_cli_path): + effective_vrf = conf.get_config_dict(vrf_cli_path, key_mangling=('-', '_'), + get_first_key=False, + no_tag_node_value_mangle=True, + effective=True) + vrf = {'name' : {}} + for vrf_name, vrf_config in effective_vrf.get('name', {}).items(): + vrf['name'].update({vrf_name : {}}) + for protocol in frr_protocols: + if protocol in vrf_config.get('protocols', []): + # Create initial protocols key if not present + if 'protocols' not in vrf['name'][vrf_name]: + vrf['name'][vrf_name].update({'protocols' : {}}) + # All routing protocols are deleted when we pass this point + tmp = {'deleted' : ''} + + # Special treatment for BGP routing protocol + if protocol == 'bgp': + tmp['dependent_vrfs'] = {} + if 'name' in vrf: + tmp['dependent_vrfs'] = conf.get_config_dict( + vrf_cli_path, key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True, + effective=True) + # Add dependency on possible existing default VRF to this VRF + if 'bgp' in dict: + tmp['dependent_vrfs'].update({'default': {'protocols': { + 'bgp': dependent_vrfs_default}}}) + # We can safely delete ourself from the dependent VRF list + if vrf_name in tmp['dependent_vrfs']: + del tmp['dependent_vrfs'][vrf_name] + + # Update VRF related dict + vrf['name'][vrf_name]['protocols'].update({protocol : tmp}) + + dict.update({'vrf' : vrf}) + + print('======== < > ==========') + import pprint + pprint.pprint(dict) + print('======== < > ==========') + return dict diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 92996f2eea..4450dc16b3 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -420,7 +420,7 @@ def verify_common_route_maps(config): continue tmp = config[route_map] # Check if the specified route-map exists, if not error out - if dict_search(f'policy.route-map.{tmp}', config) == None: + if dict_search(f'policy.route_map.{tmp}', config) == None: raise ConfigError(f'Specified route-map "{tmp}" does not exist!') if 'redistribute' in config: @@ -434,7 +434,7 @@ def verify_route_map(route_map_name, config): recurring validation if a specified route-map exists! """ # Check if the specified route-map exists, if not error out - if dict_search(f'policy.route-map.{route_map_name}', config) == None: + if dict_search(f'policy.route_map.{route_map_name}', config) == None: raise ConfigError(f'Specified route-map "{route_map_name}" does not exist!') def verify_prefix_list(prefix_list, config, version=''): @@ -443,7 +443,7 @@ def verify_prefix_list(prefix_list, config, version=''): recurring validation if a specified prefix-list exists! """ # Check if the specified prefix-list exists, if not error out - if dict_search(f'policy.prefix-list{version}.{prefix_list}', config) == None: + if dict_search(f'policy.prefix_list{version}.{prefix_list}', config) == None: raise ConfigError(f'Specified prefix-list{version} "{prefix_list}" does not exist!') def verify_access_list(access_list, config, version=''): @@ -452,7 +452,7 @@ def verify_access_list(access_list, config, version=''): recurring validation if a specified prefix-list exists! """ # Check if the specified ACL exists, if not error out - if dict_search(f'policy.access-list{version}.{access_list}', config) == None: + if dict_search(f'policy.access_list{version}.{access_list}', config) == None: raise ConfigError(f'Specified access-list{version} "{access_list}" does not exist!') def verify_pki_certificate(config: dict, cert_name: str, no_password_protected: bool=False): @@ -537,3 +537,10 @@ def verify_eapol(config: dict): if 'ca_certificate' in config['eapol']: for ca_cert in config['eapol']['ca_certificate']: verify_pki_ca_certificate(config, ca_cert) + +def has_frr_protocol_in_dict(config_dict: dict, protocol: str, vrf: str=None) -> bool: + if vrf and protocol in (dict_search(f'vrf.name.{vrf}.protocols', config_dict) or []): + return True + if config_dict and protocol in config_dict: + return True + return False diff --git a/python/vyos/frrender.py b/python/vyos/frrender.py new file mode 100644 index 0000000000..015596a8fc --- /dev/null +++ b/python/vyos/frrender.py @@ -0,0 +1,156 @@ +# Copyright 2024 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see . + +""" +Library used to interface with FRRs mgmtd introduced in version 10.0 +""" + +import os + +from vyos.utils.file import write_file +from vyos.utils.process import rc_cmd +from vyos.template import render_to_string +from vyos import ConfigError + +DEBUG_ON = os.path.exists('/tmp/vyos.frr.debug') +DEBUG_ON = True + +def debug(message): + if not DEBUG_ON: + return + print(message) + +pim_daemon = 'pimd' + +frr_protocols = ['babel', 'bfd', 'bgp', 'eigrp', 'isis', 'mpls', 'nhrp', + 'openfabric', 'ospf', 'ospfv3', 'pim', 'pim6', 'rip', + 'ripng', 'rpki', 'segment_routing', 'static'] + +class FRRender: + def __init__(self): + self._frr_conf = '/run/frr/config/frr.conf' + + def generate(self, config): + if not isinstance(config, dict): + raise ValueError('config must be of type dict') + + def inline_helper(config_dict) -> str: + output = '!\n' + if 'babel' in config_dict and 'deleted' not in config_dict['babel']: + output += render_to_string('frr/babeld.frr.j2', config_dict['babel']) + output += '\n' + if 'bfd' in config_dict and 'deleted' not in config_dict['bfd']: + output += render_to_string('frr/bfdd.frr.j2', config_dict['bfd']) + output += '\n' + if 'bgp' in config_dict and 'deleted' not in config_dict['bgp']: + output += render_to_string('frr/bgpd.frr.j2', config_dict['bgp']) + output += '\n' + if 'eigrp' in config_dict and 'deleted' not in config_dict['eigrp']: + output += render_to_string('frr/eigrpd.frr.j2', config_dict['eigrp']) + output += '\n' + if 'isis' in config_dict and 'deleted' not in config_dict['isis']: + output += render_to_string('frr/isisd.frr.j2', config_dict['isis']) + output += '\n' + if 'mpls' in config_dict and 'deleted' not in config_dict['mpls']: + output += render_to_string('frr/ldpd.frr.j2', config_dict['mpls']) + output += '\n' + if 'openfabric' in config_dict and 'deleted' not in config_dict['openfabric']: + output += render_to_string('frr/fabricd.frr.j2', config_dict['openfabric']) + output += '\n' + if 'ospf' in config_dict and 'deleted' not in config_dict['ospf']: + output += render_to_string('frr/ospfd.frr.j2', config_dict['ospf']) + output += '\n' + if 'ospfv3' in config_dict and 'deleted' not in config_dict['ospfv3']: + output += render_to_string('frr/ospf6d.frr.j2', config_dict['ospfv3']) + output += '\n' + if 'pim' in config_dict and 'deleted' not in config_dict['pim']: + output += render_to_string('frr/pimd.frr.j2', config_dict['pim']) + output += '\n' + if 'pim6' in config_dict and 'deleted' not in config_dict['pim6']: + output += render_to_string('frr/pim6d.frr.j2', config_dict['pim6']) + output += '\n' + if 'policy' in config_dict and len(config_dict['policy']) > 0: + output += render_to_string('frr/policy.frr.j2', config_dict['policy']) + output += '\n' + if 'rip' in config_dict and 'deleted' not in config_dict['rip']: + output += render_to_string('frr/ripd.frr.j2', config_dict['rip']) + output += '\n' + if 'ripng' in config_dict and 'deleted' not in config_dict['ripng']: + output += render_to_string('frr/ripngd.frr.j2', config_dict['ripng']) + output += '\n' + if 'rpki' in config_dict and 'deleted' not in config_dict['rpki']: + output += render_to_string('frr/rpki.frr.j2', config_dict['rpki']) + output += '\n' + if 'segment_routing' in config_dict and 'deleted' not in config_dict['segment_routing']: + output += render_to_string('frr/zebra.segment_routing.frr.j2', config_dict['segment_routing']) + output += '\n' + if 'static' in config_dict and 'deleted' not in config_dict['static']: + output += render_to_string('frr/staticd.frr.j2', config_dict['static']) + output += '\n' + return output + + debug('======< RENDERING CONFIG >======') + # we can not reload an empty file, thus we always embed the marker + output = '!\n' + # Enable SNMP agentx support + # SNMP AgentX support cannot be disabled once enabled + if 'snmp' in config: + output += 'agentx\n' + # Add routing protocols in global VRF + output += inline_helper(config) + # Interface configuration for EVPN is not VRF related + if 'interfaces' in config: + output += render_to_string('frr/evpn.mh.frr.j2', {'interfaces' : config['interfaces']}) + output += '\n' + + if 'vrf' in config and 'name' in config['vrf']: + output += render_to_string('frr/zebra.vrf.route-map.frr.j2', config['vrf']) + '\n' + for vrf, vrf_config in config['vrf']['name'].items(): + if 'protocols' not in vrf_config: + continue + for protocol in vrf_config['protocols']: + vrf_config['protocols'][protocol]['vrf'] = vrf + + output += inline_helper(vrf_config['protocols']) + + debug(output) + debug('======< RENDERING CONFIG COMPLETE >======') + write_file(self._frr_conf, output) + if DEBUG_ON: write_file('/tmp/frr.conf.debug', output) + + def apply(self): + count = 0 + count_max = 5 + emsg = '' + while count < count_max: + count += 1 + print('FRR: Reloading configuration', count) + + cmdline = '/usr/lib/frr/frr-reload.py --reload' + if DEBUG_ON: + cmdline += ' --debug' + rc, emsg = rc_cmd(f'{cmdline} {self._frr_conf}') + if rc != 0: + debug('FRR configuration reload failed, retrying') + continue + debug(emsg) + debug('======< DONE APPLYING CONFIG >======') + break + if count >= count_max: + raise ConfigError(emsg) + + def save_configuration(): + """ T3217: Save FRR configuration to /run/frr/config/frr.conf """ + return cmd('/usr/bin/vtysh -n --writeconfig') diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py index a0c6ab0550..7ea1b610e0 100755 --- a/smoketest/scripts/cli/test_policy.py +++ b/smoketest/scripts/cli/test_policy.py @@ -1945,7 +1945,7 @@ def test_frr_individual_remove_T6283_T6250(self): local_preference = base_local_preference table = base_table for route_map in route_maps: - config = self.getFRRconfig(f'route-map {route_map} permit {seq}', end='') + config = self.getFRRconfig(f'route-map {route_map} permit {seq}', end='', endsection='^exit') self.assertIn(f' set local-preference {local_preference}', config) self.assertIn(f' set table {table}', config) local_preference += 20 @@ -1958,7 +1958,7 @@ def test_frr_individual_remove_T6283_T6250(self): local_preference = base_local_preference for route_map in route_maps: - config = self.getFRRconfig(f'route-map {route_map} permit {seq}', end='') + config = self.getFRRconfig(f'route-map {route_map} permit {seq}', end='', endsection='^exit') self.assertIn(f' set local-preference {local_preference}', config) local_preference += 20 @@ -1972,7 +1972,7 @@ def test_frr_individual_remove_T6283_T6250(self): self.cli_commit() for route_map in route_maps: - config = self.getFRRconfig(f'route-map {route_map} permit {seq}', end='') + config = self.getFRRconfig(f'route-map {route_map} permit {seq}', end='', endsection='^exit') self.assertIn(f' set as-path prepend {prepend}', config) for route_map in route_maps: @@ -1981,7 +1981,7 @@ def test_frr_individual_remove_T6283_T6250(self): self.cli_commit() for route_map in route_maps: - config = self.getFRRconfig(f'route-map {route_map} permit {seq}', end='') + config = self.getFRRconfig(f'route-map {route_map} permit {seq}', end='', endsection='^exit') self.assertNotIn(f' set', config) def sort_ip(output): diff --git a/smoketest/scripts/cli/test_protocols_bfd.py b/smoketest/scripts/cli/test_protocols_bfd.py index 716d0a8068..9f178c821a 100755 --- a/smoketest/scripts/cli/test_protocols_bfd.py +++ b/smoketest/scripts/cli/test_protocols_bfd.py @@ -22,6 +22,7 @@ PROCESS_NAME = 'bfdd' base_path = ['protocols', 'bfd'] +frr_endsection = '^ exit' dum_if = 'dum1001' vrf_name = 'red' @@ -130,7 +131,7 @@ def test_bfd_peer(self): self.cli_commit() # Verify FRR bgpd configuration - frrconfig = self.getFRRconfig('bfd', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('bfd', endsection='^exit', daemon=PROCESS_NAME) for peer, peer_config in peers.items(): tmp = f'peer {peer}' if 'multihop' in peer_config: @@ -143,8 +144,8 @@ def test_bfd_peer(self): tmp += f' vrf {peer_config["vrf"]}' self.assertIn(tmp, frrconfig) - peerconfig = self.getFRRconfig(f' peer {peer}', end='', daemon=PROCESS_NAME) - + peerconfig = self.getFRRconfig(f' peer {peer}', end='', endsection=frr_endsection, + daemon=PROCESS_NAME) if 'echo_mode' in peer_config: self.assertIn(f'echo-mode', peerconfig) if 'intv_echo' in peer_config: @@ -206,7 +207,7 @@ def test_bfd_profile(self): # Verify FRR bgpd configuration for profile, profile_config in profiles.items(): - config = self.getFRRconfig(f' profile {profile}', endsection='^ !') + config = self.getFRRconfig(f' profile {profile}', endsection=frr_endsection) if 'echo_mode' in profile_config: self.assertIn(f' echo-mode', config) if 'intv_echo' in profile_config: @@ -228,7 +229,8 @@ def test_bfd_profile(self): self.assertNotIn(f'shutdown', config) for peer, peer_config in peers.items(): - peerconfig = self.getFRRconfig(f' peer {peer}', end='', daemon=PROCESS_NAME) + peerconfig = self.getFRRconfig(f' peer {peer}', end='', + endsection=frr_endsection, daemon=PROCESS_NAME) if 'profile' in peer_config: self.assertIn(f' profile {peer_config["profile"]}', peerconfig) diff --git a/smoketest/scripts/cli/test_protocols_static.py b/smoketest/scripts/cli/test_protocols_static.py index f676e2a521..980bc6e7f9 100755 --- a/smoketest/scripts/cli/test_protocols_static.py +++ b/smoketest/scripts/cli/test_protocols_static.py @@ -33,7 +33,11 @@ '192.0.2.110' : { 'distance' : '110', 'interface' : 'eth0' }, '192.0.2.120' : { 'distance' : '120', 'disable' : '' }, '192.0.2.130' : { 'bfd' : '' }, - '192.0.2.140' : { 'bfd_source' : '192.0.2.10' }, + '192.0.2.131' : { 'bfd' : '', + 'bfd_profile' : 'vyos1' }, + '192.0.2.140' : { 'bfd' : '', + 'bfd_source' : '192.0.2.10', + 'bfd_profile' : 'vyos2' }, }, 'interface' : { 'eth0' : { 'distance' : '130' }, @@ -114,6 +118,45 @@ }, } +multicast_routes = { + '224.0.0.0/24' : { + 'next_hop' : { + '224.203.0.1' : { }, + '224.203.0.2' : { 'distance' : '110'}, + }, + }, + '224.1.0.0/24' : { + 'next_hop' : { + '224.205.0.1' : { 'disable' : {} }, + '224.205.0.2' : { 'distance' : '110'}, + }, + }, + '224.2.0.0/24' : { + 'next_hop' : { + '1.2.3.0' : { }, + '1.2.3.1' : { 'distance' : '110'}, + }, + }, + '224.10.0.0/24' : { + 'interface' : { + 'eth1' : { 'disable' : {} }, + 'eth2' : { 'distance' : '110'}, + }, + }, + '224.11.0.0/24' : { + 'interface' : { + 'eth0' : { }, + 'eth1' : { 'distance' : '10'}, + }, + }, + '224.12.0.0/24' : { + 'interface' : { + 'eth0' : { }, + 'eth1' : { 'distance' : '200'}, + }, + }, +} + tables = ['80', '81', '82'] class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase): @@ -138,7 +181,6 @@ def tearDown(self): self.assertFalse(v6route) def test_01_static(self): - bfd_profile = 'vyos-test' for route, route_config in routes.items(): route_type = 'route' if is_ipv6(route): @@ -156,9 +198,12 @@ def test_01_static(self): if 'vrf' in next_hop_config: self.cli_set(base + ['next-hop', next_hop, 'vrf', next_hop_config['vrf']]) if 'bfd' in next_hop_config: - self.cli_set(base + ['next-hop', next_hop, 'bfd', 'profile', bfd_profile ]) - if 'bfd_source' in next_hop_config: - self.cli_set(base + ['next-hop', next_hop, 'bfd', 'multi-hop', 'source', next_hop_config['bfd_source'], 'profile', bfd_profile]) + self.cli_set(base + ['next-hop', next_hop, 'bfd']) + if 'bfd_profile' in next_hop_config: + self.cli_set(base + ['next-hop', next_hop, 'bfd', 'profile', next_hop_config['bfd_profile']]) + if 'bfd_source' in next_hop_config: + self.cli_set(base + ['next-hop', next_hop, 'bfd', 'multi-hop']) + self.cli_set(base + ['next-hop', next_hop, 'bfd', 'source-address', next_hop_config['bfd_source']]) if 'segments' in next_hop_config: self.cli_set(base + ['next-hop', next_hop, 'segments', next_hop_config['segments']]) @@ -217,9 +262,11 @@ def test_01_static(self): if 'vrf' in next_hop_config: tmp += ' nexthop-vrf ' + next_hop_config['vrf'] if 'bfd' in next_hop_config: - tmp += ' bfd profile ' + bfd_profile - if 'bfd_source' in next_hop_config: - tmp += ' bfd multi-hop source ' + next_hop_config['bfd_source'] + ' profile ' + bfd_profile + tmp += ' bfd' + if 'bfd_source' in next_hop_config: + tmp += ' multi-hop source ' + next_hop_config['bfd_source'] + if 'bfd_profile' in next_hop_config: + tmp += ' profile ' + next_hop_config['bfd_profile'] if 'segments' in next_hop_config: tmp += ' segments ' + next_hop_config['segments'] @@ -426,7 +473,7 @@ def test_03_static_vrf(self): self.assertEqual(tmp['linkinfo']['info_kind'], 'vrf') # Verify FRR bgpd configuration - frrconfig = self.getFRRconfig(f'vrf {vrf}') + frrconfig = self.getFRRconfig(f'vrf {vrf}', endsection='^exit-vrf') self.assertIn(f'vrf {vrf}', frrconfig) # Verify routes @@ -478,5 +525,48 @@ def test_03_static_vrf(self): self.assertIn(tmp, frrconfig) + def test_04_static_multicast(self): + for route, route_config in multicast_routes.items(): + if 'next_hop' in route_config: + base = base_path + ['multicast', 'route', route] + for next_hop, next_hop_config in route_config['next_hop'].items(): + self.cli_set(base + ['next-hop', next_hop]) + if 'distance' in next_hop_config: + self.cli_set(base + ['next-hop', next_hop, 'distance', next_hop_config['distance']]) + if 'disable' in next_hop_config: + self.cli_set(base + ['next-hop', next_hop, 'disable']) + + if 'interface' in route_config: + base = base_path + ['multicast', 'route', route] + for next_hop, next_hop_config in route_config['interface'].items(): + self.cli_set(base + ['interface', next_hop]) + if 'distance' in next_hop_config: + self.cli_set(base + ['interface', next_hop, 'distance', next_hop_config['distance']]) + + self.cli_commit() + + # Verify FRR configuration + frrconfig = self.getFRRconfig('ip mroute', end='') + for route, route_config in multicast_routes.items(): + if 'next_hop' in route_config: + for next_hop, next_hop_config in route_config['next_hop'].items(): + tmp = f'ip mroute {route} {next_hop}' + if 'distance' in next_hop_config: + tmp += ' ' + next_hop_config['distance'] + if 'disable' in next_hop_config: + self.assertNotIn(tmp, frrconfig) + else: + self.assertIn(tmp, frrconfig) + + if 'next_hop_interface' in route_config: + for next_hop, next_hop_config in route_config['next_hop_interface'].items(): + tmp = f'ip mroute {route} {next_hop}' + if 'distance' in next_hop_config: + tmp += ' ' + next_hop_config['distance'] + if 'disable' in next_hop_config: + self.assertNotIn(tmp, frrconfig) + else: + self.assertIn(tmp, frrconfig) + if __name__ == '__main__': - unittest.main(verbosity=2, failfast=True) + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_static_multicast.py b/smoketest/scripts/cli/test_protocols_static_multicast.py deleted file mode 100755 index 9fdda236f6..0000000000 --- a/smoketest/scripts/cli/test_protocols_static_multicast.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2024 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 unittest - -from base_vyostest_shim import VyOSUnitTestSHIM - - -base_path = ['protocols', 'static', 'multicast'] - - -class TestProtocolsStaticMulticast(VyOSUnitTestSHIM.TestCase): - - def tearDown(self): - self.cli_delete(base_path) - self.cli_commit() - - mroute = self.getFRRconfig('ip mroute', end='') - self.assertFalse(mroute) - - def test_01_static_multicast(self): - - self.cli_set(base_path + ['route', '224.202.0.0/24', 'next-hop', '224.203.0.1']) - self.cli_set(base_path + ['interface-route', '224.203.0.0/24', 'next-hop-interface', 'eth0']) - - self.cli_commit() - - # Verify FRR bgpd configuration - frrconfig = self.getFRRconfig('ip mroute', end='') - - self.assertIn('ip mroute 224.202.0.0/24 224.203.0.1', frrconfig) - self.assertIn('ip mroute 224.203.0.0/24 eth0', frrconfig) - - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/src/conf_mode/interfaces_bonding.py b/src/conf_mode/interfaces_bonding.py index 633fb797c9..adea8fc633 100755 --- a/src/conf_mode/interfaces_bonding.py +++ b/src/conf_mode/interfaces_bonding.py @@ -17,6 +17,7 @@ from sys import exit from vyos.config import Config +from vyos.configdict import get_frrender_dict from vyos.configdict import get_interface_dict from vyos.configdict import is_node_changed from vyos.configdict import leaf_node_changed @@ -30,10 +31,10 @@ from vyos.configverify import verify_mtu_ipv6 from vyos.configverify import verify_vlan_config from vyos.configverify import verify_vrf +from vyos.frrender import FRRender from vyos.ifconfig import BondIf from vyos.ifconfig.ethernet import EthernetIf from vyos.ifconfig import Section -from vyos.template import render_to_string from vyos.utils.assertion import assert_mac from vyos.utils.dict import dict_search from vyos.utils.dict import dict_to_paths_values @@ -42,9 +43,9 @@ from vyos.configdict import has_vrf_configured from vyos.configdep import set_dependents, call_dependents from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() +frrender = FRRender() def get_bond_mode(mode): if mode == 'round-robin': @@ -87,10 +88,13 @@ def get_config(config=None): bond['mode'] = get_bond_mode(bond['mode']) tmp = is_node_changed(conf, base + [ifname, 'mode']) - if tmp: bond['shutdown_required'] = {} + if tmp: bond.update({'shutdown_required' : {}}) tmp = is_node_changed(conf, base + [ifname, 'lacp-rate']) - if tmp: bond['shutdown_required'] = {} + if tmp: bond.update({'shutdown_required' : {}}) + + tmp = is_node_changed(conf, base + [ifname, 'evpn']) + if tmp: bond.update({'frrender' : get_frrender_dict(conf)}) # determine which members have been removed interfaces_removed = leaf_node_changed(conf, base + [ifname, 'member', 'interface']) @@ -260,16 +264,16 @@ def verify(bond): return None def generate(bond): - bond['frr_zebra_config'] = '' - if 'deleted' not in bond: - bond['frr_zebra_config'] = render_to_string('frr/evpn.mh.frr.j2', bond) + if 'frrender' in bond: + frrender.generate(bond['frrender']) return None def apply(bond): - ifname = bond['ifname'] - b = BondIf(ifname) + if 'frrender' in bond: + frrender.apply() + + b = BondIf(bond['ifname']) if 'deleted' in bond: - # delete interface b.remove() else: b.update(bond) @@ -281,16 +285,6 @@ def apply(bond): raise ConfigError('Error in updating ethernet interface ' 'after deleting it from bond') - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - - # The route-map used for the FIB (zebra) is part of the zebra daemon - frr_cfg.load_configuration(frr.mgmt_daemon) - frr_cfg.modify_section(f'^interface {ifname}', stop_pattern='^exit', remove_stop_mark=True) - if 'frr_zebra_config' in bond: - frr_cfg.add_before(frr.default_add_before, bond['frr_zebra_config']) - frr_cfg.commit_configuration() - return None if __name__ == '__main__': diff --git a/src/conf_mode/interfaces_ethernet.py b/src/conf_mode/interfaces_ethernet.py index edbbb00c91..6a035e9e95 100755 --- a/src/conf_mode/interfaces_ethernet.py +++ b/src/conf_mode/interfaces_ethernet.py @@ -20,6 +20,7 @@ from vyos.base import Warning from vyos.config import Config +from vyos.configdict import get_frrender_dict from vyos.configdict import get_interface_dict from vyos.configdict import is_node_changed from vyos.configverify import verify_address @@ -33,17 +34,17 @@ from vyos.configverify import verify_bond_bridge_member from vyos.configverify import verify_eapol from vyos.ethtool import Ethtool +from vyos.frrender import FRRender from vyos.ifconfig import EthernetIf from vyos.ifconfig import BondIf -from vyos.template import render_to_string from vyos.utils.dict import dict_search from vyos.utils.dict import dict_to_paths_values from vyos.utils.dict import dict_set from vyos.utils.dict import dict_delete from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() +frrender = FRRender() def update_bond_options(conf: Config, eth_conf: dict) -> list: """ @@ -164,6 +165,9 @@ def get_config(config=None): tmp = is_node_changed(conf, base + [ifname, 'duplex']) if tmp: ethernet.update({'speed_duplex_changed': {}}) + tmp = is_node_changed(conf, base + [ifname, 'evpn']) + if tmp: ethernet.update({'frrender' : get_frrender_dict(conf)}) + return ethernet def verify_speed_duplex(ethernet: dict, ethtool: Ethtool): @@ -318,32 +322,20 @@ def verify_ethernet(ethernet): return None def generate(ethernet): - if 'deleted' in ethernet: - return None - - ethernet['frr_zebra_config'] = '' - if 'deleted' not in ethernet: - ethernet['frr_zebra_config'] = render_to_string('frr/evpn.mh.frr.j2', ethernet) - + if 'frrender' in ethernet: + frrender.generate(ethernet['frrender']) return None def apply(ethernet): - ifname = ethernet['ifname'] + if 'frrender' in ethernet: + frrender.apply() - e = EthernetIf(ifname) + e = EthernetIf(ethernet['ifname']) if 'deleted' in ethernet: - # delete interface e.remove() else: e.update(ethernet) - - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(frr.mgmt_daemon) - frr_cfg.modify_section(f'^interface {ifname}', stop_pattern='^exit', remove_stop_mark=True) - if 'frr_zebra_config' in ethernet: - frr_cfg.add_before(frr.default_add_before, ethernet['frr_zebra_config']) - frr_cfg.commit_configuration() + return None if __name__ == '__main__': try: diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py index aef9b96c4a..e6b6d474af 100755 --- a/src/conf_mode/policy.py +++ b/src/conf_mode/policy.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021-2022 VyOS maintainers and contributors +# Copyright (C) 2021-2024 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -17,15 +17,16 @@ from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge -from vyos.template import render_to_string +from vyos.configdict import get_frrender_dict +from vyos.configverify import has_frr_protocol_in_dict +from vyos.frrender import FRRender +from vyos.frrender import frr_protocols from vyos.utils.dict import dict_search from vyos import ConfigError -from vyos import frr from vyos import airbag - airbag.enable() +frrender = FRRender() def community_action_compatibility(actions: dict) -> bool: """ @@ -87,31 +88,27 @@ def get_config(config=None): else: conf = Config() - base = ['policy'] - policy = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True) - - # We also need some additional information from the config, prefix-lists - # and route-maps for instance. They will be used in verify(). - # - # XXX: one MUST always call this without the key_mangling() option! See - # vyos.configverify.verify_common_route_maps() for more information. - tmp = conf.get_config_dict(['protocols'], key_mangling=('-', '_'), - no_tag_node_value_mangle=True) - # Merge policy dict into "regular" config dict - policy = dict_merge(tmp, policy) - return policy - - -def verify(policy): - if not policy: + return get_frrender_dict(conf) + + +def verify(config_dict): + if not has_frr_protocol_in_dict(config_dict, 'policy'): return None - for policy_type in ['access_list', 'access_list6', 'as_path_list', - 'community_list', 'extcommunity_list', - 'large_community_list', - 'prefix_list', 'prefix_list6', 'route_map']: + policy_types = ['access_list', 'access_list6', 'as_path_list', + 'community_list', 'extcommunity_list', + 'large_community_list', 'prefix_list', + 'prefix_list6', 'route_map'] + + policy = config_dict['policy'] + for protocol in frr_protocols: + if protocol not in config_dict: + continue + if 'protocol' not in policy: + policy.update({'protocol': {}}) + policy['protocol'].update({protocol : config_dict[protocol]}) + + for policy_type in policy_types: # Bail out early and continue with next policy type if policy_type not in policy: continue @@ -246,69 +243,33 @@ def verify(policy): # When the "routing policy" changes and policies, route-maps etc. are deleted, # it is our responsibility to verify that the policy can not be deleted if it # is used by any routing protocol - if 'protocols' in policy: - for policy_type in ['access_list', 'access_list6', 'as_path_list', - 'community_list', - 'extcommunity_list', 'large_community_list', - 'prefix_list', 'route_map']: - if policy_type in policy: - for policy_name in list(set(routing_policy_find(policy_type, - policy[ - 'protocols']))): - found = False - if policy_name in policy[policy_type]: - found = True - # BGP uses prefix-list for selecting both an IPv4 or IPv6 AFI related - # list - we need to go the extra mile here and check both prefix-lists - if policy_type == 'prefix_list' and 'prefix_list6' in policy and policy_name in \ - policy['prefix_list6']: - found = True - if not found: - tmp = policy_type.replace('_', '-') - raise ConfigError( - f'Can not delete {tmp} "{policy_name}", still in use!') - - return None - + # Check if any routing protocol is activated + if 'protocol' in policy: + for policy_type in policy_types: + for policy_name in list(set(routing_policy_find(policy_type, policy['protocol']))): + found = False + if policy_type in policy and policy_name in policy[policy_type]: + found = True + # BGP uses prefix-list for selecting both an IPv4 or IPv6 AFI related + # list - we need to go the extra mile here and check both prefix-lists + if policy_type == 'prefix_list' and 'prefix_list6' in policy and policy_name in \ + policy['prefix_list6']: + found = True + if not found: + tmp = policy_type.replace('_', '-') + raise ConfigError( + f'Can not delete {tmp} "{policy_name}", still in use!') -def generate(policy): - if not policy: - return None - policy['new_frr_config'] = render_to_string('frr/policy.frr.j2', policy) return None -def apply(policy): - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - - # The route-map used for the FIB (zebra) is part of the zebra daemon - frr_cfg.load_configuration(frr.bgp_daemon) - frr_cfg.modify_section(r'^bgp as-path access-list .*') - frr_cfg.modify_section(r'^bgp community-list .*') - frr_cfg.modify_section(r'^bgp extcommunity-list .*') - frr_cfg.modify_section(r'^bgp large-community-list .*') - frr_cfg.modify_section(r'^route-map .*', stop_pattern='^exit', - remove_stop_mark=True) - if 'new_frr_config' in policy: - frr_cfg.add_before(frr.default_add_before, policy['new_frr_config']) - frr_cfg.commit_configuration(frr.bgp_daemon) - - # The route-map used for the FIB (zebra) is part of the zebra daemon - frr_cfg.load_configuration(frr.zebra_daemon) - frr_cfg.modify_section(r'^access-list .*') - frr_cfg.modify_section(r'^ipv6 access-list .*') - frr_cfg.modify_section(r'^ip prefix-list .*') - frr_cfg.modify_section(r'^ipv6 prefix-list .*') - frr_cfg.modify_section(r'^route-map .*', stop_pattern='^exit', - remove_stop_mark=True) - if 'new_frr_config' in policy: - frr_cfg.add_before(frr.default_add_before, policy['new_frr_config']) - frr_cfg.commit_configuration(frr.zebra_daemon) +def generate(config_dict): + frrender.generate(config_dict) +def apply(config_dict): + frrender.apply() return None - if __name__ == '__main__': try: c = get_config() diff --git a/src/conf_mode/protocols_babel.py b/src/conf_mode/protocols_babel.py index 06fd9b9b64..f9d5e45a03 100755 --- a/src/conf_mode/protocols_babel.py +++ b/src/conf_mode/protocols_babel.py @@ -17,63 +17,33 @@ from sys import exit from vyos.config import Config -from vyos.config import config_dict_merge -from vyos.configdict import dict_merge -from vyos.configdict import node_changed +from vyos.configdict import get_frrender_dict +from vyos.configverify import has_frr_protocol_in_dict from vyos.configverify import verify_access_list from vyos.configverify import verify_prefix_list +from vyos.frrender import FRRender from vyos.utils.dict import dict_search -from vyos.template import render_to_string from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() +frrender = FRRender() + def get_config(config=None): if config: conf = config else: conf = Config() - base = ['protocols', 'babel'] - babel = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True) - - # FRR has VRF support for different routing daemons. As interfaces belong - # to VRFs - or the global VRF, we need to check for changed interfaces so - # that they will be properly rendered for the FRR config. Also this eases - # removal of interfaces from the running configuration. - interfaces_removed = node_changed(conf, base + ['interface']) - if interfaces_removed: - babel['interface_removed'] = list(interfaces_removed) - - # Bail out early if configuration tree does not exist - if not conf.exists(base): - babel.update({'deleted' : ''}) - return babel - - # We have gathered the dict representation of the CLI, but there are default - # values which we need to update into the dictionary retrieved. - default_values = conf.get_config_defaults(base, key_mangling=('-', '_'), - get_first_key=True, - recursive=True) - # merge in default values - babel = config_dict_merge(default_values, babel) + return get_frrender_dict(conf) - # We also need some additional information from the config, prefix-lists - # and route-maps for instance. They will be used in verify(). - # - # XXX: one MUST always call this without the key_mangling() option! See - # vyos.configverify.verify_common_route_maps() for more information. - tmp = conf.get_config_dict(['policy']) - # Merge policy dict into "regular" config dict - babel = dict_merge(tmp, babel) - return babel - -def verify(babel): - if not babel: +def verify(config_dict): + if not has_frr_protocol_in_dict(config_dict, 'babel'): return None + babel = config_dict['babel'] + babel['policy'] = config_dict['policy'] + # verify distribute_list if "distribute_list" in babel: acl_keys = { @@ -120,30 +90,11 @@ def verify(babel): verify_prefix_list(prefix_list, babel, version='6' if address_family == 'ipv6' else '') -def generate(babel): - if not babel or 'deleted' in babel: - return None - - babel['new_frr_config'] = render_to_string('frr/babeld.frr.j2', babel) - return None - -def apply(babel): - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - - frr_cfg.load_configuration(frr.babel_daemon) - frr_cfg.modify_section('^router babel', stop_pattern='^exit', remove_stop_mark=True) - - for key in ['interface', 'interface_removed']: - if key not in babel: - continue - for interface in babel[key]: - frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True) - - if 'new_frr_config' in babel: - frr_cfg.add_before(frr.default_add_before, babel['new_frr_config']) - frr_cfg.commit_configuration(frr.babel_daemon) +def generate(config_dict): + frrender.generate(config_dict) +def apply(config_dict): + frrender.apply() return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index d94ec6a0d4..a0d7fdfb5c 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -15,36 +15,31 @@ # along with this program. If not, see . from vyos.config import Config +from vyos.configdict import get_frrender_dict from vyos.configverify import verify_vrf +from vyos.configverify import has_frr_protocol_in_dict +from vyos.frrender import FRRender from vyos.template import is_ipv6 -from vyos.template import render_to_string from vyos.utils.network import is_ipv6_link_local from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() +frrender = FRRender() + def get_config(config=None): if config: conf = config else: conf = Config() - base = ['protocols', 'bfd'] - bfd = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True) - # Bail out early if configuration tree does not exist - if not conf.exists(base): - return bfd - - bfd = conf.merge_defaults(bfd, recursive=True) - return bfd + return get_frrender_dict(conf) -def verify(bfd): - if not bfd: +def verify(config_dict): + if not has_frr_protocol_in_dict(config_dict, 'bfd'): return None + bfd = config_dict['bfd'] if 'peer' in bfd: for peer, peer_config in bfd['peer'].items(): # IPv6 link local peers require an explicit local address/interface @@ -83,20 +78,11 @@ def verify(bfd): return None -def generate(bfd): - if not bfd: - return None - bfd['new_frr_config'] = render_to_string('frr/bfdd.frr.j2', bfd) - -def apply(bfd): - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(frr.bfd_daemon) - frr_cfg.modify_section('^bfd', stop_pattern='^exit', remove_stop_mark=True) - if 'new_frr_config' in bfd: - frr_cfg.add_before(frr.default_add_before, bfd['new_frr_config']) - frr_cfg.commit_configuration(frr.bfd_daemon) +def generate(config_dict): + frrender.generate(config_dict) +def apply(config_dict): + frrender.apply() return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index e5c46aee66..989cf9b5c8 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -19,92 +19,35 @@ from vyos.base import Warning from vyos.config import Config -from vyos.configdict import dict_merge -from vyos.configdict import node_changed +from vyos.configdict import get_frrender_dict +from vyos.configverify import has_frr_protocol_in_dict from vyos.configverify import verify_prefix_list from vyos.configverify import verify_route_map from vyos.configverify import verify_vrf +from vyos.frrender import FRRender from vyos.template import is_ip from vyos.template import is_interface -from vyos.template import render_to_string from vyos.utils.dict import dict_search from vyos.utils.network import get_interface_vrf from vyos.utils.network import is_addr_assigned from vyos.utils.process import process_named_running -from vyos.utils.process import call from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() +frrender = FRRender() + +vrf = None +if len(argv) > 1: + vrf = argv[1] + def get_config(config=None): if config: conf = config else: conf = Config() - vrf = None - if len(argv) > 1: - vrf = argv[1] - - base_path = ['protocols', 'bgp'] - - # eqivalent of the C foo ? 'a' : 'b' statement - base = vrf and ['vrf', 'name', vrf, 'protocols', 'bgp'] or base_path - bgp = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) - - bgp['dependent_vrfs'] = conf.get_config_dict(['vrf', 'name'], - key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True) - - # Remove per interface MPLS configuration - get a list if changed - # nodes under the interface tagNode - interfaces_removed = node_changed(conf, base + ['interface']) - if interfaces_removed: - bgp['interface_removed'] = list(interfaces_removed) - - # Assign the name of our VRF context. This MUST be done before the return - # statement below, else on deletion we will delete the default instance - # instead of the VRF instance. - if vrf: - bgp.update({'vrf' : vrf}) - # We can not delete the BGP VRF instance if there is a L3VNI configured - # FRR L3VNI must be deleted first otherwise we will see error: - # "FRR error: Please unconfigure l3vni 3000" - tmp = ['vrf', 'name', vrf, 'vni'] - if conf.exists_effective(tmp): - bgp.update({'vni' : conf.return_effective_value(tmp)}) - # We can safely delete ourself from the dependent vrf list - if vrf in bgp['dependent_vrfs']: - del bgp['dependent_vrfs'][vrf] - - bgp['dependent_vrfs'].update({'default': {'protocols': { - 'bgp': conf.get_config_dict(base_path, key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True)}}}) - - if not conf.exists(base): - # If bgp instance is deleted then mark it - bgp.update({'deleted' : ''}) - return bgp - - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - bgp = conf.merge_defaults(bgp, recursive=True) - - # We also need some additional information from the config, prefix-lists - # and route-maps for instance. They will be used in verify(). - # - # XXX: one MUST always call this without the key_mangling() option! See - # vyos.configverify.verify_common_route_maps() for more information. - tmp = conf.get_config_dict(['policy']) - # Merge policy dict into "regular" config dict - bgp = dict_merge(tmp, bgp) - - return bgp - + return get_frrender_dict(conf) def verify_vrf_as_import(search_vrf_name: str, afi_name: str, vrfs_config: dict) -> bool: """ @@ -237,7 +180,18 @@ def verify_afi(peer_config, bgp_config): if tmp: return True return False -def verify(bgp): +def verify(config_dict): + global vrf + if not has_frr_protocol_in_dict(config_dict, 'bgp', vrf): + return None + + # eqivalent of the C foo ? 'a' : 'b' statement + bgp = vrf and config_dict['vrf']['name'][vrf]['protocols']['bgp'] or config_dict['bgp'] + bgp['policy'] = config_dict['policy'] + + if vrf: + bgp['vrf'] = vrf + if 'deleted' in bgp: if 'vrf' in bgp: # Cannot delete vrf if it exists in import vrf list in other vrfs @@ -252,8 +206,9 @@ def verify(bgp): for vrf, vrf_options in bgp['dependent_vrfs'].items(): if vrf != 'default': if dict_search('protocols.bgp', vrf_options): - raise ConfigError('Cannot delete default BGP instance, ' \ - 'dependent VRF instance(s) exist(s)!') + dependent_vrfs = ', '.join(bgp['dependent_vrfs'].keys()) + raise ConfigError(f'Cannot delete default BGP instance, ' \ + f'dependent VRF instance(s): {dependent_vrfs}') if 'vni' in vrf_options: raise ConfigError('Cannot delete default BGP instance, ' \ 'dependent L3VNI exists!') @@ -602,44 +557,11 @@ def verify(bgp): return None -def generate(bgp): - if not bgp or 'deleted' in bgp: - return None - - bgp['frr_bgpd_config'] = render_to_string('frr/bgpd.frr.j2', bgp) - return None - -def apply(bgp): - if 'deleted' in bgp: - # We need to ensure that the L3VNI is deleted first. - # This is not possible with old config backend - # priority bug - if {'vrf', 'vni'} <= set(bgp): - call('vtysh -c "conf t" -c "vrf {vrf}" -c "no vni {vni}"'.format(**bgp)) - - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - - # Generate empty helper string which can be ammended to FRR commands, it - # will be either empty (default VRF) or contain the "vrf 1: + vrf = argv[1] def get_config(config=None): if config: @@ -33,48 +37,16 @@ def get_config(config=None): else: conf = Config() - vrf = None - if len(argv) > 1: - vrf = argv[1] + return get_frrender_dict(conf) - base_path = ['protocols', 'eigrp'] +def verify(config_dict): + global vrf + if not has_frr_protocol_in_dict(config_dict, 'eigrp', vrf): + return None # eqivalent of the C foo ? 'a' : 'b' statement - base = vrf and ['vrf', 'name', vrf, 'protocols', 'eigrp'] or base_path - eigrp = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) - - # Assign the name of our VRF context. This MUST be done before the return - # statement below, else on deletion we will delete the default instance - # instead of the VRF instance. - if vrf: eigrp.update({'vrf' : vrf}) - - if not conf.exists(base): - eigrp.update({'deleted' : ''}) - if not vrf: - # We are running in the default VRF context, thus we can not delete - # our main EIGRP instance if there are dependent EIGRP VRF instances. - eigrp['dependent_vrfs'] = conf.get_config_dict(['vrf', 'name'], - key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True) - - return eigrp - - # We also need some additional information from the config, prefix-lists - # and route-maps for instance. They will be used in verify(). - # - # XXX: one MUST always call this without the key_mangling() option! See - # vyos.configverify.verify_common_route_maps() for more information. - tmp = conf.get_config_dict(['policy']) - # Merge policy dict into "regular" config dict - eigrp = dict_merge(tmp, eigrp) - - return eigrp - -def verify(eigrp): - if not eigrp or 'deleted' in eigrp: - return + eigrp = vrf and config_dict['vrf']['name'][vrf]['protocols']['eigrp'] or config_dict['eigrp'] + eigrp['policy'] = config_dict['policy'] if 'system_as' not in eigrp: raise ConfigError('EIGRP system-as must be defined!') @@ -82,28 +54,11 @@ def verify(eigrp): if 'vrf' in eigrp: verify_vrf(eigrp) -def generate(eigrp): - if not eigrp or 'deleted' in eigrp: - return None - - eigrp['frr_eigrpd_config'] = render_to_string('frr/eigrpd.frr.j2', eigrp) - -def apply(eigrp): - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - - # Generate empty helper string which can be ammended to FRR commands, it - # will be either empty (default VRF) or contain the "vrf 1: + vrf = argv[1] + def get_config(config=None): if config: conf = config else: conf = Config() - vrf = None - if len(argv) > 1: - vrf = argv[1] + return get_frrender_dict(conf) - base_path = ['protocols', 'isis'] - # eqivalent of the C foo ? 'a' : 'b' statement - base = vrf and ['vrf', 'name', vrf, 'protocols', 'isis'] or base_path - isis = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True) - - # Assign the name of our VRF context. This MUST be done before the return - # statement below, else on deletion we will delete the default instance - # instead of the VRF instance. - if vrf: isis['vrf'] = vrf - - # FRR has VRF support for different routing daemons. As interfaces belong - # to VRFs - or the global VRF, we need to check for changed interfaces so - # that they will be properly rendered for the FRR config. Also this eases - # removal of interfaces from the running configuration. - interfaces_removed = node_changed(conf, base + ['interface']) - if interfaces_removed: - isis['interface_removed'] = list(interfaces_removed) - - # Bail out early if configuration tree does no longer exist. this must - # be done after retrieving the list of interfaces to be removed. - if not conf.exists(base): - isis.update({'deleted' : ''}) - return isis - - # merge in default values - isis = conf.merge_defaults(isis, recursive=True) - - # We also need some additional information from the config, prefix-lists - # and route-maps for instance. They will be used in verify(). - # - # XXX: one MUST always call this without the key_mangling() option! See - # vyos.configverify.verify_common_route_maps() for more information. - tmp = conf.get_config_dict(['policy']) - # Merge policy dict into "regular" config dict - isis = dict_merge(tmp, isis) + # base_path = ['protocols', 'isis'] + + # # eqivalent of the C foo ? 'a' : 'b' statement + # base = vrf and ['vrf', 'name', vrf, 'protocols', 'isis'] or base_path + # isis = conf.get_config_dict(base, key_mangling=('-', '_'), + # get_first_key=True, + # no_tag_node_value_mangle=True) + + # # Assign the name of our VRF context. This MUST be done before the return + # # statement below, else on deletion we will delete the default instance + # # instead of the VRF instance. + # if vrf: isis['vrf'] = vrf + + # # FRR has VRF support for different routing daemons. As interfaces belong + # # to VRFs - or the global VRF, we need to check for changed interfaces so + # # that they will be properly rendered for the FRR config. Also this eases + # # removal of interfaces from the running configuration. + # interfaces_removed = node_changed(conf, base + ['interface']) + # if interfaces_removed: + # isis['interface_removed'] = list(interfaces_removed) + + # # Bail out early if configuration tree does no longer exist. this must + # # be done after retrieving the list of interfaces to be removed. + # if not conf.exists(base): + # isis.update({'deleted' : ''}) + # return isis + + # # merge in default values + # isis = conf.merge_defaults(isis, recursive=True) + + # # We also need some additional information from the config, prefix-lists + # # and route-maps for instance. They will be used in verify(). + # # + # # XXX: one MUST always call this without the key_mangling() option! See + # # vyos.configverify.verify_common_route_maps() for more information. + # tmp = conf.get_config_dict(['policy']) + # # Merge policy dict into "regular" config dict + # isis = dict_merge(tmp, isis) return isis -def verify(isis): - # bail out early - looks like removal from running config - if not isis or 'deleted' in isis: +def verify(config_dict): + global vrf + if not has_frr_protocol_in_dict(config_dict, 'isis', vrf): return None + # eqivalent of the C foo ? 'a' : 'b' statement + isis = vrf and config_dict['vrf']['name'][vrf]['protocols']['isis'] or config_dict['isis'] + isis['policy'] = config_dict['policy'] + + if 'deleted' in isis: + return None + + if vrf: + isis['vrf'] = vrf + if 'net' not in isis: raise ConfigError('Network entity is mandatory!') @@ -266,37 +280,11 @@ def verify(isis): return None -def generate(isis): - if not isis or 'deleted' in isis: - return None - - isis['frr_isisd_config'] = render_to_string('frr/isisd.frr.j2', isis) - return None - -def apply(isis): - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - - # Generate empty helper string which can be ammended to FRR commands, it - # will be either empty (default VRF) or contain the "vrf 1: + vrf = argv[1] def get_config(config=None): if config: @@ -39,85 +42,16 @@ def get_config(config=None): else: conf = Config() - vrf = None - if len(argv) > 1: - vrf = argv[1] + return get_frrender_dict(conf) - base_path = ['protocols', 'ospf'] +def verify(config_dict): + global vrf + if not has_frr_protocol_in_dict(config_dict, 'ospf', vrf): + return None # eqivalent of the C foo ? 'a' : 'b' statement - base = vrf and ['vrf', 'name', vrf, 'protocols', 'ospf'] or base_path - ospf = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True) - - # Assign the name of our VRF context. This MUST be done before the return - # statement below, else on deletion we will delete the default instance - # instead of the VRF instance. - if vrf: ospf['vrf'] = vrf - - # FRR has VRF support for different routing daemons. As interfaces belong - # to VRFs - or the global VRF, we need to check for changed interfaces so - # that they will be properly rendered for the FRR config. Also this eases - # removal of interfaces from the running configuration. - interfaces_removed = node_changed(conf, base + ['interface']) - if interfaces_removed: - ospf['interface_removed'] = list(interfaces_removed) - - # Bail out early if configuration tree does no longer exist. this must - # be done after retrieving the list of interfaces to be removed. - if not conf.exists(base): - ospf.update({'deleted' : ''}) - return ospf - - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = conf.get_config_defaults(**ospf.kwargs, recursive=True) - - # We have to cleanup the default dict, as default values could enable features - # which are not explicitly enabled on the CLI. Example: default-information - # originate comes with a default metric-type of 2, which will enable the - # entire default-information originate tree, even when not set via CLI so we - # need to check this first and probably drop that key. - if dict_search('default_information.originate', ospf) is None: - del default_values['default_information'] - if 'mpls_te' not in ospf: - del default_values['mpls_te'] - if 'graceful_restart' not in ospf: - del default_values['graceful_restart'] - for area_num in default_values.get('area', []): - if dict_search(f'area.{area_num}.area_type.nssa', ospf) is None: - del default_values['area'][area_num]['area_type']['nssa'] - - for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'rip', 'static']: - if dict_search(f'redistribute.{protocol}', ospf) is None: - del default_values['redistribute'][protocol] - if not bool(default_values['redistribute']): - del default_values['redistribute'] - - for interface in ospf.get('interface', []): - # We need to reload the defaults on every pass b/c of - # hello-multiplier dependency on dead-interval - # If hello-multiplier is set, we need to remove the default from - # dead-interval. - if 'hello_multiplier' in ospf['interface'][interface]: - del default_values['interface'][interface]['dead_interval'] - - ospf = config_dict_merge(default_values, ospf) - - # We also need some additional information from the config, prefix-lists - # and route-maps for instance. They will be used in verify(). - # - # XXX: one MUST always call this without the key_mangling() option! See - # vyos.configverify.verify_common_route_maps() for more information. - tmp = conf.get_config_dict(['policy']) - # Merge policy dict into "regular" config dict - ospf = dict_merge(tmp, ospf) - - return ospf - -def verify(ospf): - if not ospf: - return None + ospf = vrf and config_dict['vrf']['name'][vrf]['protocols']['ospf'] or config_dict['ospf'] + ospf['policy'] = config_dict['policy'] verify_common_route_maps(ospf) @@ -164,8 +98,7 @@ def verify(ospf): # interface is bound to our requesting VRF. Due to the VyOS # priorities the interface is bound to the VRF after creation of # the VRF itself, and before any routing protocol is configured. - if 'vrf' in ospf: - vrf = ospf['vrf'] + if vrf: tmp = get_interface_config(interface) if 'master' not in tmp or tmp['master'] != vrf: raise ConfigError(f'Interface "{interface}" is not a member of VRF "{vrf}"!') @@ -244,37 +177,11 @@ def verify(ospf): return None -def generate(ospf): - if not ospf or 'deleted' in ospf: - return None - - ospf['frr_ospfd_config'] = render_to_string('frr/ospfd.frr.j2', ospf) - return None - -def apply(ospf): - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - - # Generate empty helper string which can be ammended to FRR commands, it - # will be either empty (default VRF) or contain the "vrf 1: + vrf = argv[1] + def get_config(config=None): if config: conf = config else: conf = Config() - vrf = None - if len(argv) > 1: - vrf = argv[1] + return get_frrender_dict(conf) - base_path = ['protocols', 'ospfv3'] +def verify(config_dict): + global vrf + if not has_frr_protocol_in_dict(config_dict, 'ospfv3', vrf): + return None # eqivalent of the C foo ? 'a' : 'b' statement - base = vrf and ['vrf', 'name', vrf, 'protocols', 'ospfv3'] or base_path - ospfv3 = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - - # Assign the name of our VRF context. This MUST be done before the return - # statement below, else on deletion we will delete the default instance - # instead of the VRF instance. - if vrf: ospfv3['vrf'] = vrf - - # FRR has VRF support for different routing daemons. As interfaces belong - # to VRFs - or the global VRF, we need to check for changed interfaces so - # that they will be properly rendered for the FRR config. Also this eases - # removal of interfaces from the running configuration. - interfaces_removed = node_changed(conf, base + ['interface']) - if interfaces_removed: - ospfv3['interface_removed'] = list(interfaces_removed) - - # Bail out early if configuration tree does no longer exist. this must - # be done after retrieving the list of interfaces to be removed. - if not conf.exists(base): - ospfv3.update({'deleted' : ''}) - return ospfv3 - - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = conf.get_config_defaults(**ospfv3.kwargs, - recursive=True) - - # We have to cleanup the default dict, as default values could enable features - # which are not explicitly enabled on the CLI. Example: default-information - # originate comes with a default metric-type of 2, which will enable the - # entire default-information originate tree, even when not set via CLI so we - # need to check this first and probably drop that key. - if dict_search('default_information.originate', ospfv3) is None: - del default_values['default_information'] - if 'graceful_restart' not in ospfv3: - del default_values['graceful_restart'] - - for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'ripng', 'static']: - if dict_search(f'redistribute.{protocol}', ospfv3) is None: - del default_values['redistribute'][protocol] - if not bool(default_values['redistribute']): - del default_values['redistribute'] - - default_values.pop('interface', {}) - - # merge in remaining default values - ospfv3 = config_dict_merge(default_values, ospfv3) - - # We also need some additional information from the config, prefix-lists - # and route-maps for instance. They will be used in verify(). - # - # XXX: one MUST always call this without the key_mangling() option! See - # vyos.configverify.verify_common_route_maps() for more information. - tmp = conf.get_config_dict(['policy']) - # Merge policy dict into "regular" config dict - ospfv3 = dict_merge(tmp, ospfv3) - - return ospfv3 - -def verify(ospfv3): - if not ospfv3: - return None + ospfv3 = vrf and config_dict['vrf']['name'][vrf]['protocols']['ospfv3'] or config_dict['ospfv3'] + ospfv3['policy'] = config_dict['policy'] verify_common_route_maps(ospfv3) @@ -137,45 +82,18 @@ def verify(ospfv3): # interface is bound to our requesting VRF. Due to the VyOS # priorities the interface is bound to the VRF after creation of # the VRF itself, and before any routing protocol is configured. - if 'vrf' in ospfv3: - vrf = ospfv3['vrf'] + if vrf: tmp = get_interface_config(interface) if 'master' not in tmp or tmp['master'] != vrf: raise ConfigError(f'Interface "{interface}" is not a member of VRF "{vrf}"!') return None -def generate(ospfv3): - if not ospfv3 or 'deleted' in ospfv3: - return None - - ospfv3['new_frr_config'] = render_to_string('frr/ospf6d.frr.j2', ospfv3) - return None - -def apply(ospfv3): - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - - # Generate empty helper string which can be ammended to FRR commands, it - # will be either empty (default VRF) or contain the "vrf . +from ipaddress import IPv4Network from sys import exit from sys import argv from vyos.config import Config -from vyos.configdict import dict_merge -from vyos.configdict import get_dhcp_interfaces -from vyos.configdict import get_pppoe_interfaces +from vyos.configdict import get_frrender_dict +from vyos.configverify import has_frr_protocol_in_dict from vyos.configverify import verify_common_route_maps from vyos.configverify import verify_vrf +from vyos.frrender import FRRender from vyos.template import render -from vyos.template import render_to_string from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() +frrender = FRRender() + +vrf = None +if len(argv) > 1: + vrf = argv[1] config_file = '/etc/iproute2/rt_tables.d/vyos-static.conf' @@ -38,36 +42,17 @@ def get_config(config=None): else: conf = Config() - vrf = None - if len(argv) > 1: - vrf = argv[1] + return get_frrender_dict(conf) + +def verify(config_dict): + global vrf + if not has_frr_protocol_in_dict(config_dict, 'static', vrf): + return None - base_path = ['protocols', 'static'] # eqivalent of the C foo ? 'a' : 'b' statement - base = vrf and ['vrf', 'name', vrf, 'protocols', 'static'] or base_path - static = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) - - # Assign the name of our VRF context - if vrf: static['vrf'] = vrf - - # We also need some additional information from the config, prefix-lists - # and route-maps for instance. They will be used in verify(). - # - # XXX: one MUST always call this without the key_mangling() option! See - # vyos.configverify.verify_common_route_maps() for more information. - tmp = conf.get_config_dict(['policy']) - # Merge policy dict into "regular" config dict - static = dict_merge(tmp, static) - - # T3680 - get a list of all interfaces currently configured to use DHCP - tmp = get_dhcp_interfaces(conf, vrf) - if tmp: static.update({'dhcp' : tmp}) - tmp = get_pppoe_interfaces(conf, vrf) - if tmp: static.update({'pppoe' : tmp}) - - return static - -def verify(static): + static = vrf and config_dict['vrf']['name'][vrf]['protocols']['static'] or config_dict['static'] + static['policy'] = config_dict['policy'] + verify_common_route_maps(static) for route in ['route', 'route6']: @@ -90,33 +75,28 @@ def verify(static): raise ConfigError(f'Can not use both blackhole and reject for '\ f'prefix "{prefix}"!') + if 'multicast' in static and 'route' in static['multicast']: + for prefix, prefix_options in static['multicast']['route'].items(): + if not IPv4Network(prefix).is_multicast: + raise ConfigError(f'{prefix} is not a multicast network!') + return None -def generate(static): - if not static: +def generate(config_dict): + global vrf + if not has_frr_protocol_in_dict(config_dict, 'static', vrf): return None + # eqivalent of the C foo ? 'a' : 'b' statement + static = vrf and config_dict['vrf']['name'][vrf]['protocols']['static'] or config_dict['static'] + # Put routing table names in /etc/iproute2/rt_tables render(config_file, 'iproute2/static.conf.j2', static) - static['new_frr_config'] = render_to_string('frr/staticd.frr.j2', static) + frrender.generate(config_dict) return None def apply(static): - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(frr.mgmt_daemon) - - if 'vrf' in static: - vrf = static['vrf'] - frr_cfg.modify_section(f'^vrf {vrf}', stop_pattern='^exit-vrf', remove_stop_mark=True) - else: - frr_cfg.modify_section(r'^ip route .*') - frr_cfg.modify_section(r'^ipv6 route .*') - - if 'new_frr_config' in static: - frr_cfg.add_before(frr.default_add_before, static['new_frr_config']) - frr_cfg.commit_configuration() - + frrender.apply() return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_static_multicast.py b/src/conf_mode/protocols_static_multicast.py deleted file mode 100755 index 4393f3ed38..0000000000 --- a/src/conf_mode/protocols_static_multicast.py +++ /dev/null @@ -1,133 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2020-2024 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 . - - -from ipaddress import IPv4Address -from sys import exit - -from vyos import ConfigError -from vyos import frr -from vyos.config import Config -from vyos.template import render_to_string - -from vyos import airbag -airbag.enable() - -config_file = r'/tmp/static_mcast.frr' - -# Get configuration for static multicast route -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - mroute = { - 'old_mroute' : {}, - 'mroute' : {} - } - - base_path = "protocols static multicast" - - if not (conf.exists(base_path) or conf.exists_effective(base_path)): - return None - - conf.set_level(base_path) - - # Get multicast effective routes - for route in conf.list_effective_nodes('route'): - mroute['old_mroute'][route] = {} - for next_hop in conf.list_effective_nodes('route {0} next-hop'.format(route)): - mroute['old_mroute'][route].update({ - next_hop : conf.return_value('route {0} next-hop {1} distance'.format(route, next_hop)) - }) - - # Get multicast effective interface-routes - for route in conf.list_effective_nodes('interface-route'): - if not route in mroute['old_mroute']: - mroute['old_mroute'][route] = {} - for next_hop in conf.list_effective_nodes('interface-route {0} next-hop-interface'.format(route)): - mroute['old_mroute'][route].update({ - next_hop : conf.return_value('interface-route {0} next-hop-interface {1} distance'.format(route, next_hop)) - }) - - # Get multicast routes - for route in conf.list_nodes('route'): - mroute['mroute'][route] = {} - for next_hop in conf.list_nodes('route {0} next-hop'.format(route)): - mroute['mroute'][route].update({ - next_hop : conf.return_value('route {0} next-hop {1} distance'.format(route, next_hop)) - }) - - # Get multicast interface-routes - for route in conf.list_nodes('interface-route'): - if not route in mroute['mroute']: - mroute['mroute'][route] = {} - for next_hop in conf.list_nodes('interface-route {0} next-hop-interface'.format(route)): - mroute['mroute'][route].update({ - next_hop : conf.return_value('interface-route {0} next-hop-interface {1} distance'.format(route, next_hop)) - }) - - return mroute - -def verify(mroute): - if mroute is None: - return None - - for mcast_route in mroute['mroute']: - route = mcast_route.split('/') - if IPv4Address(route[0]) < IPv4Address('224.0.0.0'): - raise ConfigError(f'{mcast_route} not a multicast network') - - -def generate(mroute): - if mroute is None: - return None - - mroute['new_frr_config'] = render_to_string('frr/static_mcast.frr.j2', mroute) - return None - - -def apply(mroute): - if mroute is None: - return None - - frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(frr.mgmt_daemon) - - if 'old_mroute' in mroute: - for route_gr in mroute['old_mroute']: - for nh in mroute['old_mroute'][route_gr]: - if mroute['old_mroute'][route_gr][nh]: - frr_cfg.modify_section(f'^ip mroute {route_gr} {nh} {mroute["old_mroute"][route_gr][nh]}') - else: - frr_cfg.modify_section(f'^ip mroute {route_gr} {nh}') - - if 'new_frr_config' in mroute: - frr_cfg.add_before(frr.default_add_before, mroute['new_frr_config']) - - frr_cfg.commit_configuration() - return None - - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/service_snmp.py b/src/conf_mode/service_snmp.py index 134662f850..1174b12383 100755 --- a/src/conf_mode/service_snmp.py +++ b/src/conf_mode/service_snmp.py @@ -34,7 +34,6 @@ from vyos.utils.permission import chmod_755 from vyos.version import get_version_data from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() @@ -261,16 +260,6 @@ def apply(snmp): # start SNMP daemon call(f'systemctl reload-or-restart {systemd_service}') - - # Enable AgentX in FRR - # This should be done for each daemon individually because common command - # works only if all the daemons started with SNMP support - # Following daemons from FRR 9.0/stable have SNMP module compiled in VyOS - frr_daemons_list = [frr.zebra_daemon, frr.bgp_daemon, frr.ospf_daemon, frr.ospf6_daemon, - frr.rip_daemon, frr.isis_daemon, frr.ldpd_daemon] - for frr_daemon in frr_daemons_list: - call(f'vtysh -c "configure terminal" -d {frr_daemon} -c "agentx" >/dev/null') - return None if __name__ == '__main__': diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index 3d3845fdf3..a13bb8b1e0 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -19,13 +19,14 @@ from json import loads from vyos.config import Config +from vyos.configdict import get_frrender_dict from vyos.configdict import dict_merge from vyos.configdict import node_changed from vyos.configverify import verify_route_map from vyos.firewall import conntrack_required +from vyos.frrender import FRRender from vyos.ifconfig import Interface from vyos.template import render -from vyos.template import render_to_string from vyos.utils.dict import dict_search from vyos.utils.network import get_vrf_tableid from vyos.utils.network import get_vrf_members @@ -35,9 +36,9 @@ from vyos.utils.process import popen from vyos.utils.system import sysctl_write from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() +frrender = FRRender() config_file = '/etc/iproute2/rt_tables.d/vyos-vrf.conf' k_mod = ['vrf'] @@ -132,6 +133,10 @@ def get_config(config=None): if 'name' in vrf: vrf['conntrack'] = conntrack_required(conf) + # We need to merge the FRR rendering dict into the VRF dict + # this is required to get the route-map information to FRR + vrf.update({'frrender' : get_frrender_dict(conf)}) + # We also need the route-map information from the config # # XXX: one MUST always call this without the key_mangling() option! See @@ -204,8 +209,9 @@ def verify(vrf): def generate(vrf): # Render iproute2 VR helper names render(config_file, 'iproute2/vrf.conf.j2', vrf) - # Render VRF Kernel/Zebra route-map filters - vrf['frr_zebra_config'] = render_to_string('frr/zebra.vrf.route-map.frr.j2', vrf) + + if 'frrender' in vrf: + frrender.generate(vrf['frrender']) return None @@ -347,13 +353,8 @@ def apply(vrf): if has_rule(afi, 2000, 'l3mdev'): call(f'ip {afi} rule del pref 2000 l3mdev unreachable') - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(frr.mgmt_daemon) - frr_cfg.modify_section(f'^vrf .+', stop_pattern='^exit-vrf', remove_stop_mark=True) - if 'frr_zebra_config' in vrf: - frr_cfg.add_before(frr.default_add_before, vrf['frr_zebra_config']) - frr_cfg.commit_configuration() + if 'frrender' in vrf: + frrender.apply() return None From 13baad691410009d1e5f7e6f6bf5afe72afd2f73 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sun, 8 Dec 2024 09:11:35 +0100 Subject: [PATCH 12/28] multicast: T6746: flatten CLI by merging "multicast route" to "mroute" CLI tagNode This will save an entire level for the configuration and there is no need for a parent "multicast" node, as it will only have "route" as tagNode below. Move set protocols static multicast route to: * set protocols static mroute --- data/templates/frr/staticd.frr.j2 | 8 +- interface-definitions/protocols_static.xml.in | 79 +++++++++---------- .../scripts/cli/test_protocols_static.py | 4 +- src/tests/test_initial_setup.py | 4 +- 4 files changed, 43 insertions(+), 52 deletions(-) diff --git a/data/templates/frr/staticd.frr.j2 b/data/templates/frr/staticd.frr.j2 index c662b76505..227807c62c 100644 --- a/data/templates/frr/staticd.frr.j2 +++ b/data/templates/frr/staticd.frr.j2 @@ -128,15 +128,13 @@ exit-vrf {% endif %} ! {# Multicast route #} -{% if multicast is vyos_defined %} +{% if mroute is vyos_defined %} {% set ip_prefix = 'ip m' %} {# IPv4 multicast routing #} -{% if multicast.route is vyos_defined %} -{% for prefix, prefix_config in multicast.route.items() %} +{% for prefix, prefix_config in mroute.items() %} {{ static_routes(ip_prefix, prefix, prefix_config) }} {# j2lint: disable=jinja-statements-delimeter #} -{%- endfor %} -{% endif %} +{%- endfor %} {% endif %} ! {% if route_map is vyos_defined %} diff --git a/interface-definitions/protocols_static.xml.in b/interface-definitions/protocols_static.xml.in index 407e565533..d8e0ee56b4 100644 --- a/interface-definitions/protocols_static.xml.in +++ b/interface-definitions/protocols_static.xml.in @@ -11,62 +11,55 @@ 480 - + - Multicast static route + Static IPv4 route for Multicast RIB + + ipv4net + Network + + + + - + + + Next-hop IPv4 router address + + ipv4 + Next-hop router address + + + + + + + #include + #include + + + - Configure static unicast route into MRIB for multicast RPF lookup + Next-hop IPv4 router interface + + + - ipv4net - Network + txt + Gateway interface name - + #include - - - Next-hop IPv4 router address - - ipv4 - Next-hop router address - - - - - - - #include - #include - - - - - Next-hop IPv4 router interface - - - - - txt - Gateway interface name - - - #include - - - - #include - #include - - + #include + #include - + #include #include #include diff --git a/smoketest/scripts/cli/test_protocols_static.py b/smoketest/scripts/cli/test_protocols_static.py index 980bc6e7f9..086235086a 100755 --- a/smoketest/scripts/cli/test_protocols_static.py +++ b/smoketest/scripts/cli/test_protocols_static.py @@ -528,7 +528,7 @@ def test_03_static_vrf(self): def test_04_static_multicast(self): for route, route_config in multicast_routes.items(): if 'next_hop' in route_config: - base = base_path + ['multicast', 'route', route] + base = base_path + ['mroute', route] for next_hop, next_hop_config in route_config['next_hop'].items(): self.cli_set(base + ['next-hop', next_hop]) if 'distance' in next_hop_config: @@ -537,7 +537,7 @@ def test_04_static_multicast(self): self.cli_set(base + ['next-hop', next_hop, 'disable']) if 'interface' in route_config: - base = base_path + ['multicast', 'route', route] + base = base_path + ['mroute', route] for next_hop, next_hop_config in route_config['interface'].items(): self.cli_set(base + ['interface', next_hop]) if 'distance' in next_hop_config: diff --git a/src/tests/test_initial_setup.py b/src/tests/test_initial_setup.py index 4cd5fb169e..7737f9df58 100644 --- a/src/tests/test_initial_setup.py +++ b/src/tests/test_initial_setup.py @@ -92,8 +92,8 @@ def test_set_gateway(self): vis.set_default_gateway(self.config, '192.0.2.1') self.assertTrue(self.config.exists(['protocols', 'static', 'route', '0.0.0.0/0', 'next-hop', '192.0.2.1'])) - self.assertTrue(self.xml.is_tag(['protocols', 'static', 'multicast', 'route', '0.0.0.0/0', 'next-hop'])) - self.assertTrue(self.xml.is_tag(['protocols', 'static', 'multicast', 'route'])) + self.assertTrue(self.xml.is_tag(['protocols', 'static', 'mroute', '0.0.0.0/0', 'next-hop'])) + self.assertTrue(self.xml.is_tag(['protocols', 'static', 'mroute'])) if __name__ == "__main__": unittest.main() From 9f42616aa1771ef3369071f175aa26742eab5d06 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sun, 8 Dec 2024 14:27:03 +0100 Subject: [PATCH 13/28] Debian: T6746: restart vyos-configd on package installation - if running --- debian/vyos-1x.postinst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst index d83634cfc5..ff5a91e097 100644 --- a/debian/vyos-1x.postinst +++ b/debian/vyos-1x.postinst @@ -267,3 +267,8 @@ fi # T4287 - as we have a non-signed kernel use the upstream wireless reulatory database update-alternatives --set regulatory.db /lib/firmware/regulatory.db-upstream + +# Restart vyos-configd to apply changes in Python scripts/templates +if systemctl is-active --quiet vyos-configd; then + systemctl restart vyos-configd +fi From e11a6b7abb719b0e698ae2e02647f450671c6fb1 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sun, 8 Dec 2024 15:36:49 +0100 Subject: [PATCH 14/28] op-mode: T6746: add CLI options to show/monitor log of vyos-configd This is pretty usefull to monitor what's going on under the hood Dec 08 15:27:34 vyos-configd[4324]: Received message: {"type": "init"} Dec 08 15:27:34 vyos-configd[4324]: config session pid is 4400 Dec 08 15:27:34 vyos-configd[4324]: config session sudo_user is cpo Dec 08 15:27:34 vyos-configd[4324]: commit_scripts: ['protocols_babel', 'protocols_bfd', 'protocols_bgp'] Dec 08 15:27:34 vyos-configd[4324]: Received message: {"type": "node", "last": false, "data": "/usr/libexec/vyos/conf_mode/protocols_babel.py"} Dec 08 15:27:34 vyos-configd[4324]: Sending reply: error_code 1 with output Dec 08 15:27:34 vyos-configd[4324]: Received message: {"type": "node", "last": false, "data": "/usr/libexec/vyos/conf_mode/protocols_bgp.py"} Dec 08 15:27:34 vyos-configd[4324]: Sending reply: error_code 1 with output Dec 08 15:27:34 vyos-configd[4324]: Received message: {"type": "node", "last": true, "data": "/usr/libexec/vyos/conf_mode/protocols_bfd.py"} Dec 08 15:27:34 vyos-configd[4324]: Sending reply: error_code 1 with output Dec 08 15:27:34 vyos-configd[4324]: scripts_called: ['protocols_babel', 'protocols_bgp', 'protocols_bfd'] Dec 08 15:27:34 vyos-configd[4324]: FRR: Reloading configuration - tries: 1 Python class ID: 139842739583248 Debugging the new FRRender/vyos-config integration --- op-mode-definitions/monitor-log.xml.in | 6 ++++++ op-mode-definitions/show-log.xml.in | 8 +++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in index 6a2b7e53bc..66b7ecb2f0 100644 --- a/op-mode-definitions/monitor-log.xml.in +++ b/op-mode-definitions/monitor-log.xml.in @@ -365,6 +365,12 @@ journalctl --no-hostname --boot --follow --unit keepalived.service + + + Monitor last lines of VyOS configuration daemon log + + journalctl --no-hostname --boot --follow --unit vyos-configd.service + Monitor last lines of Wireless interface log diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in index c2504686df..b032f5e38b 100755 --- a/op-mode-definitions/show-log.xml.in +++ b/op-mode-definitions/show-log.xml.in @@ -836,7 +836,7 @@ - Monitor last lines of ALL Virtual Private Network services + Show log for ALL Virtual Private Network services journalctl --no-hostname --boot --unit strongswan.service --unit accel-ppp@*.service --unit ocserv.service @@ -893,6 +893,12 @@ journalctl --no-hostname --boot --unit keepalived.service + + + Show log for VyOS configuration daemon + + journalctl --no-hostname --boot --unit vyos-configd.service + Show log for Wireless interface From 1666e3d99b8de700c880f09ce6ad5ea8bc7f4568 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sun, 8 Dec 2024 15:41:53 +0100 Subject: [PATCH 15/28] frr: T6746: add default entry for config debug file A lot of services have dynamic debug capabilities which will be turned on by creating a file in /tmp. These scripts have the path hardcoded and sometimes accross multiple places (bad). This commit introduces vyos.defaults.frr_debug_enable to get the path for the debug file from a single location. --- python/vyos/configdict.py | 10 ++++++---- python/vyos/defaults.py | 1 + python/vyos/frr.py | 3 ++- python/vyos/frrender.py | 4 ++-- src/init/vyos-router | 3 ++- 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 88d131573d..c7384f71d3 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -19,6 +19,7 @@ import os import json +from vyos.defaults import frr_debug_enable from vyos.utils.dict import dict_search from vyos.utils.process import cmd @@ -1124,8 +1125,9 @@ def dict_helper_pim_defaults(pim, path): dict.update({'vrf' : vrf}) - print('======== < > ==========') - import pprint - pprint.pprint(dict) - print('======== < > ==========') + if os.path.exists(frr_debug_enable): + print('======== < BEGIN > ==========') + import pprint + pprint.pprint(dict) + print('========= < END > ===========') return dict diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py index 4259909676..9757a34df3 100644 --- a/python/vyos/defaults.py +++ b/python/vyos/defaults.py @@ -42,6 +42,7 @@ config_status = '/tmp/vyos-config-status' api_config_state = '/run/http-api-state' +frr_debug_enable = '/tmp/vyos.frr.debug' cfg_group = 'vyattacfg' diff --git a/python/vyos/frr.py b/python/vyos/frr.py index 183805e135..67279a6f7f 100644 --- a/python/vyos/frr.py +++ b/python/vyos/frr.py @@ -69,6 +69,7 @@ import re from vyos import ConfigError +from vyos.defaults import frr_debug_enable from vyos.utils.process import cmd from vyos.utils.process import popen from vyos.utils.process import STDOUT @@ -144,7 +145,7 @@ class ConfigSectionNotFound(FrrError): def init_debugging(): global DEBUG - DEBUG = os.path.exists('/tmp/vyos.frr.debug') + DEBUG = os.path.exists(frr_debug_enable) if DEBUG: LOG.setLevel(logging.DEBUG) diff --git a/python/vyos/frrender.py b/python/vyos/frrender.py index 015596a8fc..2069930a90 100644 --- a/python/vyos/frrender.py +++ b/python/vyos/frrender.py @@ -19,13 +19,13 @@ import os +from vyos.defaults import frr_debug_enable from vyos.utils.file import write_file from vyos.utils.process import rc_cmd from vyos.template import render_to_string from vyos import ConfigError -DEBUG_ON = os.path.exists('/tmp/vyos.frr.debug') -DEBUG_ON = True +DEBUG_ON = os.path.exists(frr_debug_enable) def debug(message): if not DEBUG_ON: diff --git a/src/init/vyos-router b/src/init/vyos-router index e2e964656a..00136309b8 100755 --- a/src/init/vyos-router +++ b/src/init/vyos-router @@ -474,9 +474,10 @@ start () # enable some debugging before loading the configuration if grep -q vyos-debug /proc/cmdline; then log_action_begin_msg "Enable runtime debugging options" + FRR_DEBUG=$(python3 -c "from vyos.defaults import frr_debug_enable; print(frr_debug_enable)") + touch $FRR_DEBUG touch /tmp/vyos.container.debug touch /tmp/vyos.ifconfig.debug - touch /tmp/vyos.frr.debug touch /tmp/vyos.container.debug touch /tmp/vyos.smoketest.debug fi From 779f311e7fe81e3c85de28f13e4e12e33b255483 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sun, 8 Dec 2024 16:33:45 +0100 Subject: [PATCH 16/28] frr: T6746: integrate FRRender class into vyos-configd When running under vyos-configd only a single apply() is done as last step in the commit algorithm. FRRender class address is provided via an attribute from vyos-configd process. --- python/vyos/configdict.py | 6 ++++++ python/vyos/frrender.py | 2 +- src/conf_mode/interfaces_bonding.py | 11 +++++------ src/conf_mode/interfaces_ethernet.py | 11 +++++------ src/conf_mode/policy.py | 9 +++++---- src/conf_mode/protocols_babel.py | 9 +++++---- src/conf_mode/protocols_bfd.py | 8 ++++---- src/conf_mode/protocols_bgp.py | 9 +++++---- src/conf_mode/protocols_eigrp.py | 8 +++++--- src/conf_mode/protocols_isis.py | 9 +++++---- src/conf_mode/protocols_mpls.py | 9 +++++---- src/conf_mode/protocols_openfabric.py | 9 +++++---- src/conf_mode/protocols_ospf.py | 8 +++++--- src/conf_mode/protocols_ospfv3.py | 9 +++++---- src/conf_mode/protocols_pim.py | 8 +++++--- src/conf_mode/protocols_pim6.py | 9 ++++++--- src/conf_mode/protocols_rip.py | 13 +++++++------ src/conf_mode/protocols_ripng.py | 8 ++++---- src/conf_mode/protocols_rpki.py | 7 ++++--- src/conf_mode/protocols_segment-routing.py | 7 ++++--- src/conf_mode/protocols_static.py | 8 +++++--- src/conf_mode/vrf.py | 11 +++++------ src/services/vyos-configd | 8 ++++++++ 23 files changed, 114 insertions(+), 82 deletions(-) diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index c7384f71d3..baffd94ddb 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -1125,6 +1125,12 @@ def dict_helper_pim_defaults(pim, path): dict.update({'vrf' : vrf}) + # Use singleton instance of the FRR render class + if hasattr(conf, 'frrender_cls'): + frrender = getattr(conf, 'frrender_cls') + dict.update({'frrender_cls' : frrender}) + frrender.generate(dict) + if os.path.exists(frr_debug_enable): print('======== < BEGIN > ==========') import pprint diff --git a/python/vyos/frrender.py b/python/vyos/frrender.py index 2069930a90..e02094bbb4 100644 --- a/python/vyos/frrender.py +++ b/python/vyos/frrender.py @@ -136,7 +136,7 @@ def apply(self): emsg = '' while count < count_max: count += 1 - print('FRR: Reloading configuration', count) + print('FRR: Reloading configuration - tries:', count, 'Python class ID:', id(self)) cmdline = '/usr/lib/frr/frr-reload.py --reload' if DEBUG_ON: diff --git a/src/conf_mode/interfaces_bonding.py b/src/conf_mode/interfaces_bonding.py index adea8fc633..5f839b33ce 100755 --- a/src/conf_mode/interfaces_bonding.py +++ b/src/conf_mode/interfaces_bonding.py @@ -45,7 +45,6 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -frrender = FRRender() def get_bond_mode(mode): if mode == 'round-robin': @@ -94,7 +93,7 @@ def get_config(config=None): if tmp: bond.update({'shutdown_required' : {}}) tmp = is_node_changed(conf, base + [ifname, 'evpn']) - if tmp: bond.update({'frrender' : get_frrender_dict(conf)}) + if tmp: bond.update({'frr_dict' : get_frrender_dict(conf)}) # determine which members have been removed interfaces_removed = leaf_node_changed(conf, base + [ifname, 'member', 'interface']) @@ -264,13 +263,13 @@ def verify(bond): return None def generate(bond): - if 'frrender' in bond: - frrender.generate(bond['frrender']) + if 'frr_dict' in bond and 'frrender_cls' not in bond['frr_dict']: + FRRender().generate(bond['frr_dict']) return None def apply(bond): - if 'frrender' in bond: - frrender.apply() + if 'frr_dict' in bond and 'frrender_cls' not in bond['frr_dict']: + FRRender().apply() b = BondIf(bond['ifname']) if 'deleted' in bond: diff --git a/src/conf_mode/interfaces_ethernet.py b/src/conf_mode/interfaces_ethernet.py index 6a035e9e95..accfb6b8e0 100755 --- a/src/conf_mode/interfaces_ethernet.py +++ b/src/conf_mode/interfaces_ethernet.py @@ -44,7 +44,6 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -frrender = FRRender() def update_bond_options(conf: Config, eth_conf: dict) -> list: """ @@ -166,7 +165,7 @@ def get_config(config=None): if tmp: ethernet.update({'speed_duplex_changed': {}}) tmp = is_node_changed(conf, base + [ifname, 'evpn']) - if tmp: ethernet.update({'frrender' : get_frrender_dict(conf)}) + if tmp: ethernet.update({'frr_dict' : get_frrender_dict(conf)}) return ethernet @@ -322,13 +321,13 @@ def verify_ethernet(ethernet): return None def generate(ethernet): - if 'frrender' in ethernet: - frrender.generate(ethernet['frrender']) + if 'frr_dict' in ethernet and 'frrender_cls' not in ethernet['frr_dict']: + FRRender().generate(ethernet['frr_dict']) return None def apply(ethernet): - if 'frrender' in ethernet: - frrender.apply() + if 'frr_dict' in ethernet and 'frrender_cls' not in ethernet['frr_dict']: + FRRender().apply() e = EthernetIf(ethernet['ifname']) if 'deleted' in ethernet: diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py index e6b6d474af..2122cb032c 100755 --- a/src/conf_mode/policy.py +++ b/src/conf_mode/policy.py @@ -26,8 +26,6 @@ from vyos import airbag airbag.enable() -frrender = FRRender() - def community_action_compatibility(actions: dict) -> bool: """ Check compatibility of values in community and large community sections @@ -264,10 +262,13 @@ def verify(config_dict): def generate(config_dict): - frrender.generate(config_dict) + if 'frrender_cls' not in config_dict: + FRRender().generate(config_dict) + return None def apply(config_dict): - frrender.apply() + if 'frrender_cls' not in config_dict: + FRRender().apply() return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_babel.py b/src/conf_mode/protocols_babel.py index f9d5e45a03..f458493c2e 100755 --- a/src/conf_mode/protocols_babel.py +++ b/src/conf_mode/protocols_babel.py @@ -27,8 +27,6 @@ from vyos import airbag airbag.enable() -frrender = FRRender() - def get_config(config=None): if config: conf = config @@ -91,10 +89,13 @@ def verify(config_dict): def generate(config_dict): - frrender.generate(config_dict) + if 'frrender_cls' not in config_dict: + FRRender().generate(config_dict) + return None def apply(config_dict): - frrender.apply() + if 'frrender_cls' not in config_dict: + FRRender().apply() return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index a0d7fdfb5c..d8b19fa0e4 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -25,8 +25,6 @@ from vyos import airbag airbag.enable() -frrender = FRRender() - def get_config(config=None): if config: conf = config @@ -79,10 +77,12 @@ def verify(config_dict): return None def generate(config_dict): - frrender.generate(config_dict) + if 'frrender_cls' not in config_dict: + FRRender().generate(config_dict) def apply(config_dict): - frrender.apply() + if 'frrender_cls' not in config_dict: + FRRender().apply() return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index 989cf9b5c8..2a4bcbadf5 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -35,8 +35,6 @@ from vyos import airbag airbag.enable() -frrender = FRRender() - vrf = None if len(argv) > 1: vrf = argv[1] @@ -558,10 +556,13 @@ def verify(config_dict): return None def generate(config_dict): - frrender.generate(config_dict) + if 'frrender_cls' not in config_dict: + FRRender().generate(config_dict) + return None def apply(config_dict): - frrender.apply() + if 'frrender_cls' not in config_dict: + FRRender().apply() return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_eigrp.py b/src/conf_mode/protocols_eigrp.py index 5d60e6bfdc..4f56d2b940 100755 --- a/src/conf_mode/protocols_eigrp.py +++ b/src/conf_mode/protocols_eigrp.py @@ -25,7 +25,6 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -frrender = FRRender() vrf = None if len(argv) > 1: @@ -55,10 +54,13 @@ def verify(config_dict): verify_vrf(eigrp) def generate(config_dict): - frrender.generate(config_dict) + if 'frrender_cls' not in config_dict: + FRRender().generate(config_dict) + return None def apply(config_dict): - frrender.apply() + if 'frrender_cls' not in config_dict: + FRRender().apply() return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py index e812770bce..9e494ecc8d 100755 --- a/src/conf_mode/protocols_isis.py +++ b/src/conf_mode/protocols_isis.py @@ -30,8 +30,6 @@ from vyos import airbag airbag.enable() -frrender = FRRender() - vrf = None if len(argv) > 1: vrf = argv[1] @@ -281,10 +279,13 @@ def verify(config_dict): return None def generate(config_dict): - frrender.generate(config_dict) + if 'frrender_cls' not in config_dict: + FRRender().generate(config_dict) + return None def apply(config_dict): - frrender.apply() + if 'frrender_cls' not in config_dict: + FRRender().apply() return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py index 2a691f4a43..12899f0b2c 100755 --- a/src/conf_mode/protocols_mpls.py +++ b/src/conf_mode/protocols_mpls.py @@ -31,8 +31,6 @@ from vyos import airbag airbag.enable() -frrender = FRRender() - def get_config(config=None): if config: conf = config @@ -69,10 +67,13 @@ def verify(config_dict): return None def generate(config_dict): - frrender.generate(config_dict) + if 'frrender_cls' not in config_dict: + FRRender().generate(config_dict) + return None def apply(config_dict): - frrender.apply() + if 'frrender_cls' not in config_dict: + FRRender().apply() if not has_frr_protocol_in_dict(config_dict, 'mpls'): return None diff --git a/src/conf_mode/protocols_openfabric.py b/src/conf_mode/protocols_openfabric.py index 3dc06ee682..9fdcf4b505 100644 --- a/src/conf_mode/protocols_openfabric.py +++ b/src/conf_mode/protocols_openfabric.py @@ -24,9 +24,7 @@ from vyos.frrender import FRRender from vyos import ConfigError from vyos import airbag - airbag.enable() -frrender = FRRender() def get_config(config=None): if config: @@ -91,10 +89,13 @@ def verify(config_dict): return None def generate(config_dict): - frrender.generate(config_dict) + if 'frrender_cls' not in config_dict: + FRRender().generate(config_dict) + return None def apply(config_dict): - frrender.apply() + if 'frrender_cls' not in config_dict: + FRRender().apply() return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py index 32ad5e497c..07e6a5860d 100755 --- a/src/conf_mode/protocols_ospf.py +++ b/src/conf_mode/protocols_ospf.py @@ -30,7 +30,6 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -frrender = FRRender() vrf = None if len(argv) > 1: @@ -178,10 +177,13 @@ def verify(config_dict): return None def generate(config_dict): - frrender.generate(config_dict) + if 'frrender_cls' not in config_dict: + FRRender().generate(config_dict) + return None def apply(config_dict): - frrender.apply() + if 'frrender_cls' not in config_dict: + FRRender().apply() return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_ospfv3.py b/src/conf_mode/protocols_ospfv3.py index 038fcd2b47..9af85cabfe 100755 --- a/src/conf_mode/protocols_ospfv3.py +++ b/src/conf_mode/protocols_ospfv3.py @@ -31,8 +31,6 @@ from vyos import airbag airbag.enable() -frrender = FRRender() - vrf = None if len(argv) > 1: vrf = argv[1] @@ -90,10 +88,13 @@ def verify(config_dict): return None def generate(config_dict): - frrender.generate(config_dict) + if 'frrender_cls' not in config_dict: + FRRender().generate(config_dict) + return None def apply(config_dict): - frrender.apply() + if 'frrender_cls' not in config_dict: + FRRender().apply() return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_pim.py b/src/conf_mode/protocols_pim.py index 1ff7203b28..df0e82d69c 100755 --- a/src/conf_mode/protocols_pim.py +++ b/src/conf_mode/protocols_pim.py @@ -32,7 +32,6 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -frrender = FRRender() def get_config(config=None): if config: @@ -87,7 +86,9 @@ def verify(config_dict): unique.append(gr_addr) def generate(config_dict): - frrender.generate(config_dict) + if 'frrender_cls' not in config_dict: + FRRender().generate(config_dict) + return None def apply(config_dict): if not has_frr_protocol_in_dict(config_dict, 'pim'): @@ -102,7 +103,8 @@ def apply(config_dict): if not pim_pid: call('/usr/lib/frr/pimd -d -F traditional --daemon -A 127.0.0.1') - frrender.apply() + if 'frrender_cls' not in config_dict: + FRRender().apply() return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_pim6.py b/src/conf_mode/protocols_pim6.py index b3a4099d2a..a5d6128149 100755 --- a/src/conf_mode/protocols_pim6.py +++ b/src/conf_mode/protocols_pim6.py @@ -26,7 +26,6 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -frrender = FRRender() def get_config(config=None): if config: @@ -76,10 +75,14 @@ def verify(config_dict): unique.append(gr_addr) def generate(config_dict): - frrender.generate(config_dict) + if 'frrender_cls' not in config_dict: + FRRender().generate(config_dict) + return None def apply(config_dict): - frrender.apply() + if 'frrender_cls' not in config_dict: + FRRender().apply() + return None if __name__ == '__main__': try: diff --git a/src/conf_mode/protocols_rip.py b/src/conf_mode/protocols_rip.py index 3862530a29..7eb0605043 100755 --- a/src/conf_mode/protocols_rip.py +++ b/src/conf_mode/protocols_rip.py @@ -28,8 +28,6 @@ from vyos import airbag airbag.enable() -frrender = FRRender() - def get_config(config=None): if config: conf = config @@ -69,11 +67,14 @@ def verify(config_dict): raise ConfigError(f'You can not have "split-horizon poison-reverse" enabled ' \ f'with "split-horizon disable" for "{interface}"!') -def generate(frr_dict): - frrender.generate(frr_dict) +def generate(config_dict): + if 'frrender_cls' not in config_dict: + FRRender().generate(config_dict) + return None -def apply(rip): - frrender.apply() +def apply(config_dict): + if 'frrender_cls' not in config_dict: + FRRender().apply() return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_ripng.py b/src/conf_mode/protocols_ripng.py index 327a0d2397..5884e61f96 100755 --- a/src/conf_mode/protocols_ripng.py +++ b/src/conf_mode/protocols_ripng.py @@ -28,8 +28,6 @@ from vyos import airbag airbag.enable() -frrender = FRRender() - def get_config(config=None): if config: conf = config @@ -70,11 +68,13 @@ def verify(config_dict): f'with "split-horizon disable" for "{interface}"!') def generate(config_dict): - frrender.generate(config_dict) + if 'frrender_cls' not in config_dict: + FRRender().generate(config_dict) return None def apply(config_dict): - frrender.apply() + if 'frrender_cls' not in config_dict: + FRRender().apply() return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_rpki.py b/src/conf_mode/protocols_rpki.py index c75f958605..33696a742a 100755 --- a/src/conf_mode/protocols_rpki.py +++ b/src/conf_mode/protocols_rpki.py @@ -30,7 +30,6 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -frrender = FRRender() rpki_ssh_key_base = '/run/frr/id_rpki' @@ -95,11 +94,13 @@ def generate(config_dict): write_file(cache_config['ssh']['public_key_file'], wrap_openssh_public_key(public_key_data, public_key_type)) write_file(cache_config['ssh']['private_key_file'], wrap_openssh_private_key(private_key_data)) - frrender.generate(config_dict) + if 'frrender_cls' not in config_dict: + FRRender().generate(config_dict) return None def apply(config_dict): - frrender.apply() + if 'frrender_cls' not in config_dict: + FRRender().apply() return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_segment-routing.py b/src/conf_mode/protocols_segment-routing.py index 0fad968e14..a776d10380 100755 --- a/src/conf_mode/protocols_segment-routing.py +++ b/src/conf_mode/protocols_segment-routing.py @@ -27,7 +27,6 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -frrender = FRRender() def get_config(config=None): if config: @@ -55,7 +54,8 @@ def verify(config_dict): return None def generate(config_dict): - frrender.generate(config_dict) + if 'frrender_cls' not in config_dict: + FRRender().generate(config_dict) return None def apply(config_dict): @@ -88,7 +88,8 @@ def apply(config_dict): else: sysctl_write(f'net.ipv6.conf.{interface}.seg6_enabled', '0') - frrender.apply() + if 'frrender_cls' not in config_dict: + FRRender().apply() return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_static.py b/src/conf_mode/protocols_static.py index 1e498b256a..69500377c1 100755 --- a/src/conf_mode/protocols_static.py +++ b/src/conf_mode/protocols_static.py @@ -28,7 +28,6 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -frrender = FRRender() vrf = None if len(argv) > 1: @@ -92,11 +91,14 @@ def generate(config_dict): # Put routing table names in /etc/iproute2/rt_tables render(config_file, 'iproute2/static.conf.j2', static) - frrender.generate(config_dict) + + if 'frrender_cls' not in config_dict: + FRRender().generate(config_dict) return None def apply(static): - frrender.apply() + if 'frrender_cls' not in config_dict: + FRRender().apply() return None if __name__ == '__main__': diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index a13bb8b1e0..6eea9af4db 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -38,7 +38,6 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -frrender = FRRender() config_file = '/etc/iproute2/rt_tables.d/vyos-vrf.conf' k_mod = ['vrf'] @@ -135,7 +134,7 @@ def get_config(config=None): # We need to merge the FRR rendering dict into the VRF dict # this is required to get the route-map information to FRR - vrf.update({'frrender' : get_frrender_dict(conf)}) + vrf.update({'frr_dict' : get_frrender_dict(conf)}) # We also need the route-map information from the config # @@ -210,8 +209,8 @@ def generate(vrf): # Render iproute2 VR helper names render(config_file, 'iproute2/vrf.conf.j2', vrf) - if 'frrender' in vrf: - frrender.generate(vrf['frrender']) + if 'frr_dict' in vrf and 'frrender_cls' not in vrf['frr_dict']: + FRRender().generate(vrf['frr_dict']) return None @@ -353,8 +352,8 @@ def apply(vrf): if has_rule(afi, 2000, 'l3mdev'): call(f'ip {afi} rule del pref 2000 l3mdev unreachable') - if 'frrender' in vrf: - frrender.apply() + if 'frr_dict' in vrf and 'frrender_cls' not in vrf['frr_dict']: + FRRender().apply() return None diff --git a/src/services/vyos-configd b/src/services/vyos-configd index d977ba2cb1..21d91005ae 100755 --- a/src/services/vyos-configd +++ b/src/services/vyos-configd @@ -37,6 +37,7 @@ from vyos.configsource import ConfigSourceString from vyos.configsource import ConfigSourceError from vyos.configdiff import get_commit_scripts from vyos.config import Config +from vyos.frrender import FRRender from vyos import ConfigError CFG_GROUP = 'vyattacfg' @@ -209,6 +210,9 @@ def initialization(socket): scripts_called = [] setattr(config, 'scripts_called', scripts_called) + if not hasattr(config, 'frrender_cls'): + setattr(config, 'frrender_cls', FRRender()) + return config @@ -326,5 +330,9 @@ if __name__ == '__main__': if message['last'] and config: scripts_called = getattr(config, 'scripts_called', []) logger.debug(f'scripts_called: {scripts_called}') + + if hasattr(config, 'frrender_cls'): + frrender_cls = getattr(config, 'frrender_cls') + frrender_cls.apply() else: logger.critical(f'Unexpected message: {message}') From 55683a8406e17408021437cb35b57c48bd8b2ab1 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Wed, 11 Dec 2024 20:14:45 +0100 Subject: [PATCH 17/28] configd: T6746: handle FRR config reload as last step in commit --- python/vyos/configdict.py | 22 +++++--- python/vyos/configverify.py | 5 +- python/vyos/frrender.py | 5 +- src/conf_mode/policy.py | 4 +- src/conf_mode/protocols_babel.py | 4 +- src/conf_mode/protocols_bfd.py | 4 +- src/conf_mode/protocols_bgp.py | 42 +++++++------- src/conf_mode/protocols_eigrp.py | 21 ++++--- src/conf_mode/protocols_isis.py | 65 +++------------------- src/conf_mode/protocols_mpls.py | 4 +- src/conf_mode/protocols_openfabric.py | 4 +- src/conf_mode/protocols_ospf.py | 17 +++--- src/conf_mode/protocols_ospfv3.py | 17 +++--- src/conf_mode/protocols_pim.py | 4 +- src/conf_mode/protocols_pim6.py | 4 +- src/conf_mode/protocols_rip.py | 4 +- src/conf_mode/protocols_ripng.py | 4 +- src/conf_mode/protocols_rpki.py | 4 +- src/conf_mode/protocols_segment-routing.py | 4 +- src/conf_mode/protocols_static.py | 26 +++++---- src/services/vyos-configd | 2 +- 21 files changed, 114 insertions(+), 152 deletions(-) diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index baffd94ddb..f5e84267eb 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -666,7 +666,7 @@ def get_accel_dict(config, base, chap_secrets, with_pki=False): return dict -def get_frrender_dict(conf) -> dict: +def get_frrender_dict(conf, argv=None) -> dict: from copy import deepcopy from vyos.config import config_dict_merge from vyos.frrender import frr_protocols @@ -675,6 +675,9 @@ def get_frrender_dict(conf) -> dict: # returned to the caller dict = {} + if argv and len(argv) > 1: + dict['vrf_context'] = argv[1] + def dict_helper_ospf_defaults(ospf, path): # We have gathered the dict representation of the CLI, but there are default # options which we need to update into the dictionary retrived. @@ -1024,7 +1027,11 @@ def dict_helper_pim_defaults(pim, path): if 'bgp' in dict: dict['bgp']['dependent_vrfs'].update({vrf_name : {'protocols': tmp} }) - vrf['name'][vrf_name]['protocols'].update({'bgp' : tmp}) + + if 'protocols' not in vrf['name'][vrf_name]: + vrf['name'][vrf_name].update({'protocols': {'bgp' : tmp}}) + else: + vrf['name'][vrf_name]['protocols'].update({'bgp' : tmp}) # We need to check the CLI if the EIGRP node is present and thus load in all the default # values present on the CLI - that's why we have if conf.exists() @@ -1125,15 +1132,16 @@ def dict_helper_pim_defaults(pim, path): dict.update({'vrf' : vrf}) + if os.path.exists(frr_debug_enable): + print('======== < BEGIN > ==========') + import pprint + pprint.pprint(dict) + print('========= < END > ===========') + # Use singleton instance of the FRR render class if hasattr(conf, 'frrender_cls'): frrender = getattr(conf, 'frrender_cls') dict.update({'frrender_cls' : frrender}) frrender.generate(dict) - if os.path.exists(frr_debug_enable): - print('======== < BEGIN > ==========') - import pprint - pprint.pprint(dict) - print('========= < END > ===========') return dict diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 4450dc16b3..4084425b19 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -538,7 +538,10 @@ def verify_eapol(config: dict): for ca_cert in config['eapol']['ca_certificate']: verify_pki_ca_certificate(config, ca_cert) -def has_frr_protocol_in_dict(config_dict: dict, protocol: str, vrf: str=None) -> bool: +def has_frr_protocol_in_dict(config_dict: dict, protocol: str) -> bool: + vrf = None + if config_dict and 'vrf_context' in config_dict: + vrf = config_dict['vrf_context'] if vrf and protocol in (dict_search(f'vrf.name.{vrf}.protocols', config_dict) or []): return True if config_dict and protocol in config_dict: diff --git a/python/vyos/frrender.py b/python/vyos/frrender.py index e02094bbb4..f1bb390945 100644 --- a/python/vyos/frrender.py +++ b/python/vyos/frrender.py @@ -44,7 +44,8 @@ def __init__(self): def generate(self, config): if not isinstance(config, dict): - raise ValueError('config must be of type dict') + tmp = type(config) + raise ValueError(f'Config must be of type "dict" and not "{tmp}"!') def inline_helper(config_dict) -> str: output = '!\n' @@ -136,7 +137,7 @@ def apply(self): emsg = '' while count < count_max: count += 1 - print('FRR: Reloading configuration - tries:', count, 'Python class ID:', id(self)) + debug(f'FRR: Reloading configuration - tries: {count} | Python class ID: {id(self)}') cmdline = '/usr/lib/frr/frr-reload.py --reload' if DEBUG_ON: diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py index 2122cb032c..4abb150ace 100755 --- a/src/conf_mode/policy.py +++ b/src/conf_mode/policy.py @@ -262,12 +262,12 @@ def verify(config_dict): def generate(config_dict): - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().generate(config_dict) return None def apply(config_dict): - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().apply() return None diff --git a/src/conf_mode/protocols_babel.py b/src/conf_mode/protocols_babel.py index f458493c2e..af0751e027 100755 --- a/src/conf_mode/protocols_babel.py +++ b/src/conf_mode/protocols_babel.py @@ -89,12 +89,12 @@ def verify(config_dict): def generate(config_dict): - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().generate(config_dict) return None def apply(config_dict): - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().apply() return None diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index d8b19fa0e4..623801897e 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -77,11 +77,11 @@ def verify(config_dict): return None def generate(config_dict): - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().generate(config_dict) def apply(config_dict): - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().apply() return None diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index 2a4bcbadf5..db3123bd34 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -35,17 +35,13 @@ from vyos import airbag airbag.enable() -vrf = None -if len(argv) > 1: - vrf = argv[1] - def get_config(config=None): if config: conf = config else: conf = Config() - return get_frrender_dict(conf) + return get_frrender_dict(conf, argv) def verify_vrf_as_import(search_vrf_name: str, afi_name: str, vrfs_config: dict) -> bool: """ @@ -179,23 +175,28 @@ def verify_afi(peer_config, bgp_config): return False def verify(config_dict): - global vrf - if not has_frr_protocol_in_dict(config_dict, 'bgp', vrf): + + print('====== verify() ======') + import pprint + pprint.pprint(config_dict) + + if not has_frr_protocol_in_dict(config_dict, 'bgp'): return None + vrf = None + if 'vrf_context' in config_dict: + vrf = config_dict['vrf_context'] + # eqivalent of the C foo ? 'a' : 'b' statement bgp = vrf and config_dict['vrf']['name'][vrf]['protocols']['bgp'] or config_dict['bgp'] bgp['policy'] = config_dict['policy'] - if vrf: - bgp['vrf'] = vrf - if 'deleted' in bgp: - if 'vrf' in bgp: + if vrf: # Cannot delete vrf if it exists in import vrf list in other vrfs for tmp_afi in ['ipv4_unicast', 'ipv6_unicast']: - if verify_vrf_as_import(bgp['vrf'], tmp_afi, bgp['dependent_vrfs']): - raise ConfigError(f'Cannot delete VRF instance "{bgp["vrf"]}", ' \ + if verify_vrf_as_import(vrf, tmp_afi, bgp['dependent_vrfs']): + raise ConfigError(f'Cannot delete VRF instance "{vrf}", ' \ 'unconfigure "import vrf" commands!') else: # We are running in the default VRF context, thus we can not delete @@ -234,9 +235,8 @@ def verify(config_dict): for interface in bgp['interface']: error_msg = f'Interface "{interface}" belongs to different VRF instance' tmp = get_interface_vrf(interface) - if 'vrf' in bgp: - if bgp['vrf'] != tmp: - vrf = bgp['vrf'] + if vrf: + if vrf != tmp: raise ConfigError(f'{error_msg} "{vrf}"!') elif tmp != 'default': raise ConfigError(f'{error_msg} "{tmp}"!') @@ -337,10 +337,8 @@ def verify(config_dict): # Only checks for ipv4 and ipv6 neighbors # Check if neighbor address is assigned as system interface address - vrf = None vrf_error_msg = f' in default VRF!' - if 'vrf' in bgp: - vrf = bgp['vrf'] + if vrf: vrf_error_msg = f' in VRF "{vrf}"!' if is_ip(peer) and is_addr_assigned(peer, vrf): @@ -482,7 +480,7 @@ def verify(config_dict): f'{afi} administrative distance {key}!') if afi in ['ipv4_unicast', 'ipv6_unicast']: - vrf_name = bgp['vrf'] if dict_search('vrf', bgp) else 'default' + vrf_name = vrf if vrf else 'default' # Verify if currant VRF contains rd and route-target options # and does not exist in import list in other VRFs if dict_search(f'rd.vpn.export', afi_config): @@ -556,12 +554,12 @@ def verify(config_dict): return None def generate(config_dict): - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().generate(config_dict) return None def apply(config_dict): - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().apply() return None diff --git a/src/conf_mode/protocols_eigrp.py b/src/conf_mode/protocols_eigrp.py index 4f56d2b940..33812d3503 100755 --- a/src/conf_mode/protocols_eigrp.py +++ b/src/conf_mode/protocols_eigrp.py @@ -26,23 +26,22 @@ from vyos import airbag airbag.enable() -vrf = None -if len(argv) > 1: - vrf = argv[1] - def get_config(config=None): if config: conf = config else: conf = Config() - return get_frrender_dict(conf) + return get_frrender_dict(conf, argv) def verify(config_dict): - global vrf - if not has_frr_protocol_in_dict(config_dict, 'eigrp', vrf): + if not has_frr_protocol_in_dict(config_dict, 'eigrp'): return None + vrf = None + if 'vrf_context' in config_dict: + vrf = config_dict['vrf_context'] + # eqivalent of the C foo ? 'a' : 'b' statement eigrp = vrf and config_dict['vrf']['name'][vrf]['protocols']['eigrp'] or config_dict['eigrp'] eigrp['policy'] = config_dict['policy'] @@ -50,16 +49,16 @@ def verify(config_dict): if 'system_as' not in eigrp: raise ConfigError('EIGRP system-as must be defined!') - if 'vrf' in eigrp: - verify_vrf(eigrp) + if vrf: + verify_vrf({'vrf': vrf}) def generate(config_dict): - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().generate(config_dict) return None def apply(config_dict): - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().apply() return None diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py index 9e494ecc8d..7a54685bb4 100755 --- a/src/conf_mode/protocols_isis.py +++ b/src/conf_mode/protocols_isis.py @@ -30,65 +30,22 @@ from vyos import airbag airbag.enable() -vrf = None -if len(argv) > 1: - vrf = argv[1] - def get_config(config=None): if config: conf = config else: conf = Config() - return get_frrender_dict(conf) - - - # base_path = ['protocols', 'isis'] - - # # eqivalent of the C foo ? 'a' : 'b' statement - # base = vrf and ['vrf', 'name', vrf, 'protocols', 'isis'] or base_path - # isis = conf.get_config_dict(base, key_mangling=('-', '_'), - # get_first_key=True, - # no_tag_node_value_mangle=True) - - # # Assign the name of our VRF context. This MUST be done before the return - # # statement below, else on deletion we will delete the default instance - # # instead of the VRF instance. - # if vrf: isis['vrf'] = vrf - - # # FRR has VRF support for different routing daemons. As interfaces belong - # # to VRFs - or the global VRF, we need to check for changed interfaces so - # # that they will be properly rendered for the FRR config. Also this eases - # # removal of interfaces from the running configuration. - # interfaces_removed = node_changed(conf, base + ['interface']) - # if interfaces_removed: - # isis['interface_removed'] = list(interfaces_removed) - - # # Bail out early if configuration tree does no longer exist. this must - # # be done after retrieving the list of interfaces to be removed. - # if not conf.exists(base): - # isis.update({'deleted' : ''}) - # return isis - - # # merge in default values - # isis = conf.merge_defaults(isis, recursive=True) - - # # We also need some additional information from the config, prefix-lists - # # and route-maps for instance. They will be used in verify(). - # # - # # XXX: one MUST always call this without the key_mangling() option! See - # # vyos.configverify.verify_common_route_maps() for more information. - # tmp = conf.get_config_dict(['policy']) - # # Merge policy dict into "regular" config dict - # isis = dict_merge(tmp, isis) - - return isis + return get_frrender_dict(conf, argv) def verify(config_dict): - global vrf - if not has_frr_protocol_in_dict(config_dict, 'isis', vrf): + if not has_frr_protocol_in_dict(config_dict, 'isis'): return None + vrf = None + if 'vrf_context' in config_dict: + vrf = config_dict['vrf_context'] + # eqivalent of the C foo ? 'a' : 'b' statement isis = vrf and config_dict['vrf']['name'][vrf]['protocols']['isis'] or config_dict['isis'] isis['policy'] = config_dict['policy'] @@ -96,9 +53,6 @@ def verify(config_dict): if 'deleted' in isis: return None - if vrf: - isis['vrf'] = vrf - if 'net' not in isis: raise ConfigError('Network entity is mandatory!') @@ -126,12 +80,11 @@ def verify(config_dict): f'Recommended area lsp-mtu {recom_area_mtu} or less ' \ '(calculated on MTU size).') - if 'vrf' in isis: + if vrf: # If interface specific options are set, we must ensure that the # interface is bound to our requesting VRF. Due to the VyOS # priorities the interface is bound to the VRF after creation of # the VRF itself, and before any routing protocol is configured. - vrf = isis['vrf'] tmp = get_interface_config(interface) if 'master' not in tmp or tmp['master'] != vrf: raise ConfigError(f'Interface "{interface}" is not a member of VRF "{vrf}"!') @@ -279,12 +232,12 @@ def verify(config_dict): return None def generate(config_dict): - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().generate(config_dict) return None def apply(config_dict): - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().apply() return None diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py index 12899f0b2c..bab9648c49 100755 --- a/src/conf_mode/protocols_mpls.py +++ b/src/conf_mode/protocols_mpls.py @@ -67,12 +67,12 @@ def verify(config_dict): return None def generate(config_dict): - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().generate(config_dict) return None def apply(config_dict): - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().apply() if not has_frr_protocol_in_dict(config_dict, 'mpls'): diff --git a/src/conf_mode/protocols_openfabric.py b/src/conf_mode/protocols_openfabric.py index 9fdcf4b505..48c89d742b 100644 --- a/src/conf_mode/protocols_openfabric.py +++ b/src/conf_mode/protocols_openfabric.py @@ -89,12 +89,12 @@ def verify(config_dict): return None def generate(config_dict): - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().generate(config_dict) return None def apply(config_dict): - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().apply() return None diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py index 07e6a5860d..9d35aa007d 100755 --- a/src/conf_mode/protocols_ospf.py +++ b/src/conf_mode/protocols_ospf.py @@ -31,23 +31,22 @@ from vyos import airbag airbag.enable() -vrf = None -if len(argv) > 1: - vrf = argv[1] - def get_config(config=None): if config: conf = config else: conf = Config() - return get_frrender_dict(conf) + return get_frrender_dict(conf, argv) def verify(config_dict): - global vrf - if not has_frr_protocol_in_dict(config_dict, 'ospf', vrf): + if not has_frr_protocol_in_dict(config_dict, 'ospf'): return None + vrf = None + if 'vrf_context' in config_dict: + vrf = config_dict['vrf_context'] + # eqivalent of the C foo ? 'a' : 'b' statement ospf = vrf and config_dict['vrf']['name'][vrf]['protocols']['ospf'] or config_dict['ospf'] ospf['policy'] = config_dict['policy'] @@ -177,12 +176,12 @@ def verify(config_dict): return None def generate(config_dict): - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().generate(config_dict) return None def apply(config_dict): - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().apply() return None diff --git a/src/conf_mode/protocols_ospfv3.py b/src/conf_mode/protocols_ospfv3.py index 9af85cabfe..c6b042b54a 100755 --- a/src/conf_mode/protocols_ospfv3.py +++ b/src/conf_mode/protocols_ospfv3.py @@ -31,23 +31,22 @@ from vyos import airbag airbag.enable() -vrf = None -if len(argv) > 1: - vrf = argv[1] - def get_config(config=None): if config: conf = config else: conf = Config() - return get_frrender_dict(conf) + return get_frrender_dict(conf, argv) def verify(config_dict): - global vrf - if not has_frr_protocol_in_dict(config_dict, 'ospfv3', vrf): + if not has_frr_protocol_in_dict(config_dict, 'ospfv3'): return None + vrf = None + if 'vrf_context' in config_dict: + vrf = config_dict['vrf_context'] + # eqivalent of the C foo ? 'a' : 'b' statement ospfv3 = vrf and config_dict['vrf']['name'][vrf]['protocols']['ospfv3'] or config_dict['ospfv3'] ospfv3['policy'] = config_dict['policy'] @@ -88,12 +87,12 @@ def verify(config_dict): return None def generate(config_dict): - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().generate(config_dict) return None def apply(config_dict): - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().apply() return None diff --git a/src/conf_mode/protocols_pim.py b/src/conf_mode/protocols_pim.py index df0e82d69c..c40b9d86a1 100755 --- a/src/conf_mode/protocols_pim.py +++ b/src/conf_mode/protocols_pim.py @@ -86,7 +86,7 @@ def verify(config_dict): unique.append(gr_addr) def generate(config_dict): - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().generate(config_dict) return None @@ -103,7 +103,7 @@ def apply(config_dict): if not pim_pid: call('/usr/lib/frr/pimd -d -F traditional --daemon -A 127.0.0.1') - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().apply() return None diff --git a/src/conf_mode/protocols_pim6.py b/src/conf_mode/protocols_pim6.py index a5d6128149..ff8fa38c3c 100755 --- a/src/conf_mode/protocols_pim6.py +++ b/src/conf_mode/protocols_pim6.py @@ -75,12 +75,12 @@ def verify(config_dict): unique.append(gr_addr) def generate(config_dict): - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().generate(config_dict) return None def apply(config_dict): - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().apply() return None diff --git a/src/conf_mode/protocols_rip.py b/src/conf_mode/protocols_rip.py index 7eb0605043..38108c9f51 100755 --- a/src/conf_mode/protocols_rip.py +++ b/src/conf_mode/protocols_rip.py @@ -68,12 +68,12 @@ def verify(config_dict): f'with "split-horizon disable" for "{interface}"!') def generate(config_dict): - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().generate(config_dict) return None def apply(config_dict): - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().apply() return None diff --git a/src/conf_mode/protocols_ripng.py b/src/conf_mode/protocols_ripng.py index 5884e61f96..46f3ce0144 100755 --- a/src/conf_mode/protocols_ripng.py +++ b/src/conf_mode/protocols_ripng.py @@ -68,12 +68,12 @@ def verify(config_dict): f'with "split-horizon disable" for "{interface}"!') def generate(config_dict): - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().generate(config_dict) return None def apply(config_dict): - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().apply() return None diff --git a/src/conf_mode/protocols_rpki.py b/src/conf_mode/protocols_rpki.py index 33696a742a..d3f515febd 100755 --- a/src/conf_mode/protocols_rpki.py +++ b/src/conf_mode/protocols_rpki.py @@ -94,12 +94,12 @@ def generate(config_dict): write_file(cache_config['ssh']['public_key_file'], wrap_openssh_public_key(public_key_data, public_key_type)) write_file(cache_config['ssh']['private_key_file'], wrap_openssh_private_key(private_key_data)) - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().generate(config_dict) return None def apply(config_dict): - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().apply() return None diff --git a/src/conf_mode/protocols_segment-routing.py b/src/conf_mode/protocols_segment-routing.py index a776d10380..cc42e5ac78 100755 --- a/src/conf_mode/protocols_segment-routing.py +++ b/src/conf_mode/protocols_segment-routing.py @@ -54,7 +54,7 @@ def verify(config_dict): return None def generate(config_dict): - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().generate(config_dict) return None @@ -88,7 +88,7 @@ def apply(config_dict): else: sysctl_write(f'net.ipv6.conf.{interface}.seg6_enabled', '0') - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().apply() return None diff --git a/src/conf_mode/protocols_static.py b/src/conf_mode/protocols_static.py index 69500377c1..29fa530f42 100755 --- a/src/conf_mode/protocols_static.py +++ b/src/conf_mode/protocols_static.py @@ -29,10 +29,6 @@ from vyos import airbag airbag.enable() -vrf = None -if len(argv) > 1: - vrf = argv[1] - config_file = '/etc/iproute2/rt_tables.d/vyos-static.conf' def get_config(config=None): @@ -41,13 +37,16 @@ def get_config(config=None): else: conf = Config() - return get_frrender_dict(conf) + return get_frrender_dict(conf, argv) def verify(config_dict): - global vrf - if not has_frr_protocol_in_dict(config_dict, 'static', vrf): + if not has_frr_protocol_in_dict(config_dict, 'static'): return None + vrf = None + if 'vrf_context' in config_dict: + vrf = config_dict['vrf_context'] + # eqivalent of the C foo ? 'a' : 'b' statement static = vrf and config_dict['vrf']['name'][vrf]['protocols']['static'] or config_dict['static'] static['policy'] = config_dict['policy'] @@ -82,22 +81,25 @@ def verify(config_dict): return None def generate(config_dict): - global vrf - if not has_frr_protocol_in_dict(config_dict, 'static', vrf): + if not has_frr_protocol_in_dict(config_dict, 'static'): return None + vrf = None + if 'vrf_context' in config_dict: + vrf = config_dict['vrf_context'] + # eqivalent of the C foo ? 'a' : 'b' statement static = vrf and config_dict['vrf']['name'][vrf]['protocols']['static'] or config_dict['static'] # Put routing table names in /etc/iproute2/rt_tables render(config_file, 'iproute2/static.conf.j2', static) - if 'frrender_cls' not in config_dict: + if config_dict and 'frrender_cls' not in config_dict: FRRender().generate(config_dict) return None -def apply(static): - if 'frrender_cls' not in config_dict: +def apply(config_dict): + if config_dict and 'frrender_cls' not in config_dict: FRRender().apply() return None diff --git a/src/services/vyos-configd b/src/services/vyos-configd index 21d91005ae..ecad85801b 100755 --- a/src/services/vyos-configd +++ b/src/services/vyos-configd @@ -331,7 +331,7 @@ if __name__ == '__main__': scripts_called = getattr(config, 'scripts_called', []) logger.debug(f'scripts_called: {scripts_called}') - if hasattr(config, 'frrender_cls'): + if hasattr(config, 'frrender_cls') and res == R_SUCCESS: frrender_cls = getattr(config, 'frrender_cls') frrender_cls.apply() else: From 2e4725cd77a33f91e091d86d94056f43dafd153d Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Wed, 11 Dec 2024 21:07:28 +0100 Subject: [PATCH 18/28] frr: T6746: handle "system ip" and "system ipv6" with FRRender class FRR 10.2 will use "[no] ip forwarding" and "[no] ipv6 forwarding" to enable or disable IP(v6) forwarding. We no longer rely on sysctl as this was overridden by FRR later on. Remove code path for sysctl setting and solely rely on FRR. --- data/templates/frr/zebra.route-map.frr.j2 | 2 + python/vyos/configdict.py | 9 +++ python/vyos/frrender.py | 14 ++++- smoketest/scripts/cli/test_system_ip.py | 55 ++++++++++-------- smoketest/scripts/cli/test_system_ipv6.py | 57 +++++++++++-------- src/conf_mode/system_ip.py | 69 ++++++++--------------- src/conf_mode/system_ipv6.py | 65 ++++++++------------- 7 files changed, 136 insertions(+), 135 deletions(-) diff --git a/data/templates/frr/zebra.route-map.frr.j2 b/data/templates/frr/zebra.route-map.frr.j2 index 669d58354e..70a810f434 100644 --- a/data/templates/frr/zebra.route-map.frr.j2 +++ b/data/templates/frr/zebra.route-map.frr.j2 @@ -1,4 +1,6 @@ ! +{{ 'no ' if disable_forwarding is vyos_defined }}{{ afi }} forwarding +! {% if nht.no_resolve_via_default is vyos_defined %} no {{ afi }} nht resolve-via-default {% endif %} diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index f5e84267eb..9522d8fccd 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -775,6 +775,15 @@ def dict_helper_pim_defaults(pim, path): # At least one participating EVPN interface found, add to result dict if tmp: dict['interfaces'] = tmp + # Zebra prefix exchange for Kernel IP/IPv6 and routing protocols + for ip_version in ['ip', 'ipv6']: + ip_cli_path = ['system', ip_version] + ip_dict = conf.get_config_dict(ip_cli_path, key_mangling=('-', '_'), + get_first_key=True, with_recursive_defaults=True) + if ip_dict: + ip_dict['afi'] = ip_version + dict.update({ip_version : ip_dict}) + # Enable SNMP agentx support # SNMP AgentX support cannot be disabled once enabled if conf.exists(['service', 'snmp']): diff --git a/python/vyos/frrender.py b/python/vyos/frrender.py index f1bb390945..7a0b661a30 100644 --- a/python/vyos/frrender.py +++ b/python/vyos/frrender.py @@ -32,12 +32,16 @@ def debug(message): return print(message) -pim_daemon = 'pimd' - frr_protocols = ['babel', 'bfd', 'bgp', 'eigrp', 'isis', 'mpls', 'nhrp', 'openfabric', 'ospf', 'ospfv3', 'pim', 'pim6', 'rip', 'ripng', 'rpki', 'segment_routing', 'static'] +bgp_daemon = 'bgpd' +isis_daemon = 'isisd' +mgmt_daemon = 'mgmtd' +pim_daemon = 'pimd' +zebra_daemon = 'zebra' + class FRRender: def __init__(self): self._frr_conf = '/run/frr/config/frr.conf' @@ -100,6 +104,12 @@ def inline_helper(config_dict) -> str: if 'static' in config_dict and 'deleted' not in config_dict['static']: output += render_to_string('frr/staticd.frr.j2', config_dict['static']) output += '\n' + if 'ip' in config_dict and 'deleted' not in config_dict['ip']: + output += render_to_string('frr/zebra.route-map.frr.j2', config_dict['ip']) + output += '\n' + if 'ipv6' in config_dict and 'deleted' not in config_dict['ipv6']: + output += render_to_string('frr/zebra.route-map.frr.j2', config_dict['ipv6']) + output += '\n' return output debug('======< RENDERING CONFIG >======') diff --git a/smoketest/scripts/cli/test_system_ip.py b/smoketest/scripts/cli/test_system_ip.py index 4ab5e81812..7d730f7b28 100755 --- a/smoketest/scripts/cli/test_system_ip.py +++ b/smoketest/scripts/cli/test_system_ip.py @@ -18,12 +18,21 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError -from vyos.utils.file import read_file -from vyos.frr import mgmt_daemon +from vyos.utils.system import sysctl_read +from vyos.xml_ref import default_value +from vyos.frrender import mgmt_daemon +from vyos.frrender import zebra_daemon base_path = ['system', 'ip'] class TestSystemIP(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestSystemIP, cls).setUpClass() + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + def tearDown(self): self.cli_delete(base_path) self.cli_commit() @@ -31,47 +40,45 @@ def tearDown(self): def test_system_ip_forwarding(self): # Test if IPv4 forwarding can be disabled globally, default is '1' # which means forwarding enabled - all_forwarding = '/proc/sys/net/ipv4/conf/all/forwarding' - self.assertEqual(read_file(all_forwarding), '1') + self.assertEqual(sysctl_read('net.ipv4.conf.all.forwarding'), '1') self.cli_set(base_path + ['disable-forwarding']) self.cli_commit() + self.assertEqual(sysctl_read('net.ipv4.conf.all.forwarding'), '0') + frrconfig = self.getFRRconfig('', end='', daemon=zebra_daemon) + self.assertIn('no ip forwarding', frrconfig) - self.assertEqual(read_file(all_forwarding), '0') + self.cli_delete(base_path + ['disable-forwarding']) + self.cli_commit() + self.assertEqual(sysctl_read('net.ipv4.conf.all.forwarding'), '1') + frrconfig = self.getFRRconfig('', end='', daemon=zebra_daemon) + self.assertNotIn('no ip forwarding', frrconfig) def test_system_ip_multipath(self): # Test IPv4 multipathing options, options default to off -> '0' - use_neigh = '/proc/sys/net/ipv4/fib_multipath_use_neigh' - hash_policy = '/proc/sys/net/ipv4/fib_multipath_hash_policy' - - self.assertEqual(read_file(use_neigh), '0') - self.assertEqual(read_file(hash_policy), '0') + self.assertEqual(sysctl_read('net.ipv4.fib_multipath_use_neigh'), '0') + self.assertEqual(sysctl_read('net.ipv4.fib_multipath_hash_policy'), '0') self.cli_set(base_path + ['multipath', 'ignore-unreachable-nexthops']) self.cli_set(base_path + ['multipath', 'layer4-hashing']) self.cli_commit() - self.assertEqual(read_file(use_neigh), '1') - self.assertEqual(read_file(hash_policy), '1') + self.assertEqual(sysctl_read('net.ipv4.fib_multipath_use_neigh'), '1') + self.assertEqual(sysctl_read('net.ipv4.fib_multipath_hash_policy'), '1') def test_system_ip_arp_table_size(self): - # Maximum number of entries to keep in the ARP cache, the - # default is 8k + cli_default = int(default_value(base_path + ['arp', 'table-size'])) + def _verify_gc_thres(table_size): + self.assertEqual(sysctl_read('net.ipv4.neigh.default.gc_thresh3'), str(table_size)) + self.assertEqual(sysctl_read('net.ipv4.neigh.default.gc_thresh2'), str(table_size // 2)) + self.assertEqual(sysctl_read('net.ipv4.neigh.default.gc_thresh1'), str(table_size // 8)) - gc_thresh3 = '/proc/sys/net/ipv4/neigh/default/gc_thresh3' - gc_thresh2 = '/proc/sys/net/ipv4/neigh/default/gc_thresh2' - gc_thresh1 = '/proc/sys/net/ipv4/neigh/default/gc_thresh1' - self.assertEqual(read_file(gc_thresh3), '8192') - self.assertEqual(read_file(gc_thresh2), '4096') - self.assertEqual(read_file(gc_thresh1), '1024') + _verify_gc_thres(cli_default) for size in [1024, 2048, 4096, 8192, 16384, 32768]: self.cli_set(base_path + ['arp', 'table-size', str(size)]) self.cli_commit() - - self.assertEqual(read_file(gc_thresh3), str(size)) - self.assertEqual(read_file(gc_thresh2), str(size // 2)) - self.assertEqual(read_file(gc_thresh1), str(size // 8)) + _verify_gc_thres(size) def test_system_ip_protocol_route_map(self): protocols = ['any', 'babel', 'bgp', 'connected', 'eigrp', 'isis', diff --git a/smoketest/scripts/cli/test_system_ipv6.py b/smoketest/scripts/cli/test_system_ipv6.py index 1c778a11dd..be9751c4d5 100755 --- a/smoketest/scripts/cli/test_system_ipv6.py +++ b/smoketest/scripts/cli/test_system_ipv6.py @@ -19,17 +19,21 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError -from vyos.utils.file import read_file -from vyos.frr import mgmt_daemon +from vyos.utils.system import sysctl_read +from vyos.xml_ref import default_value +from vyos.frrender import mgmt_daemon +from vyos.frrender import zebra_daemon base_path = ['system', 'ipv6'] -file_forwarding = '/proc/sys/net/ipv6/conf/all/forwarding' -file_disable = '/proc/sys/net/ipv6/conf/all/disable_ipv6' -file_dad = '/proc/sys/net/ipv6/conf/all/accept_dad' -file_multipath = '/proc/sys/net/ipv6/fib_multipath_hash_policy' - class TestSystemIPv6(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestSystemIPv6, cls).setUpClass() + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + def tearDown(self): self.cli_delete(base_path) self.cli_commit() @@ -37,16 +41,23 @@ def tearDown(self): def test_system_ipv6_forwarding(self): # Test if IPv6 forwarding can be disabled globally, default is '1' # which means forwearding enabled - self.assertEqual(read_file(file_forwarding), '1') + self.assertEqual(sysctl_read('net.ipv6.conf.all.forwarding'), '1') self.cli_set(base_path + ['disable-forwarding']) self.cli_commit() + self.assertEqual(sysctl_read('net.ipv6.conf.all.forwarding'), '0') + frrconfig = self.getFRRconfig('', end='', daemon=zebra_daemon) + self.assertIn('no ipv6 forwarding', frrconfig) - self.assertEqual(read_file(file_forwarding), '0') + self.cli_delete(base_path + ['disable-forwarding']) + self.cli_commit() + self.assertEqual(sysctl_read('net.ipv6.conf.all.forwarding'), '1') + frrconfig = self.getFRRconfig('', end='', daemon=zebra_daemon) + self.assertNotIn('no ipv6 forwarding', frrconfig) def test_system_ipv6_strict_dad(self): # This defaults to 1 - self.assertEqual(read_file(file_dad), '1') + self.assertEqual(sysctl_read('net.ipv6.conf.all.accept_dad'), '1') # Do not assign any IPv6 address on interfaces, this requires a reboot # which can not be tested, but we can read the config file :) @@ -54,11 +65,11 @@ def test_system_ipv6_strict_dad(self): self.cli_commit() # Verify configuration file - self.assertEqual(read_file(file_dad), '2') + self.assertEqual(sysctl_read('net.ipv6.conf.all.accept_dad'), '2') def test_system_ipv6_multipath(self): # This defaults to 0 - self.assertEqual(read_file(file_multipath), '0') + self.assertEqual(sysctl_read('net.ipv6.fib_multipath_hash_policy'), '0') # Do not assign any IPv6 address on interfaces, this requires a reboot # which can not be tested, but we can read the config file :) @@ -66,26 +77,24 @@ def test_system_ipv6_multipath(self): self.cli_commit() # Verify configuration file - self.assertEqual(read_file(file_multipath), '1') + self.assertEqual(sysctl_read('net.ipv6.fib_multipath_hash_policy'), '1') def test_system_ipv6_neighbor_table_size(self): # Maximum number of entries to keep in the ARP cache, the # default is 8192 + cli_default = int(default_value(base_path + ['neighbor', 'table-size'])) - gc_thresh3 = '/proc/sys/net/ipv6/neigh/default/gc_thresh3' - gc_thresh2 = '/proc/sys/net/ipv6/neigh/default/gc_thresh2' - gc_thresh1 = '/proc/sys/net/ipv6/neigh/default/gc_thresh1' - self.assertEqual(read_file(gc_thresh3), '8192') - self.assertEqual(read_file(gc_thresh2), '4096') - self.assertEqual(read_file(gc_thresh1), '1024') + def _verify_gc_thres(table_size): + self.assertEqual(sysctl_read('net.ipv6.neigh.default.gc_thresh3'), str(table_size)) + self.assertEqual(sysctl_read('net.ipv6.neigh.default.gc_thresh2'), str(table_size // 2)) + self.assertEqual(sysctl_read('net.ipv6.neigh.default.gc_thresh1'), str(table_size // 8)) + + _verify_gc_thres(cli_default) for size in [1024, 2048, 4096, 8192, 16384, 32768]: self.cli_set(base_path + ['neighbor', 'table-size', str(size)]) self.cli_commit() - - self.assertEqual(read_file(gc_thresh3), str(size)) - self.assertEqual(read_file(gc_thresh2), str(size // 2)) - self.assertEqual(read_file(gc_thresh1), str(size // 8)) + _verify_gc_thres(size) def test_system_ipv6_protocol_route_map(self): protocols = ['any', 'babel', 'bgp', 'connected', 'isis', @@ -143,4 +152,4 @@ def test_system_ipv6_nht(self): self.assertNotIn(f'no ipv6 nht resolve-via-default', frrconfig) if __name__ == '__main__': - unittest.main(verbosity=2) + unittest.main(verbosity=2, failfast=True) diff --git a/src/conf_mode/system_ip.py b/src/conf_mode/system_ip.py index 5afb574049..374e6e611e 100755 --- a/src/conf_mode/system_ip.py +++ b/src/conf_mode/system_ip.py @@ -17,17 +17,16 @@ from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge +from vyos.configdep import set_dependents +from vyos.configdep import call_dependents +from vyos.configdict import get_frrender_dict +from vyos.configverify import has_frr_protocol_in_dict from vyos.configverify import verify_route_map -from vyos.template import render_to_string +from vyos.frrender import FRRender from vyos.utils.dict import dict_search -from vyos.utils.file import write_file from vyos.utils.process import is_systemd_service_active from vyos.utils.system import sysctl_write -from vyos.configdep import set_dependents -from vyos.configdep import call_dependents from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() @@ -36,42 +35,36 @@ def get_config(config=None): conf = config else: conf = Config() - base = ['system', 'ip'] - - opt = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, - with_recursive_defaults=True) - - # When working with FRR we need to know the corresponding address-family - opt['afi'] = 'ip' - - # We also need the route-map information from the config - # - # XXX: one MUST always call this without the key_mangling() option! See - # vyos.configverify.verify_common_route_maps() for more information. - tmp = {'policy' : {'route-map' : conf.get_config_dict(['policy', 'route-map'], - get_first_key=True)}} - # Merge policy dict into "regular" config dict - opt = dict_merge(tmp, opt) # If IPv4 ARP table size is set here and also manually in sysctl, the more # fine grained value from sysctl must win set_dependents('sysctl', conf) + return get_frrender_dict(conf) - return opt +def verify(config_dict): + if not has_frr_protocol_in_dict(config_dict, 'ip'): + return None + + opt = config_dict['ip'] + opt['policy'] = config_dict['policy'] -def verify(opt): if 'protocol' in opt: for protocol, protocol_options in opt['protocol'].items(): if 'route_map' in protocol_options: verify_route_map(protocol_options['route_map'], opt) return -def generate(opt): - opt['frr_zebra_config'] = render_to_string('frr/zebra.route-map.frr.j2', opt) - return +def generate(config_dict): + if config_dict and 'frrender_cls' not in config_dict: + FRRender().generate(config_dict) + return None + +def apply(config_dict): + if not has_frr_protocol_in_dict(config_dict, 'ip'): + + return None + opt = config_dict['ip'] -def apply(opt): # Apply ARP threshold values # table_size has a default value - thus the key always exists size = int(dict_search('arp.table_size', opt)) @@ -82,11 +75,6 @@ def apply(opt): # Minimum number of stored records is indicated which is not cleared sysctl_write('net.ipv4.neigh.default.gc_thresh1', size // 8) - # enable/disable IPv4 forwarding - tmp = dict_search('disable_forwarding', opt) - value = '0' if (tmp != None) else '1' - write_file('/proc/sys/net/ipv4/conf/all/forwarding', value) - # configure multipath tmp = dict_search('multipath.ignore_unreachable_nexthops', opt) value = '1' if (tmp != None) else '0' @@ -121,18 +109,11 @@ def apply(opt): # running when this script is called first. Skip this part and wait for initial # commit of the configuration to trigger this statement if is_systemd_service_active('frr.service'): - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - - # The route-map used for the FIB (zebra) is part of the zebra daemon - frr_cfg.load_configuration(frr.mgmt_daemon) - frr_cfg.modify_section(r'no ip nht resolve-via-default') - frr_cfg.modify_section(r'ip protocol \w+ route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') - if 'frr_zebra_config' in opt: - frr_cfg.add_before(frr.default_add_before, opt['frr_zebra_config']) - frr_cfg.commit_configuration() + if config_dict and 'frrender_cls' not in config_dict: + FRRender().apply() call_dependents() + return None if __name__ == '__main__': try: diff --git a/src/conf_mode/system_ipv6.py b/src/conf_mode/system_ipv6.py index 90d5100d74..02c9a8201a 100755 --- a/src/conf_mode/system_ipv6.py +++ b/src/conf_mode/system_ipv6.py @@ -18,17 +18,17 @@ from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge +from vyos.configdep import set_dependents +from vyos.configdep import call_dependents +from vyos.configdict import get_frrender_dict +from vyos.configverify import has_frr_protocol_in_dict from vyos.configverify import verify_route_map -from vyos.template import render_to_string +from vyos.frrender import FRRender from vyos.utils.dict import dict_search from vyos.utils.file import write_file from vyos.utils.process import is_systemd_service_active from vyos.utils.system import sysctl_write -from vyos.configdep import set_dependents -from vyos.configdep import call_dependents from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() @@ -37,42 +37,35 @@ def get_config(config=None): conf = config else: conf = Config() - base = ['system', 'ipv6'] - - opt = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, - with_recursive_defaults=True) - - # When working with FRR we need to know the corresponding address-family - opt['afi'] = 'ipv6' - - # We also need the route-map information from the config - # - # XXX: one MUST always call this without the key_mangling() option! See - # vyos.configverify.verify_common_route_maps() for more information. - tmp = {'policy' : {'route-map' : conf.get_config_dict(['policy', 'route-map'], - get_first_key=True)}} - # Merge policy dict into "regular" config dict - opt = dict_merge(tmp, opt) # If IPv6 neighbor table size is set here and also manually in sysctl, the more # fine grained value from sysctl must win set_dependents('sysctl', conf) + return get_frrender_dict(conf) + +def verify(config_dict): + if not has_frr_protocol_in_dict(config_dict, 'ipv6'): + return None - return opt + opt = config_dict['ipv6'] + opt['policy'] = config_dict['policy'] -def verify(opt): if 'protocol' in opt: for protocol, protocol_options in opt['protocol'].items(): if 'route_map' in protocol_options: verify_route_map(protocol_options['route_map'], opt) return -def generate(opt): - opt['frr_zebra_config'] = render_to_string('frr/zebra.route-map.frr.j2', opt) - return +def generate(config_dict): + if config_dict and 'frrender_cls' not in config_dict: + FRRender().generate(config_dict) + return None + +def apply(config_dict): + if not has_frr_protocol_in_dict(config_dict, 'ipv6'): + return None + opt = config_dict['ipv6'] -def apply(opt): # configure multipath tmp = dict_search('multipath.layer4_hashing', opt) value = '1' if (tmp != None) else '0' @@ -88,11 +81,6 @@ def apply(opt): # Minimum number of stored records is indicated which is not cleared sysctl_write('net.ipv6.neigh.default.gc_thresh1', size // 8) - # enable/disable IPv6 forwarding - tmp = dict_search('disable_forwarding', opt) - value = '0' if (tmp != None) else '1' - write_file('/proc/sys/net/ipv6/conf/all/forwarding', value) - # configure IPv6 strict-dad tmp = dict_search('strict_dad', opt) value = '2' if (tmp != None) else '1' @@ -105,16 +93,11 @@ def apply(opt): # running when this script is called first. Skip this part and wait for initial # commit of the configuration to trigger this statement if is_systemd_service_active('frr.service'): - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(frr.mgmt_daemon) - frr_cfg.modify_section(r'no ipv6 nht resolve-via-default') - frr_cfg.modify_section(r'ipv6 protocol \w+ route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') - if 'frr_zebra_config' in opt: - frr_cfg.add_before(frr.default_add_before, opt['frr_zebra_config']) - frr_cfg.commit_configuration() + if config_dict and 'frrender_cls' not in config_dict: + FRRender().apply() call_dependents() + return None if __name__ == '__main__': try: From 147751ec59527800e956b7ea689805ba80769abe Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Wed, 11 Dec 2024 21:22:41 +0100 Subject: [PATCH 19/28] static: T6746: migrate BFD CLI nodes Migrate "set protocols static route next-hop bfd multi-hop source profile " to: "set protocols static route next-hop bfd profile bar" FRR supports only one source IP address per BFD multi-hop session. VyOS had CLI cupport for multiple source addresses which made no sense. --- data/templates/frr/staticd.frr.j2 | 4 +- .../include/static/bfd-multi-hop.xml.i | 8 - .../include/static/static-route.xml.i | 10 +- .../include/static/static-route6.xml.i | 10 +- .../include/version/quagga-version.xml.i | 2 +- smoketest/config-tests/static-route-basic | 35 +++++ smoketest/configs/static-route-basic | 148 ++++++++++++++++++ .../scripts/cli/test_protocols_static.py | 3 +- src/migration-scripts/quagga/11-to-12 | 54 +++++++ 9 files changed, 257 insertions(+), 17 deletions(-) delete mode 100644 interface-definitions/include/static/bfd-multi-hop.xml.i create mode 100644 smoketest/config-tests/static-route-basic create mode 100644 smoketest/configs/static-route-basic create mode 100644 src/migration-scripts/quagga/11-to-12 diff --git a/data/templates/frr/staticd.frr.j2 b/data/templates/frr/staticd.frr.j2 index 227807c62c..90d17ec144 100644 --- a/data/templates/frr/staticd.frr.j2 +++ b/data/templates/frr/staticd.frr.j2 @@ -15,8 +15,8 @@ {% set ip_route = ip_route ~ ' bfd' %} {% if config.bfd.multi_hop is vyos_defined %} {% set ip_route = ip_route ~ ' multi-hop' %} -{% if config.bfd.source_address is vyos_defined %} -{% set ip_route = ip_route ~ ' source ' ~ config.bfd.source_address %} +{% if config.bfd.multi_hop.source_address is vyos_defined %} +{% set ip_route = ip_route ~ ' source ' ~ config.bfd.multi_hop.source_address %} {% endif %} {% endif %} {% if config.bfd.profile is vyos_defined %} diff --git a/interface-definitions/include/static/bfd-multi-hop.xml.i b/interface-definitions/include/static/bfd-multi-hop.xml.i deleted file mode 100644 index e53994191d..0000000000 --- a/interface-definitions/include/static/bfd-multi-hop.xml.i +++ /dev/null @@ -1,8 +0,0 @@ - - - - Enable BFD multi-hop session (requires source-address) - - - - diff --git a/interface-definitions/include/static/static-route.xml.i b/interface-definitions/include/static/static-route.xml.i index fa11311185..fd73662865 100644 --- a/interface-definitions/include/static/static-route.xml.i +++ b/interface-definitions/include/static/static-route.xml.i @@ -57,8 +57,14 @@ #include - #include - #include + + + Configure BFD multi-hop session + + + #include + + diff --git a/interface-definitions/include/static/static-route6.xml.i b/interface-definitions/include/static/static-route6.xml.i index e75385dc73..6fcc18b8a1 100644 --- a/interface-definitions/include/static/static-route6.xml.i +++ b/interface-definitions/include/static/static-route6.xml.i @@ -58,8 +58,14 @@ #include - #include - #include + + + Configure BFD multi-hop session + + + #include + + diff --git a/interface-definitions/include/version/quagga-version.xml.i b/interface-definitions/include/version/quagga-version.xml.i index 23d884cd48..10ca2816e0 100644 --- a/interface-definitions/include/version/quagga-version.xml.i +++ b/interface-definitions/include/version/quagga-version.xml.i @@ -1,3 +1,3 @@ - + diff --git a/smoketest/config-tests/static-route-basic b/smoketest/config-tests/static-route-basic new file mode 100644 index 0000000000..4416e6b197 --- /dev/null +++ b/smoketest/config-tests/static-route-basic @@ -0,0 +1,35 @@ +set interfaces ethernet eth0 duplex 'auto' +set interfaces ethernet eth0 speed 'auto' +set interfaces ethernet eth0 vif 203 address '172.18.203.10/24' +set interfaces ethernet eth1 duplex 'auto' +set interfaces ethernet eth1 speed 'auto' +set protocols static route 10.0.0.0/8 blackhole distance '200' +set protocols static route 10.0.0.0/8 blackhole tag '333' +set protocols static route 10.0.0.0/8 next-hop 192.0.2.140 bfd multi-hop source-address '192.0.2.10' +set protocols static route 10.0.0.0/8 next-hop 192.0.2.140 bfd profile 'vyos-test' +set protocols static route 10.0.0.0/8 next-hop 192.0.2.140 distance '123' +set protocols static route 10.0.0.0/8 next-hop 192.0.2.140 interface 'eth0' +set protocols static route 172.16.0.0/16 next-hop 172.18.203.254 bfd multi-hop source-address '172.18.203.254' +set protocols static route 172.16.0.0/16 next-hop 172.18.203.254 bfd profile 'foo' +set protocols static route6 2001:db8:1::/48 next-hop fe80::1 bfd multi-hop source-address 'fe80::1' +set protocols static route6 2001:db8:1::/48 next-hop fe80::1 bfd profile 'bar' +set protocols static route6 2001:db8:1::/48 next-hop fe80::1 interface 'eth0.203' +set protocols static route6 2001:db8:2::/48 next-hop fe80::1 bfd multi-hop source-address 'fe80::1' +set protocols static route6 2001:db8:2::/48 next-hop fe80::1 bfd profile 'bar' +set protocols static route6 2001:db8:2::/48 next-hop fe80::1 interface 'eth0.203' +set protocols static route6 2001:db8:3::/48 next-hop fe80::1 bfd +set protocols static route6 2001:db8:3::/48 next-hop fe80::1 interface 'eth0.203' +set service lldp interface all +set service ntp allow-client address '0.0.0.0/0' +set service ntp allow-client address '::/0' +set service ntp server 172.16.100.10 +set service ntp server 172.16.100.20 +set service ntp server 172.16.110.30 +set system config-management commit-revisions '100' +set system console device ttyS0 speed '115200' +set system host-name 'vyos' +set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0' +set system login user vyos authentication plaintext-password '' +set system syslog global facility all level 'info' +set system syslog global facility local7 level 'debug' +set system time-zone 'Asia/Macau' diff --git a/smoketest/configs/static-route-basic b/smoketest/configs/static-route-basic new file mode 100644 index 0000000000..4bf114e335 --- /dev/null +++ b/smoketest/configs/static-route-basic @@ -0,0 +1,148 @@ +interfaces { + ethernet eth0 { + duplex "auto" + speed "auto" + vif 203 { + address "172.18.203.10/24" + } + } + ethernet eth1 { + duplex "auto" + speed "auto" + } +} +protocols { + static { + multicast { + interface-route 224.0.0.0/24 { + next-hop-interface eth0.203 { + distance "10" + } + } + route 224.0.0.0/24 { + next-hop 172.18.203.254 { + distance "20" + } + } + } + route 10.0.0.0/8 { + blackhole { + distance "200" + tag "333" + } + next-hop 192.0.2.140 { + bfd { + multi-hop { + source 192.0.2.10 { + profile "vyos-test" + } + } + } + distance "123" + interface "eth0" + } + } + route 172.16.0.0/16 { + next-hop 172.18.203.254 { + bfd { + multi-hop { + source 172.18.203.254 { + profile "foo" + } + } + } + } + } + route6 2001:db8:1::/48 { + next-hop fe80::1 { + bfd { + multi-hop { + source fe80::1 { + profile "bar" + } + } + } + interface eth0.203 + } + } + route6 2001:db8:2::/48 { + next-hop fe80::1 { + bfd { + multi-hop { + source fe80::1 { + profile "bar" + } + } + } + interface eth0.203 + } + } + route6 2001:db8:3::/48 { + next-hop fe80::1 { + bfd { + } + interface eth0.203 + } + } + } +} +service { + lldp { + interface all { + } + } + ntp { + allow-client { + address "0.0.0.0/0" + address "::/0" + } + server 172.16.100.10 { + } + server 172.16.100.20 { + } + server 172.16.110.30 { + } + } +} +system { + config-management { + commit-revisions 100 + } + console { + device ttyS0 { + speed 115200 + } + } + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0 + plaintext-password "" + } + } + } + ntp { + server 0.pool.ntp.org { + } + server 1.pool.ntp.org { + } + server 2.pool.ntp.org { + } + } + syslog { + global { + facility all { + level info + } + facility local7 { + level debug + } + } + } + time-zone "Asia/Macau" +} + +// Warning: Do not remove the following line. +// vyos-config-version: "bgp@5:broadcast-relay@1:cluster@2:config-management@1:conntrack@5:conntrack-sync@2:container@2:dhcp-relay@2:dhcp-server@8:dhcpv6-server@1:dns-dynamic@4:dns-forwarding@4:firewall@15:flow-accounting@1:https@6:ids@1:interfaces@32:ipoe-server@3:ipsec@13:isis@3:l2tp@9:lldp@2:mdns@1:monitoring@1:nat@8:nat66@3:ntp@3:openconnect@3:ospf@2:pim@1:policy@8:pppoe-server@10:pptp@5:qos@2:quagga@11:reverse-proxy@1:rip@1:rpki@2:salt@1:snmp@3:ssh@2:sstp@6:system@27:vrf@3:vrrp@4:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2" +// Release version: 1.4.0 diff --git a/smoketest/scripts/cli/test_protocols_static.py b/smoketest/scripts/cli/test_protocols_static.py index 086235086a..a2cde02370 100755 --- a/smoketest/scripts/cli/test_protocols_static.py +++ b/smoketest/scripts/cli/test_protocols_static.py @@ -202,8 +202,7 @@ def test_01_static(self): if 'bfd_profile' in next_hop_config: self.cli_set(base + ['next-hop', next_hop, 'bfd', 'profile', next_hop_config['bfd_profile']]) if 'bfd_source' in next_hop_config: - self.cli_set(base + ['next-hop', next_hop, 'bfd', 'multi-hop']) - self.cli_set(base + ['next-hop', next_hop, 'bfd', 'source-address', next_hop_config['bfd_source']]) + self.cli_set(base + ['next-hop', next_hop, 'bfd', 'multi-hop', 'source-address', next_hop_config['bfd_source']]) if 'segments' in next_hop_config: self.cli_set(base + ['next-hop', next_hop, 'segments', next_hop_config['segments']]) diff --git a/src/migration-scripts/quagga/11-to-12 b/src/migration-scripts/quagga/11-to-12 new file mode 100644 index 0000000000..becc441623 --- /dev/null +++ b/src/migration-scripts/quagga/11-to-12 @@ -0,0 +1,54 @@ +# Copyright 2024 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see . + +# T6747: +# - Migrate static BFD configuration to match FRR possibillities +# - Consolidate static multicast routing configuration under a new node + +from vyos.configtree import ConfigTree + +static_base = ['protocols', 'static'] + +def migrate(config: ConfigTree) -> None: + # Check for static route/route6 configuration + for route_route6 in ['route', 'route6']: + route_route6_base = static_base + [route_route6] + if not config.exists(route_route6_base): + continue + + for prefix in config.list_nodes(route_route6_base): + next_hop_base = route_route6_base + [prefix, 'next-hop'] + if not config.exists(next_hop_base): + continue + + for next_hop in config.list_nodes(next_hop_base): + multi_hop_base = next_hop_base + [next_hop, 'bfd', 'multi-hop'] + + if not config.exists(multi_hop_base): + continue + + mh_source_base = multi_hop_base + ['source'] + source = None + profile = None + for src_ip in config.list_nodes(mh_source_base): + source = src_ip + if config.exists(mh_source_base + [source, 'profile']): + profile = config.return_value(mh_source_base + [source, 'profile']) + # FRR only supports one source, we will use the first one + break + + config.delete(multi_hop_base) + config.set(multi_hop_base + ['source-address'], value=source) + config.set(next_hop_base + [next_hop, 'bfd', 'profile'], value=profile) From 4d0774e534766d70961975933635e6c54dcc1982 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Wed, 11 Dec 2024 21:35:54 +0100 Subject: [PATCH 20/28] vrf: T6746: bugfix change of VNI VNI was always retrieved via effective configuration and not active configuration. --- python/vyos/configdict.py | 4 ++-- smoketest/scripts/cli/test_vrf.py | 2 +- src/conf_mode/vrf.py | 15 ++------------- 3 files changed, 5 insertions(+), 16 deletions(-) diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 9522d8fccd..fb63650602 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -1100,8 +1100,8 @@ def dict_helper_pim_defaults(pim, path): vrf['name'][vrf_name]['protocols'].update({'static': {'deleted' : ''}}) vrf_vni_path = ['vrf', 'name', vrf_name, 'vni'] - if conf.exists_effective(vrf_vni_path): - vrf_config.update({'vni': conf.return_effective_value(vrf_vni_path)}) + if conf.exists(vrf_vni_path): + vrf_config.update({'vni': conf.return_value(vrf_vni_path)}) dict.update({'vrf' : vrf}) elif conf.exists_effective(vrf_cli_path): diff --git a/smoketest/scripts/cli/test_vrf.py b/smoketest/scripts/cli/test_vrf.py index 3cab5248e7..f4ed1a61f3 100755 --- a/smoketest/scripts/cli/test_vrf.py +++ b/smoketest/scripts/cli/test_vrf.py @@ -23,7 +23,7 @@ from jmespath import search from vyos.configsession import ConfigSessionError -from vyos.frr import mgmt_daemon +from vyos.frrender import mgmt_daemon from vyos.ifconfig import Interface from vyos.ifconfig import Section from vyos.utils.file import read_file diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index 6eea9af4db..1b19c55d27 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -20,7 +20,6 @@ from vyos.config import Config from vyos.configdict import get_frrender_dict -from vyos.configdict import dict_merge from vyos.configdict import node_changed from vyos.configverify import verify_route_map from vyos.firewall import conntrack_required @@ -135,16 +134,6 @@ def get_config(config=None): # We need to merge the FRR rendering dict into the VRF dict # this is required to get the route-map information to FRR vrf.update({'frr_dict' : get_frrender_dict(conf)}) - - # We also need the route-map information from the config - # - # XXX: one MUST always call this without the key_mangling() option! See - # vyos.configverify.verify_common_route_maps() for more information. - tmp = {'policy' : {'route-map' : conf.get_config_dict(['policy', 'route-map'], - get_first_key=True)}} - - # Merge policy dict into "regular" config dict - vrf = dict_merge(tmp, vrf) return vrf def verify(vrf): @@ -194,13 +183,13 @@ def verify(vrf): if tmp != None: for protocol, protocol_options in tmp.items(): if 'route_map' in protocol_options: - verify_route_map(protocol_options['route_map'], vrf) + verify_route_map(protocol_options['route_map'], vrf['frr_dict']) tmp = dict_search('ipv6.protocol', vrf_config) if tmp != None: for protocol, protocol_options in tmp.items(): if 'route_map' in protocol_options: - verify_route_map(protocol_options['route_map'], vrf) + verify_route_map(protocol_options['route_map'], vrf['frr_dict']) return None From 76b74d62b607961f08bd0284a9dbc5427ba48e1d Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Wed, 11 Dec 2024 21:37:34 +0100 Subject: [PATCH 21/28] frr: T6746: remove superseeded vyos.frr module --- python/vyos/frr.py | 569 ------------------ python/vyos/frrender.py | 1 + .../scripts/cli/test_interfaces_bonding.py | 2 +- .../scripts/cli/test_interfaces_ethernet.py | 2 +- smoketest/scripts/cli/test_protocols_bgp.py | 2 +- smoketest/scripts/cli/test_protocols_isis.py | 2 +- .../scripts/cli/test_protocols_openfabric.py | 2 +- .../cli/test_protocols_segment-routing.py | 2 +- smoketest/scripts/cli/test_system_ipv6.py | 2 +- 9 files changed, 8 insertions(+), 576 deletions(-) delete mode 100644 python/vyos/frr.py diff --git a/python/vyos/frr.py b/python/vyos/frr.py deleted file mode 100644 index 67279a6f7f..0000000000 --- a/python/vyos/frr.py +++ /dev/null @@ -1,569 +0,0 @@ -# Copyright 2020-2024 VyOS maintainers and contributors -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library 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 -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this library. If not, see . - -r""" -A Library for interracting with the FRR daemon suite. -It supports simple configuration manipulation and loading using the official tools -supplied with FRR (vtysh and frr-reload) - -All configuration management and manipulation is done using strings and regex. - - -Example Usage -##### - -# Reading configuration from frr: -``` ->>> original_config = get_configuration() ->>> repr(original_config) -'!\nfrr version 7.3.1\nfrr defaults traditional\nhostname debian\n...... -``` - - -# Modify a configuration section: -``` ->>> new_bgp_section = 'router bgp 65000\n neighbor 192.0.2.1 remote-as 65000\n' ->>> modified_config = replace_section(original_config, new_bgp_section, replace_re=r'router bgp \d+') ->>> repr(modified_config) -'............router bgp 65000\n neighbor 192.0.2.1 remote-as 65000\n...........' -``` - -Remove a configuration section: -``` ->>> modified_config = remove_section(original_config, r'router ospf') -``` - -Test the new configuration: -``` ->>> try: ->>> mark_configuration(modified configuration) ->>> except ConfigurationNotValid as e: ->>> print('resulting configuration is not valid') ->>> sys.exit(1) -``` - -Apply the new configuration: -``` ->>> try: ->>> replace_configuration(modified_config) ->>> except CommitError as e: ->>> print('Exception while commiting the supplied configuration') ->>> print(e) ->>> exit(1) -``` -""" - -import tempfile -import re - -from vyos import ConfigError -from vyos.defaults import frr_debug_enable -from vyos.utils.process import cmd -from vyos.utils.process import popen -from vyos.utils.process import STDOUT - -import logging -from logging.handlers import SysLogHandler -import os -import sys - -LOG = logging.getLogger(__name__) -DEBUG = False - -ch = SysLogHandler(address='/dev/log') -ch2 = logging.StreamHandler(stream=sys.stdout) -LOG.addHandler(ch) -LOG.addHandler(ch2) - -babel_daemon = 'babeld' -bfd_daemon = 'bfdd' -bgp_daemon = 'bgpd' -eigrp_daemon = 'eigrpd' -isis_daemon = 'isisd' -ldpd_daemon = 'ldpd' -mgmt_daemon = 'mgmtd' -openfabric_daemon = 'fabricd' -ospf_daemon = 'ospfd' -ospf6_daemon = 'ospf6d' -pim_daemon = 'pimd' -pim6_daemon = 'pim6d' -rip_daemon = 'ripd' -ripng_daemon = 'ripngd' -static_daemon = 'staticd' -zebra_daemon = 'zebra' - -_frr_daemons = [zebra_daemon, static_daemon, bgp_daemon, ospf_daemon, ospf6_daemon, rip_daemon, ripng_daemon, mgmt_daemon, - isis_daemon, pim_daemon, pim6_daemon, ldpd_daemon, eigrp_daemon, babel_daemon, bfd_daemon, openfabric_daemon] - -path_vtysh = '/usr/bin/vtysh' -path_frr_reload = '/usr/lib/frr/frr-reload.py' -path_config = '/run/frr' - -default_add_before = r'(ip prefix-list .*|route-map .*|line vty|end)' - - -class FrrError(Exception): - pass - - -class ConfigurationNotValid(FrrError): - """ - The configuratioin supplied to vtysh is not valid - """ - pass - - -class CommitError(FrrError): - """ - Commiting the supplied configuration failed to commit by a unknown reason - see commit error and/or run mark_configuration on the specified configuration - to se error generated - - used by: reload_configuration() - """ - pass - - -class ConfigSectionNotFound(FrrError): - """ - Removal of configuration failed because it is not existing in the supplied configuration - """ - pass - -def init_debugging(): - global DEBUG - - DEBUG = os.path.exists(frr_debug_enable) - if DEBUG: - LOG.setLevel(logging.DEBUG) - -def get_configuration(daemon=None, marked=False): - """ Get current running FRR configuration - daemon: Collect only configuration for the specified FRR daemon, - supplying daemon=None retrieves the complete configuration - marked: Mark the configuration with "end" tags - - return: string containing the running configuration from frr - - """ - if daemon and daemon not in _frr_daemons: - raise ValueError(f'The specified daemon type is not supported {repr(daemon)}') - - cmd = f"{path_vtysh} -c 'show run'" - if daemon: - cmd += f' -d {daemon}' - - output, code = popen(cmd, stderr=STDOUT) - if code: - raise OSError(code, output) - - config = output.replace('\r', '') - # Remove first header lines from FRR config - config = config.split("\n", 3)[-1] - # Mark the configuration with end tags - if marked: - config = mark_configuration(config) - - return config - - -def mark_configuration(config): - """ Add end marks and Test the configuration for syntax faults - If the configuration is valid a marked version of the configuration is returned, - or else it failes with a ConfigurationNotValid Exception - - config: The configuration string to mark/test - return: The marked configuration from FRR - """ - output, code = popen(f"{path_vtysh} -m -f -", stderr=STDOUT, input=config) - - if code == 2: - raise ConfigurationNotValid(str(output)) - elif code: - raise OSError(code, output) - - config = output.replace('\r', '') - return config - - -def reload_configuration(config, daemon=None): - """ Execute frr-reload with the new configuration - This will try to reapply the supplied configuration inside FRR. - The configuration needs to be a complete configuration from the integrated config or - from a daemon. - - config: The configuration to apply - daemon: Apply the conigutaion to the specified FRR daemon, - supplying daemon=None applies to the integrated configuration - return: None - """ - if daemon and daemon not in _frr_daemons: - raise ValueError(f'The specified daemon type is not supported {repr(daemon)}') - - f = tempfile.NamedTemporaryFile('w') - f.write(config) - f.flush() - - LOG.debug(f'reload_configuration: Reloading config using temporary file: {f.name}') - cmd = f'{path_frr_reload} --reload' - if daemon: - cmd += f' --daemon {daemon}' - - if DEBUG: - cmd += f' --debug --stdout' - - cmd += f' {f.name}' - - LOG.debug(f'reload_configuration: Executing command against frr-reload: "{cmd}"') - output, code = popen(cmd, stderr=STDOUT) - f.close() - - for i, e in enumerate(output.split('\n')): - LOG.debug(f'frr-reload output: {i:3} {e}') - - if code == 1: - raise ConfigError(output) - elif code: - raise OSError(code, output) - - return output - - -def save_configuration(): - """ T3217: Save FRR configuration to /run/frr/config/frr.conf """ - return cmd(f'{path_vtysh} -n -w') - - -def execute(command): - """ Run commands inside vtysh - command: str containing commands to execute inside a vtysh session - """ - if not isinstance(command, str): - raise ValueError(f'command needs to be a string: {repr(command)}') - - cmd = f"{path_vtysh} -c '{command}'" - - output, code = popen(cmd, stderr=STDOUT) - if code: - raise OSError(code, output) - - config = output.replace('\r', '') - return config - - -def configure(lines, daemon=False): - """ run commands inside config mode vtysh - lines: list or str conaining commands to execute inside a configure session - only one command executed on each configure() - Executing commands inside a subcontext uses the list to describe the context - ex: ['router bgp 6500', 'neighbor 192.0.2.1 remote-as 65000'] - return: None - """ - if isinstance(lines, str): - lines = [lines] - elif not isinstance(lines, list): - raise ValueError('lines needs to be string or list of commands') - - if daemon and daemon not in _frr_daemons: - raise ValueError(f'The specified daemon type is not supported {repr(daemon)}') - - cmd = f'{path_vtysh}' - if daemon: - cmd += f' -d {daemon}' - - cmd += " -c 'configure terminal'" - for x in lines: - cmd += f" -c '{x}'" - - output, code = popen(cmd, stderr=STDOUT) - if code == 1: - raise ConfigurationNotValid(f'Configuration FRR failed: {repr(output)}') - elif code: - raise OSError(code, output) - - config = output.replace('\r', '') - return config - - -def _replace_section(config, replacement, replace_re, before_re): - r"""Replace a section of FRR config - config: full original configuration - replacement: replacement configuration section - replace_re: The regex to replace - example: ^router bgp \d+$.?*^!$ - this will replace everything between ^router bgp X$ and ^!$ - before_re: When replace_re is not existant, the config will be added before this tag - example: ^line vty$ - - return: modified configuration as a text file - """ - # DEPRECATED, this is replaced by a new implementation - # Check if block is configured, remove the existing instance else add a new one - if re.findall(replace_re, config, flags=re.MULTILINE | re.DOTALL): - # Section is in the configration, replace it - return re.sub(replace_re, replacement, config, count=1, - flags=re.MULTILINE | re.DOTALL) - if before_re: - if not re.findall(before_re, config, flags=re.MULTILINE | re.DOTALL): - raise ConfigSectionNotFound(f"Config section {before_re} not found in config") - - # If no section is in the configuration, add it before the line vty line - return re.sub(before_re, rf'{replacement}\n\g<1>', config, count=1, - flags=re.MULTILINE | re.DOTALL) - - raise ConfigSectionNotFound(f"Config section {replacement} not found in config") - - -def replace_section(config, replacement, from_re, to_re=r'!', before_re=r'line vty'): - r"""Replace a section of FRR config - config: full original configuration - replacement: replacement configuration section - from_re: Regex for the start of section matching - example: 'router bgp \d+' - to_re: Regex for stop of section matching - default: '!' - example: '!' or 'end' - before_re: When from_re/to_re does not return a match, the config will - be added before this tag - default: ^line vty$ - - startline and endline tags will be automatically added to the resulting from_re/to_re and before_re regex'es - """ - # DEPRECATED, this is replaced by a new implementation - return _replace_section(config, replacement, replace_re=rf'^{from_re}$.*?^{to_re}$', before_re=rf'^({before_re})$') - - -def remove_section(config, from_re, to_re='!'): - # DEPRECATED, this is replaced by a new implementation - return _replace_section(config, '', replace_re=rf'^{from_re}$.*?^{to_re}$', before_re=None) - - -def _find_first_block(config, start_pattern, stop_pattern, start_at=0): - '''Find start and stop line numbers for a config block - config: (list) A list conaining the configuration that is searched - start_pattern: (raw-str) The pattern searched for a a start of block tag - stop_pattern: (raw-str) The pattern searched for to signify the end of the block - start_at: (int) The index to start searching at in the - - Returns: - None: No complete block could be found - set(int, int): A complete block found between the line numbers returned in the set - - The object is searched from the start for the regex until the first match is found. - On a successful match it continues the search for the regex until it is found. - After a successful run a set is returned containing the start and stop line numbers. - ''' - LOG.debug(f'_find_first_block: find start={repr(start_pattern)} stop={repr(stop_pattern)} start_at={start_at}') - _start = None - for i, element in enumerate(config[start_at:], start=start_at): - # LOG.debug(f'_find_first_block: running line {i:3} "{element}"') - if not _start: - if not re.match(start_pattern, element): - LOG.debug(f'_find_first_block: no match {i:3} "{element}"') - continue - _start = i - LOG.debug(f'_find_first_block: Found start {i:3} "{element}"') - continue - - if not re.match(stop_pattern, element): - LOG.debug(f'_find_first_block: no match {i:3} "{element}"') - continue - - LOG.debug(f'_find_first_block: Found stop {i:3} "{element}"') - return (_start, i) - - LOG.debug('_find_first_block: exit start={repr(start_pattern)} stop={repr(stop_pattern)} start_at={start_at}') - return None - - -def _find_first_element(config, pattern, start_at=0): - '''Find the first element that matches the current pattern in config - config: (list) A list containing the configuration that is searched - start_pattern: (raw-str) The pattern searched for - start_at: (int) The index to start searching at in the - - return: Line index of the line containing the searched pattern - - TODO: for now it returns -1 on a no-match because 0 also returns as False - TODO: that means that we can not use False matching to tell if its - ''' - LOG.debug(f'_find_first_element: find start="{pattern}" start_at={start_at}') - for i, element in enumerate(config[start_at:], start=0): - if re.match(pattern + '$', element): - LOG.debug(f'_find_first_element: Found stop {i:3} "{element}"') - return i - LOG.debug(f'_find_first_element: no match {i:3} "{element}"') - LOG.debug(f'_find_first_element: Did not find any match, exiting') - return -1 - - -def _find_elements(config, pattern, start_at=0): - '''Find all instances of pattern and return a list containing all element indexes - config: (list) A list containing the configuration that is searched - start_pattern: (raw-str) The pattern searched for - start_at: (int) The index to start searching at in the - - return: A list of line indexes containing the searched pattern - TODO: refactor this to return a generator instead - ''' - return [i for i, element in enumerate(config[start_at:], start=0) if re.match(pattern + '$', element)] - - -class FRRConfig: - '''Main FRR Configuration manipulation object - Using this object the user could load, manipulate and commit the configuration to FRR - ''' - def __init__(self, config=[]): - self.imported_config = '' - - if isinstance(config, list): - self.config = config.copy() - self.original_config = config.copy() - elif isinstance(config, str): - self.config = config.split('\n') - self.original_config = self.config.copy() - else: - raise ValueError( - 'The config element needs to be a string or list type object') - - if config: - LOG.debug(f'__init__: frr library initiated with initial config') - for i, e in enumerate(self.config): - LOG.debug(f'__init__: initial {i:3} {e}') - - def load_configuration(self, daemon=None): - '''Load the running configuration from FRR into the config object - daemon: str with name of the FRR Daemon to load configuration from or - None to load the consolidated config - - Using this overwrites the current loaded config objects and replaces the original loaded config - ''' - init_debugging() - - self.imported_config = get_configuration(daemon=daemon) - if daemon: - LOG.debug(f'load_configuration: Configuration loaded from FRR daemon {daemon}') - else: - LOG.debug(f'load_configuration: Configuration loaded from FRR integrated config') - - self.original_config = self.imported_config.split('\n') - self.config = self.original_config.copy() - - for i, e in enumerate(self.imported_config.split('\n')): - LOG.debug(f'load_configuration: loaded {i:3} {e}') - return - - def test_configuration(self): - '''Test the current configuration against FRR - This will exception if FRR failes to load the current configuration object - ''' - LOG.debug('test_configation: Testing configuration') - mark_configuration('\n'.join(self.config)) - - def commit_configuration(self, daemon=None): - ''' - Commit the current configuration to FRR daemon: str with name of the - FRR daemon to commit to or None to use the consolidated config. - - Configuration is automatically saved after apply - ''' - LOG.debug('commit_configuration: Commiting configuration') - for i, e in enumerate(self.config): - LOG.debug(f'commit_configuration: new_config {i:3} {e}') - - # https://github.com/FRRouting/frr/issues/10132 - # https://github.com/FRRouting/frr/issues/10133 - count = 0 - count_max = 5 - emsg = '' - while count < count_max: - count += 1 - try: - reload_configuration('\n'.join(self.config), daemon=daemon) - break - except ConfigError as e: - emsg = str(e) - except: - # we just need to re-try the commit of the configuration - # for the listed FRR issues above - pass - if count >= count_max: - if emsg: - raise ConfigError(emsg) - raise ConfigurationNotValid(f'Config commit retry counter ({count_max}) exceeded for {daemon} daemon!') - - # Save configuration to /run/frr/config/frr.conf - save_configuration() - - - def modify_section(self, start_pattern, replacement='!', stop_pattern=r'\S+', remove_stop_mark=False, count=0): - if isinstance(replacement, str): - replacement = replacement.split('\n') - elif not isinstance(replacement, list): - return ValueError("The replacement element needs to be a string or list type object") - LOG.debug(f'modify_section: starting search for {repr(start_pattern)} until {repr(stop_pattern)}') - - _count = 0 - _next_start = 0 - while True: - if count and count <= _count: - # Break out of the loop after specified amount of matches - LOG.debug(f'modify_section: reached limit ({_count}), exiting loop at line {_next_start}') - break - # While searching, always assume that the user wants to search for the exact pattern he entered - # To be more specific the user needs a override, eg. a "pattern.*" - _w = _find_first_block( - self.config, start_pattern+'$', stop_pattern, start_at=_next_start) - if not _w: - # Reached the end, no more elements to remove - LOG.debug(f'modify_section: No more config sections found, exiting') - break - start_element, end_element = _w - LOG.debug(f'modify_section: found match between {start_element} and {end_element}') - for i, e in enumerate(self.config[start_element:end_element+1 if remove_stop_mark else end_element], - start=start_element): - LOG.debug(f'modify_section: remove {i:3} {e}') - del self.config[start_element:end_element + - 1 if remove_stop_mark else end_element] - if replacement: - # Append the replacement config at the current position - for i, e in enumerate(replacement, start=start_element): - LOG.debug(f'modify_section: add {i:3} {e}') - self.config[start_element:start_element] = replacement - _count += 1 - _next_start = start_element + len(replacement) - - return _count - - def add_before(self, before_pattern, addition): - '''Add config block before this element in the configuration''' - if isinstance(addition, str): - addition = addition.split('\n') - elif not isinstance(addition, list): - return ValueError("The replacement element needs to be a string or list type object") - - start = _find_first_element(self.config, before_pattern) - if start < 0: - return False - for i, e in enumerate(addition, start=start): - LOG.debug(f'add_before: add {i:3} {e}') - self.config[start:start] = addition - return True - - def __str__(self): - return '\n'.join(self.config) - - def __repr__(self): - return f'frr({repr(str(self))})' diff --git a/python/vyos/frrender.py b/python/vyos/frrender.py index 7a0b661a30..ead893ff98 100644 --- a/python/vyos/frrender.py +++ b/python/vyos/frrender.py @@ -39,6 +39,7 @@ def debug(message): bgp_daemon = 'bgpd' isis_daemon = 'isisd' mgmt_daemon = 'mgmtd' +openfabric_daemon = 'fabricd' pim_daemon = 'pimd' zebra_daemon = 'zebra' diff --git a/smoketest/scripts/cli/test_interfaces_bonding.py b/smoketest/scripts/cli/test_interfaces_bonding.py index 4187447127..735e4f3c58 100755 --- a/smoketest/scripts/cli/test_interfaces_bonding.py +++ b/smoketest/scripts/cli/test_interfaces_bonding.py @@ -24,7 +24,7 @@ from vyos.configsession import ConfigSessionError from vyos.utils.network import get_interface_config from vyos.utils.file import read_file -from vyos.frr import mgmt_daemon +from vyos.frrender import mgmt_daemon class BondingInterfaceTest(BasicInterfaceTest.TestCase): @classmethod diff --git a/smoketest/scripts/cli/test_interfaces_ethernet.py b/smoketest/scripts/cli/test_interfaces_ethernet.py index 218fa0759c..c02ca613be 100755 --- a/smoketest/scripts/cli/test_interfaces_ethernet.py +++ b/smoketest/scripts/cli/test_interfaces_ethernet.py @@ -27,7 +27,7 @@ from base_interfaces_test import BasicInterfaceTest from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section -from vyos.frr import mgmt_daemon +from vyos.frrender import mgmt_daemon from vyos.utils.process import cmd from vyos.utils.process import popen from vyos.utils.file import read_file diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index 6eeac37a71..1b6c30dfed 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -25,7 +25,7 @@ from vyos.template import is_ipv6 from vyos.utils.process import process_named_running from vyos.utils.process import cmd -from vyos.frr import bgp_daemon +from vyos.frrender import bgp_daemon PROCESS_NAME = 'bgpd' ASN = '64512' diff --git a/smoketest/scripts/cli/test_protocols_isis.py b/smoketest/scripts/cli/test_protocols_isis.py index 5b86dd53af..2fddbfebaf 100755 --- a/smoketest/scripts/cli/test_protocols_isis.py +++ b/smoketest/scripts/cli/test_protocols_isis.py @@ -20,7 +20,7 @@ from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section from vyos.utils.process import process_named_running -from vyos.frr import isis_daemon +from vyos.frrender import isis_daemon PROCESS_NAME = 'isisd' base_path = ['protocols', 'isis'] diff --git a/smoketest/scripts/cli/test_protocols_openfabric.py b/smoketest/scripts/cli/test_protocols_openfabric.py index 889cba1358..3e99656ecd 100644 --- a/smoketest/scripts/cli/test_protocols_openfabric.py +++ b/smoketest/scripts/cli/test_protocols_openfabric.py @@ -19,7 +19,7 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError from vyos.utils.process import process_named_running -from vyos.frr import openfabric_daemon +from vyos.frrender import openfabric_daemon PROCESS_NAME = 'fabricd' base_path = ['protocols', 'openfabric'] diff --git a/smoketest/scripts/cli/test_protocols_segment-routing.py b/smoketest/scripts/cli/test_protocols_segment-routing.py index eb563db930..8905d5e452 100755 --- a/smoketest/scripts/cli/test_protocols_segment-routing.py +++ b/smoketest/scripts/cli/test_protocols_segment-routing.py @@ -20,7 +20,7 @@ from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section -from vyos.frr import zebra_daemon +from vyos.frrender import zebra_daemon from vyos.utils.process import process_named_running from vyos.utils.system import sysctl_read diff --git a/smoketest/scripts/cli/test_system_ipv6.py b/smoketest/scripts/cli/test_system_ipv6.py index be9751c4d5..ebf6202047 100755 --- a/smoketest/scripts/cli/test_system_ipv6.py +++ b/smoketest/scripts/cli/test_system_ipv6.py @@ -152,4 +152,4 @@ def test_system_ipv6_nht(self): self.assertNotIn(f'no ipv6 nht resolve-via-default', frrconfig) if __name__ == '__main__': - unittest.main(verbosity=2, failfast=True) + unittest.main(verbosity=2) From a8da54f50df5d60b18a8cdf0ea63c71f82faf14e Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Thu, 12 Dec 2024 16:12:43 +0100 Subject: [PATCH 22/28] rpki: T6746: FRRender needs to calculate SSH key path --- python/vyos/configdict.py | 5 +++++ src/conf_mode/protocols_rpki.py | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index fb63650602..cbcbf9f721 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -944,6 +944,11 @@ def dict_helper_pim_defaults(pim, path): rpki = conf.get_config_dict(rpki_cli_path, key_mangling=('-', '_'), get_first_key=True, with_pki=True, with_recursive_defaults=True) + rpki_ssh_key_base = '/run/frr/id_rpki' + for cache, cache_config in rpki.get('cache',{}).items(): + if 'ssh' in cache_config: + cache_config['ssh']['public_key_file'] = f'{rpki_ssh_key_base}_{cache}.pub' + cache_config['ssh']['private_key_file'] = f'{rpki_ssh_key_base}_{cache}' dict.update({'rpki' : rpki}) elif conf.exists_effective(rpki_cli_path): dict.update({'rpki' : {'deleted' : ''}}) diff --git a/src/conf_mode/protocols_rpki.py b/src/conf_mode/protocols_rpki.py index d3f515febd..4aefbe36c2 100755 --- a/src/conf_mode/protocols_rpki.py +++ b/src/conf_mode/protocols_rpki.py @@ -26,6 +26,7 @@ from vyos.pki import wrap_openssh_public_key from vyos.pki import wrap_openssh_private_key from vyos.utils.dict import dict_search_args +from vyos.utils.process import is_systemd_service_running from vyos.utils.file import write_file from vyos import ConfigError from vyos import airbag @@ -94,12 +95,12 @@ def generate(config_dict): write_file(cache_config['ssh']['public_key_file'], wrap_openssh_public_key(public_key_data, public_key_type)) write_file(cache_config['ssh']['private_key_file'], wrap_openssh_private_key(private_key_data)) - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().generate(config_dict) return None def apply(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().apply() return None From d58e7ace7075e24c64cfe5e56ffcaad1688446e9 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Thu, 12 Dec 2024 20:57:29 +0100 Subject: [PATCH 23/28] multicast: T6746: migrate CLI to to mimic unicast IPv4 routes syntax Consolidate "multicast interface-route" and "multicast route" under common "mroute " CLI node. --- smoketest/config-tests/static-route-basic | 2 ++ smoketest/configs/static-route-basic | 4 ++-- src/migration-scripts/quagga/11-to-12 | 21 +++++++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/smoketest/config-tests/static-route-basic b/smoketest/config-tests/static-route-basic index 4416e6b197..d2d33d0433 100644 --- a/smoketest/config-tests/static-route-basic +++ b/smoketest/config-tests/static-route-basic @@ -3,6 +3,8 @@ set interfaces ethernet eth0 speed 'auto' set interfaces ethernet eth0 vif 203 address '172.18.203.10/24' set interfaces ethernet eth1 duplex 'auto' set interfaces ethernet eth1 speed 'auto' +set protocols static mroute 224.1.0.0/24 interface eth0.203 distance '10' +set protocols static mroute 224.2.0.0/24 next-hop 172.18.203.254 distance '20' set protocols static route 10.0.0.0/8 blackhole distance '200' set protocols static route 10.0.0.0/8 blackhole tag '333' set protocols static route 10.0.0.0/8 next-hop 192.0.2.140 bfd multi-hop source-address '192.0.2.10' diff --git a/smoketest/configs/static-route-basic b/smoketest/configs/static-route-basic index 4bf114e335..648e19676f 100644 --- a/smoketest/configs/static-route-basic +++ b/smoketest/configs/static-route-basic @@ -14,12 +14,12 @@ interfaces { protocols { static { multicast { - interface-route 224.0.0.0/24 { + interface-route 224.1.0.0/24 { next-hop-interface eth0.203 { distance "10" } } - route 224.0.0.0/24 { + route 224.2.0.0/24 { next-hop 172.18.203.254 { distance "20" } diff --git a/src/migration-scripts/quagga/11-to-12 b/src/migration-scripts/quagga/11-to-12 index becc441623..8ae2023a17 100644 --- a/src/migration-scripts/quagga/11-to-12 +++ b/src/migration-scripts/quagga/11-to-12 @@ -23,6 +23,7 @@ static_base = ['protocols', 'static'] def migrate(config: ConfigTree) -> None: # Check for static route/route6 configuration + # Migrate static BFD configuration to match FRR possibillities for route_route6 in ['route', 'route6']: route_route6_base = static_base + [route_route6] if not config.exists(route_route6_base): @@ -52,3 +53,23 @@ def migrate(config: ConfigTree) -> None: config.delete(multi_hop_base) config.set(multi_hop_base + ['source-address'], value=source) config.set(next_hop_base + [next_hop, 'bfd', 'profile'], value=profile) + + # Consolidate static multicast routing configuration under a new node + if config.exists(static_base + ['multicast']): + for mroute in ['interface-route', 'route']: + mroute_base = static_base + ['multicast', mroute] + if not config.exists(mroute_base): + continue + config.set(static_base + ['mroute']) + config.set_tag(static_base + ['mroute']) + for route in config.list_nodes(mroute_base): + config.copy(mroute_base + [route], static_base + ['mroute', route]) + + mroute_base = static_base + ['mroute'] + if config.exists(mroute_base): + for mroute in config.list_nodes(mroute_base): + interface_path = mroute_base + [mroute, 'next-hop-interface'] + if config.exists(interface_path): + config.rename(interface_path, 'interface') + + config.delete(static_base + ['multicast']) From 176f974d1c50aae44ec985467aa37f01ca6e0169 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Thu, 12 Dec 2024 21:07:10 +0100 Subject: [PATCH 24/28] smoketest: T6746: add 2 second guard timer for getFRRconfig() Sometimes FRR needs some time after reloading the configuration to appear in vtysh. This is a workaround addiung a 2 second guard timer. --- smoketest/scripts/cli/base_vyostest_shim.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/smoketest/scripts/cli/base_vyostest_shim.py b/smoketest/scripts/cli/base_vyostest_shim.py index a383e596cb..affa538774 100644 --- a/smoketest/scripts/cli/base_vyostest_shim.py +++ b/smoketest/scripts/cli/base_vyostest_shim.py @@ -103,6 +103,9 @@ def op_mode(self, path : list) -> None: def getFRRconfig(self, string=None, end='$', endsection='^!', daemon=''): """ Retrieve current "running configuration" from FRR """ + # Sometimes FRR needs some time after reloading the configuration to + # appear in vtysh. This is a workaround addiung a 2 seconds guard timer + sleep(2) command = f'vtysh -c "show run {daemon} no-header"' if string: command += f' | sed -n "/^{string}{end}/,/{endsection}/p"' out = cmd(command) From 7d99257902c2d638dbf9a8a095660d6aa0d92e38 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Thu, 12 Dec 2024 21:25:42 +0100 Subject: [PATCH 25/28] frr: T6746: do not use FRRender apply() method when vyos-configd is running --- src/conf_mode/interfaces_bonding.py | 8 +++++--- src/conf_mode/interfaces_ethernet.py | 7 +++---- src/conf_mode/policy.py | 5 +++-- src/conf_mode/protocols_babel.py | 5 +++-- src/conf_mode/protocols_bfd.py | 5 +++-- src/conf_mode/protocols_bgp.py | 5 +++-- src/conf_mode/protocols_eigrp.py | 5 +++-- src/conf_mode/protocols_isis.py | 5 +++-- src/conf_mode/protocols_mpls.py | 5 +++-- src/conf_mode/protocols_openfabric.py | 5 +++-- src/conf_mode/protocols_ospf.py | 5 +++-- src/conf_mode/protocols_ospfv3.py | 5 +++-- src/conf_mode/protocols_pim.py | 5 +++-- src/conf_mode/protocols_pim6.py | 5 +++-- src/conf_mode/protocols_rip.py | 5 +++-- src/conf_mode/protocols_ripng.py | 5 +++-- src/conf_mode/protocols_rpki.py | 2 +- src/conf_mode/protocols_segment-routing.py | 5 +++-- src/conf_mode/protocols_static.py | 5 +++-- src/conf_mode/system_ip.py | 5 +++-- src/conf_mode/system_ipv6.py | 5 +++-- src/conf_mode/vrf.py | 5 +++-- 22 files changed, 66 insertions(+), 46 deletions(-) diff --git a/src/conf_mode/interfaces_bonding.py b/src/conf_mode/interfaces_bonding.py index 5f839b33ce..0844d29136 100755 --- a/src/conf_mode/interfaces_bonding.py +++ b/src/conf_mode/interfaces_bonding.py @@ -39,9 +39,11 @@ from vyos.utils.dict import dict_search from vyos.utils.dict import dict_to_paths_values from vyos.utils.network import interface_exists +from vyos.utils.process import is_systemd_service_running from vyos.configdict import has_address_configured from vyos.configdict import has_vrf_configured -from vyos.configdep import set_dependents, call_dependents +from vyos.configdep import set_dependents +from vyos.configdep import call_dependents from vyos import ConfigError from vyos import airbag airbag.enable() @@ -263,12 +265,12 @@ def verify(bond): return None def generate(bond): - if 'frr_dict' in bond and 'frrender_cls' not in bond['frr_dict']: + if 'frr_dict' in bond and not is_systemd_service_running('vyos-configd.service'): FRRender().generate(bond['frr_dict']) return None def apply(bond): - if 'frr_dict' in bond and 'frrender_cls' not in bond['frr_dict']: + if 'frr_dict' in bond and not is_systemd_service_running('vyos-configd.service'): FRRender().apply() b = BondIf(bond['ifname']) diff --git a/src/conf_mode/interfaces_ethernet.py b/src/conf_mode/interfaces_ethernet.py index accfb6b8e0..5024e6982d 100755 --- a/src/conf_mode/interfaces_ethernet.py +++ b/src/conf_mode/interfaces_ethernet.py @@ -41,6 +41,7 @@ from vyos.utils.dict import dict_to_paths_values from vyos.utils.dict import dict_set from vyos.utils.dict import dict_delete +from vyos.utils.process import is_systemd_service_running from vyos import ConfigError from vyos import airbag airbag.enable() @@ -321,14 +322,13 @@ def verify_ethernet(ethernet): return None def generate(ethernet): - if 'frr_dict' in ethernet and 'frrender_cls' not in ethernet['frr_dict']: + if 'frr_dict' in ethernet and not is_systemd_service_running('vyos-configd.service'): FRRender().generate(ethernet['frr_dict']) return None def apply(ethernet): - if 'frr_dict' in ethernet and 'frrender_cls' not in ethernet['frr_dict']: + if 'frr_dict' in ethernet and not is_systemd_service_running('vyos-configd.service'): FRRender().apply() - e = EthernetIf(ethernet['ifname']) if 'deleted' in ethernet: e.remove() @@ -341,7 +341,6 @@ def apply(ethernet): c = get_config() verify(c) generate(c) - apply(c) except ConfigError as e: print(e) diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py index 4abb150ace..5e71a612d5 100755 --- a/src/conf_mode/policy.py +++ b/src/conf_mode/policy.py @@ -22,6 +22,7 @@ from vyos.frrender import FRRender from vyos.frrender import frr_protocols from vyos.utils.dict import dict_search +from vyos.utils.process import is_systemd_service_running from vyos import ConfigError from vyos import airbag airbag.enable() @@ -262,12 +263,12 @@ def verify(config_dict): def generate(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().generate(config_dict) return None def apply(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().apply() return None diff --git a/src/conf_mode/protocols_babel.py b/src/conf_mode/protocols_babel.py index af0751e027..48b7ae734e 100755 --- a/src/conf_mode/protocols_babel.py +++ b/src/conf_mode/protocols_babel.py @@ -23,6 +23,7 @@ from vyos.configverify import verify_prefix_list from vyos.frrender import FRRender from vyos.utils.dict import dict_search +from vyos.utils.process import is_systemd_service_running from vyos import ConfigError from vyos import airbag airbag.enable() @@ -89,12 +90,12 @@ def verify(config_dict): def generate(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().generate(config_dict) return None def apply(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().apply() return None diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index 623801897e..2e7d406764 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -21,6 +21,7 @@ from vyos.frrender import FRRender from vyos.template import is_ipv6 from vyos.utils.network import is_ipv6_link_local +from vyos.utils.process import is_systemd_service_running from vyos import ConfigError from vyos import airbag airbag.enable() @@ -77,11 +78,11 @@ def verify(config_dict): return None def generate(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().generate(config_dict) def apply(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().apply() return None diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index db3123bd34..ae32dd8393 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -30,6 +30,7 @@ from vyos.utils.dict import dict_search from vyos.utils.network import get_interface_vrf from vyos.utils.network import is_addr_assigned +from vyos.utils.process import is_systemd_service_running from vyos.utils.process import process_named_running from vyos import ConfigError from vyos import airbag @@ -554,12 +555,12 @@ def verify(config_dict): return None def generate(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().generate(config_dict) return None def apply(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().apply() return None diff --git a/src/conf_mode/protocols_eigrp.py b/src/conf_mode/protocols_eigrp.py index 33812d3503..8f49bb151f 100755 --- a/src/conf_mode/protocols_eigrp.py +++ b/src/conf_mode/protocols_eigrp.py @@ -21,6 +21,7 @@ from vyos.configdict import get_frrender_dict from vyos.configverify import has_frr_protocol_in_dict from vyos.configverify import verify_vrf +from vyos.utils.process import is_systemd_service_running from vyos.frrender import FRRender from vyos import ConfigError from vyos import airbag @@ -53,12 +54,12 @@ def verify(config_dict): verify_vrf({'vrf': vrf}) def generate(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().generate(config_dict) return None def apply(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().apply() return None diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py index 7a54685bb4..1e5f0d6e88 100755 --- a/src/conf_mode/protocols_isis.py +++ b/src/conf_mode/protocols_isis.py @@ -26,6 +26,7 @@ from vyos.ifconfig import Interface from vyos.utils.dict import dict_search from vyos.utils.network import get_interface_config +from vyos.utils.process import is_systemd_service_running from vyos import ConfigError from vyos import airbag airbag.enable() @@ -232,12 +233,12 @@ def verify(config_dict): return None def generate(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().generate(config_dict) return None def apply(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().apply() return None diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py index bab9648c49..e8097b7ff5 100755 --- a/src/conf_mode/protocols_mpls.py +++ b/src/conf_mode/protocols_mpls.py @@ -25,6 +25,7 @@ from vyos.frrender import FRRender from vyos.utils.dict import dict_search from vyos.utils.file import read_file +from vyos.utils.process import is_systemd_service_running from vyos.utils.system import sysctl_write from vyos.configverify import verify_interface_exists from vyos import ConfigError @@ -67,12 +68,12 @@ def verify(config_dict): return None def generate(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().generate(config_dict) return None def apply(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().apply() if not has_frr_protocol_in_dict(config_dict, 'mpls'): diff --git a/src/conf_mode/protocols_openfabric.py b/src/conf_mode/protocols_openfabric.py index 48c89d742b..41c5d95449 100644 --- a/src/conf_mode/protocols_openfabric.py +++ b/src/conf_mode/protocols_openfabric.py @@ -21,6 +21,7 @@ from vyos.configdict import get_frrender_dict from vyos.configverify import verify_interface_exists from vyos.configverify import has_frr_protocol_in_dict +from vyos.utils.process import is_systemd_service_running from vyos.frrender import FRRender from vyos import ConfigError from vyos import airbag @@ -89,12 +90,12 @@ def verify(config_dict): return None def generate(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().generate(config_dict) return None def apply(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().apply() return None diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py index 9d35aa007d..f2c95a63c3 100755 --- a/src/conf_mode/protocols_ospf.py +++ b/src/conf_mode/protocols_ospf.py @@ -27,6 +27,7 @@ from vyos.frrender import FRRender from vyos.utils.dict import dict_search from vyos.utils.network import get_interface_config +from vyos.utils.process import is_systemd_service_running from vyos import ConfigError from vyos import airbag airbag.enable() @@ -176,12 +177,12 @@ def verify(config_dict): return None def generate(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().generate(config_dict) return None def apply(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().apply() return None diff --git a/src/conf_mode/protocols_ospfv3.py b/src/conf_mode/protocols_ospfv3.py index c6b042b54a..ac189c3785 100755 --- a/src/conf_mode/protocols_ospfv3.py +++ b/src/conf_mode/protocols_ospfv3.py @@ -27,6 +27,7 @@ from vyos.ifconfig import Interface from vyos.utils.dict import dict_search from vyos.utils.network import get_interface_config +from vyos.utils.process import is_systemd_service_running from vyos import ConfigError from vyos import airbag airbag.enable() @@ -87,12 +88,12 @@ def verify(config_dict): return None def generate(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().generate(config_dict) return None def apply(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().apply() return None diff --git a/src/conf_mode/protocols_pim.py b/src/conf_mode/protocols_pim.py index c40b9d86a1..477895b0bb 100755 --- a/src/conf_mode/protocols_pim.py +++ b/src/conf_mode/protocols_pim.py @@ -27,6 +27,7 @@ from vyos.configverify import has_frr_protocol_in_dict from vyos.frrender import FRRender from vyos.frrender import pim_daemon +from vyos.utils.process import is_systemd_service_running from vyos.utils.process import process_named_running from vyos.utils.process import call from vyos import ConfigError @@ -86,7 +87,7 @@ def verify(config_dict): unique.append(gr_addr) def generate(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().generate(config_dict) return None @@ -103,7 +104,7 @@ def apply(config_dict): if not pim_pid: call('/usr/lib/frr/pimd -d -F traditional --daemon -A 127.0.0.1') - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().apply() return None diff --git a/src/conf_mode/protocols_pim6.py b/src/conf_mode/protocols_pim6.py index ff8fa38c3c..3a9b876cc1 100755 --- a/src/conf_mode/protocols_pim6.py +++ b/src/conf_mode/protocols_pim6.py @@ -22,6 +22,7 @@ from vyos.configdict import get_frrender_dict from vyos.configverify import has_frr_protocol_in_dict from vyos.configverify import verify_interface_exists +from vyos.utils.process import is_systemd_service_running from vyos.frrender import FRRender from vyos import ConfigError from vyos import airbag @@ -75,12 +76,12 @@ def verify(config_dict): unique.append(gr_addr) def generate(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().generate(config_dict) return None def apply(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().apply() return None diff --git a/src/conf_mode/protocols_rip.py b/src/conf_mode/protocols_rip.py index 38108c9f51..39743f9654 100755 --- a/src/conf_mode/protocols_rip.py +++ b/src/conf_mode/protocols_rip.py @@ -24,6 +24,7 @@ from vyos.configverify import verify_prefix_list from vyos.frrender import FRRender from vyos.utils.dict import dict_search +from vyos.utils.process import is_systemd_service_running from vyos import ConfigError from vyos import airbag airbag.enable() @@ -68,12 +69,12 @@ def verify(config_dict): f'with "split-horizon disable" for "{interface}"!') def generate(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().generate(config_dict) return None def apply(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().apply() return None diff --git a/src/conf_mode/protocols_ripng.py b/src/conf_mode/protocols_ripng.py index 46f3ce0144..14f0384441 100755 --- a/src/conf_mode/protocols_ripng.py +++ b/src/conf_mode/protocols_ripng.py @@ -24,6 +24,7 @@ from vyos.configverify import verify_prefix_list from vyos.frrender import FRRender from vyos.utils.dict import dict_search +from vyos.utils.process import is_systemd_service_running from vyos import ConfigError from vyos import airbag airbag.enable() @@ -68,12 +69,12 @@ def verify(config_dict): f'with "split-horizon disable" for "{interface}"!') def generate(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().generate(config_dict) return None def apply(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().apply() return None diff --git a/src/conf_mode/protocols_rpki.py b/src/conf_mode/protocols_rpki.py index 4aefbe36c2..5ad656586e 100755 --- a/src/conf_mode/protocols_rpki.py +++ b/src/conf_mode/protocols_rpki.py @@ -26,8 +26,8 @@ from vyos.pki import wrap_openssh_public_key from vyos.pki import wrap_openssh_private_key from vyos.utils.dict import dict_search_args -from vyos.utils.process import is_systemd_service_running from vyos.utils.file import write_file +from vyos.utils.process import is_systemd_service_running from vyos import ConfigError from vyos import airbag airbag.enable() diff --git a/src/conf_mode/protocols_segment-routing.py b/src/conf_mode/protocols_segment-routing.py index cc42e5ac78..99cf87556f 100755 --- a/src/conf_mode/protocols_segment-routing.py +++ b/src/conf_mode/protocols_segment-routing.py @@ -23,6 +23,7 @@ from vyos.frrender import FRRender from vyos.ifconfig import Section from vyos.utils.dict import dict_search +from vyos.utils.process import is_systemd_service_running from vyos.utils.system import sysctl_write from vyos import ConfigError from vyos import airbag @@ -54,7 +55,7 @@ def verify(config_dict): return None def generate(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().generate(config_dict) return None @@ -88,7 +89,7 @@ def apply(config_dict): else: sysctl_write(f'net.ipv6.conf.{interface}.seg6_enabled', '0') - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().apply() return None diff --git a/src/conf_mode/protocols_static.py b/src/conf_mode/protocols_static.py index 29fa530f42..9d02db6dd7 100755 --- a/src/conf_mode/protocols_static.py +++ b/src/conf_mode/protocols_static.py @@ -24,6 +24,7 @@ from vyos.configverify import verify_common_route_maps from vyos.configverify import verify_vrf from vyos.frrender import FRRender +from vyos.utils.process import is_systemd_service_running from vyos.template import render from vyos import ConfigError from vyos import airbag @@ -94,12 +95,12 @@ def generate(config_dict): # Put routing table names in /etc/iproute2/rt_tables render(config_file, 'iproute2/static.conf.j2', static) - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().generate(config_dict) return None def apply(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().apply() return None diff --git a/src/conf_mode/system_ip.py b/src/conf_mode/system_ip.py index 374e6e611e..86843eb783 100755 --- a/src/conf_mode/system_ip.py +++ b/src/conf_mode/system_ip.py @@ -25,6 +25,7 @@ from vyos.frrender import FRRender from vyos.utils.dict import dict_search from vyos.utils.process import is_systemd_service_active +from vyos.utils.process import is_systemd_service_running from vyos.utils.system import sysctl_write from vyos import ConfigError from vyos import airbag @@ -55,7 +56,7 @@ def verify(config_dict): return def generate(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().generate(config_dict) return None @@ -109,7 +110,7 @@ def apply(config_dict): # running when this script is called first. Skip this part and wait for initial # commit of the configuration to trigger this statement if is_systemd_service_active('frr.service'): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().apply() call_dependents() diff --git a/src/conf_mode/system_ipv6.py b/src/conf_mode/system_ipv6.py index 02c9a8201a..593b8f7f37 100755 --- a/src/conf_mode/system_ipv6.py +++ b/src/conf_mode/system_ipv6.py @@ -27,6 +27,7 @@ from vyos.utils.dict import dict_search from vyos.utils.file import write_file from vyos.utils.process import is_systemd_service_active +from vyos.utils.process import is_systemd_service_running from vyos.utils.system import sysctl_write from vyos import ConfigError from vyos import airbag @@ -57,7 +58,7 @@ def verify(config_dict): return def generate(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().generate(config_dict) return None @@ -93,7 +94,7 @@ def apply(config_dict): # running when this script is called first. Skip this part and wait for initial # commit of the configuration to trigger this statement if is_systemd_service_active('frr.service'): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().apply() call_dependents() diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index 1b19c55d27..6533f493fc 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -32,6 +32,7 @@ from vyos.utils.network import interface_exists from vyos.utils.process import call from vyos.utils.process import cmd +from vyos.utils.process import is_systemd_service_running from vyos.utils.process import popen from vyos.utils.system import sysctl_write from vyos import ConfigError @@ -198,7 +199,7 @@ def generate(vrf): # Render iproute2 VR helper names render(config_file, 'iproute2/vrf.conf.j2', vrf) - if 'frr_dict' in vrf and 'frrender_cls' not in vrf['frr_dict']: + if 'frr_dict' in vrf and not is_systemd_service_running('vyos-configd.service'): FRRender().generate(vrf['frr_dict']) return None @@ -341,7 +342,7 @@ def apply(vrf): if has_rule(afi, 2000, 'l3mdev'): call(f'ip {afi} rule del pref 2000 l3mdev unreachable') - if 'frr_dict' in vrf and 'frrender_cls' not in vrf['frr_dict']: + if 'frr_dict' in vrf and not is_systemd_service_running('vyos-configd.service'): FRRender().apply() return None From e1a4ff9b93070bd772c0b53ae01a87b84d27bda6 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Fri, 13 Dec 2024 13:56:12 +0100 Subject: [PATCH 26/28] frr: T6746: reference common daemon definition in vyos.frrender Do not use custom daemon definitions like bgpd - re-use them from e.g. vyos.frrender.bgp_daemon --- python/vyos/frrender.py | 8 +++ smoketest/scripts/cli/test_protocols_babel.py | 16 ++--- smoketest/scripts/cli/test_protocols_bfd.py | 12 ++-- smoketest/scripts/cli/test_protocols_bgp.py | 34 ++++++----- smoketest/scripts/cli/test_protocols_isis.py | 5 +- smoketest/scripts/cli/test_protocols_mpls.py | 10 ++-- .../scripts/cli/test_protocols_openfabric.py | 5 +- smoketest/scripts/cli/test_protocols_ospf.py | 60 +++++++++---------- .../scripts/cli/test_protocols_ospfv3.py | 40 ++++++------- smoketest/scripts/cli/test_protocols_pim.py | 15 +++-- smoketest/scripts/cli/test_protocols_pim6.py | 18 +++--- smoketest/scripts/cli/test_protocols_rip.py | 6 +- smoketest/scripts/cli/test_protocols_ripng.py | 6 +- smoketest/scripts/cli/test_protocols_rpki.py | 7 +-- .../cli/test_protocols_segment-routing.py | 5 +- 15 files changed, 126 insertions(+), 121 deletions(-) diff --git a/python/vyos/frrender.py b/python/vyos/frrender.py index ead893ff98..95d6c7243b 100644 --- a/python/vyos/frrender.py +++ b/python/vyos/frrender.py @@ -36,11 +36,19 @@ def debug(message): 'openfabric', 'ospf', 'ospfv3', 'pim', 'pim6', 'rip', 'ripng', 'rpki', 'segment_routing', 'static'] +babel_daemon = 'babeld' +bfd_daemon = 'bfdd' bgp_daemon = 'bgpd' isis_daemon = 'isisd' +ldpd_daemon = 'ldpd' mgmt_daemon = 'mgmtd' openfabric_daemon = 'fabricd' +ospf_daemon = 'ospfd' +ospf6_daemon = 'ospf6d' pim_daemon = 'pimd' +pim6_daemon = 'pim6d' +rip_daemon = 'ripd' +ripng_daemon = 'ripngd' zebra_daemon = 'zebra' class FRRender: diff --git a/smoketest/scripts/cli/test_protocols_babel.py b/smoketest/scripts/cli/test_protocols_babel.py index 606c1efd33..7a79c7b9e5 100755 --- a/smoketest/scripts/cli/test_protocols_babel.py +++ b/smoketest/scripts/cli/test_protocols_babel.py @@ -19,10 +19,10 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.ifconfig import Section +from vyos.frrender import babel_daemon from vyos.utils.process import process_named_running from vyos.xml_ref import default_value -PROCESS_NAME = 'babeld' base_path = ['protocols', 'babel'] class TestProtocolsBABEL(VyOSUnitTestSHIM.TestCase): @@ -32,7 +32,7 @@ def setUpClass(cls): # call base-classes classmethod super(TestProtocolsBABEL, cls).setUpClass() # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same - cls.daemon_pid = process_named_running(PROCESS_NAME) + cls.daemon_pid = process_named_running(babel_daemon) # ensure we can also run this test on a live system - so lets clean # out the current configuration :) cls.cli_delete(cls, base_path) @@ -48,7 +48,7 @@ def tearDown(self): self.cli_commit() # check process health and continuity - self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) + self.assertEqual(self.daemon_pid, process_named_running(babel_daemon)) def test_babel_interfaces(self): def_update_interval = default_value(base_path + ['interface', 'eth0', 'update-interval']) @@ -77,11 +77,11 @@ def test_babel_interfaces(self): self.cli_commit() - frrconfig = self.getFRRconfig('router babel', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router babel', endsection='^exit', daemon=babel_daemon) for interface in self._interfaces: self.assertIn(f' network {interface}', frrconfig) - iface_config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + iface_config = self.getFRRconfig(f'interface {interface}', endsection='^exit', daemon=babel_daemon) self.assertIn(f' babel channel {channel}', iface_config) self.assertIn(f' babel enable-timestamps', iface_config) self.assertIn(f' babel update-interval {def_update_interval}', iface_config) @@ -105,7 +105,7 @@ def test_babel_redistribute(self): self.cli_commit() - frrconfig = self.getFRRconfig('router babel', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router babel', endsection='^exit', daemon=babel_daemon) for protocol in ipv4_protos: self.assertIn(f' redistribute ipv4 {protocol}', frrconfig) for protocol in ipv6_protos: @@ -125,7 +125,7 @@ def test_babel_basic(self): self.cli_commit() - frrconfig = self.getFRRconfig('router babel', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router babel', endsection='^exit', daemon=babel_daemon) self.assertIn(f' babel diversity', frrconfig) self.assertIn(f' babel diversity-factor {diversity_factor}', frrconfig) self.assertIn(f' babel resend-delay {resend_delay}', frrconfig) @@ -192,7 +192,7 @@ def test_babel_distribute_list(self): self.cli_commit() - frrconfig = self.getFRRconfig('router babel', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router babel', endsection='^exit', daemon=babel_daemon) self.assertIn(f' distribute-list {access_list_in4} in', frrconfig) self.assertIn(f' distribute-list {access_list_out4} out', frrconfig) self.assertIn(f' ipv6 distribute-list {access_list_in6} in', frrconfig) diff --git a/smoketest/scripts/cli/test_protocols_bfd.py b/smoketest/scripts/cli/test_protocols_bfd.py index 9f178c821a..32e39e8f7e 100755 --- a/smoketest/scripts/cli/test_protocols_bfd.py +++ b/smoketest/scripts/cli/test_protocols_bfd.py @@ -18,9 +18,9 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError +from vyos.frrender import bfd_daemon from vyos.utils.process import process_named_running -PROCESS_NAME = 'bfdd' base_path = ['protocols', 'bfd'] frr_endsection = '^ exit' @@ -85,7 +85,7 @@ def setUpClass(cls): super(TestProtocolsBFD, cls).setUpClass() # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same - cls.daemon_pid = process_named_running(PROCESS_NAME) + cls.daemon_pid = process_named_running(bfd_daemon) # ensure we can also run this test on a live system - so lets clean # out the current configuration :) @@ -96,7 +96,7 @@ def tearDown(self): self.cli_commit() # check process health and continuity - self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) + self.assertEqual(self.daemon_pid, process_named_running(bfd_daemon)) def test_bfd_peer(self): self.cli_set(['vrf', 'name', vrf_name, 'table', '1000']) @@ -131,7 +131,7 @@ def test_bfd_peer(self): self.cli_commit() # Verify FRR bgpd configuration - frrconfig = self.getFRRconfig('bfd', endsection='^exit', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('bfd', endsection='^exit', daemon=bfd_daemon) for peer, peer_config in peers.items(): tmp = f'peer {peer}' if 'multihop' in peer_config: @@ -145,7 +145,7 @@ def test_bfd_peer(self): self.assertIn(tmp, frrconfig) peerconfig = self.getFRRconfig(f' peer {peer}', end='', endsection=frr_endsection, - daemon=PROCESS_NAME) + daemon=bfd_daemon) if 'echo_mode' in peer_config: self.assertIn(f'echo-mode', peerconfig) if 'intv_echo' in peer_config: @@ -230,7 +230,7 @@ def test_bfd_profile(self): for peer, peer_config in peers.items(): peerconfig = self.getFRRconfig(f' peer {peer}', end='', - endsection=frr_endsection, daemon=PROCESS_NAME) + endsection=frr_endsection, daemon=bfd_daemon) if 'profile' in peer_config: self.assertIn(f' profile {peer_config["profile"]}', peerconfig) diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index 1b6c30dfed..cdf18a051b 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -27,7 +27,6 @@ from vyos.utils.process import cmd from vyos.frrender import bgp_daemon -PROCESS_NAME = 'bgpd' ASN = '64512' base_path = ['protocols', 'bgp'] @@ -179,7 +178,7 @@ def setUpClass(cls): super(TestProtocolsBGP, cls).setUpClass() # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same - cls.daemon_pid = process_named_running(PROCESS_NAME) + cls.daemon_pid = process_named_running(bgp_daemon) # ensure we can also run this test on a live system - so lets clean # out the current configuration :) @@ -218,11 +217,11 @@ def tearDown(self): self.cli_delete(base_path) self.cli_commit() - frrconfig = self.getFRRconfig('router bgp') + frrconfig = self.getFRRconfig('router bgp', endsection='^exit', daemon=bgp_daemon) self.assertNotIn(f'router bgp', frrconfig) # check process health and continuity - self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) + self.assertEqual(self.daemon_pid, process_named_running(bgp_daemon)) def create_bgp_instances_for_import_test(self): table = '1000' @@ -373,7 +372,7 @@ def test_bgp_01_simple(self): self.cli_commit() # Verify FRR bgpd configuration - frrconfig = self.getFRRconfig(f'router bgp {ASN}') + frrconfig = self.getFRRconfig(f'router bgp {ASN}', daemon=bgp_daemon) self.assertIn(f'router bgp {ASN}', frrconfig) self.assertIn(f' bgp router-id {router_id}', frrconfig) self.assertIn(f' bgp allow-martian-nexthop', frrconfig) @@ -399,15 +398,18 @@ def test_bgp_01_simple(self): self.assertNotIn(f'bgp ebgp-requires-policy', frrconfig) self.assertIn(f' no bgp suppress-duplicates', frrconfig) - afiv4_config = self.getFRRconfig(' address-family ipv4 unicast') + afiv4_config = self.getFRRconfig(' address-family ipv4 unicast', + endsection='^ exit-address-family', daemon=bgp_daemon) self.assertIn(f' maximum-paths {max_path_v4}', afiv4_config) self.assertIn(f' maximum-paths ibgp {max_path_v4ibgp}', afiv4_config) - afiv4_config = self.getFRRconfig(' address-family ipv4 labeled-unicast') + afiv4_config = self.getFRRconfig(' address-family ipv4 labeled-unicast', + endsection='^ exit-address-family', daemon=bgp_daemon) self.assertIn(f' maximum-paths {max_path_v4}', afiv4_config) self.assertIn(f' maximum-paths ibgp {max_path_v4ibgp}', afiv4_config) - afiv6_config = self.getFRRconfig(' address-family ipv6 unicast') + afiv6_config = self.getFRRconfig(' address-family ipv6 unicast', + endsection='^ exit-address-family', daemon=bgp_daemon) self.assertIn(f' maximum-paths {max_path_v6}', afiv6_config) self.assertIn(f' maximum-paths ibgp {max_path_v6ibgp}', afiv6_config) @@ -514,7 +516,7 @@ def test_bgp_02_neighbors(self): self.cli_commit() # Verify FRR bgpd configuration - frrconfig = self.getFRRconfig(f'router bgp {ASN}') + frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit', daemon=bgp_daemon) self.assertIn(f'router bgp {ASN}', frrconfig) for peer, peer_config in neighbor_config.items(): @@ -619,7 +621,7 @@ def test_bgp_03_peer_groups(self): self.cli_commit() # Verify FRR bgpd configuration - frrconfig = self.getFRRconfig(f'router bgp {ASN}') + frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit', daemon=bgp_daemon) self.assertIn(f'router bgp {ASN}', frrconfig) for peer, peer_config in peer_group_config.items(): @@ -668,7 +670,7 @@ def test_bgp_04_afi_ipv4(self): self.cli_commit() # Verify FRR bgpd configuration - frrconfig = self.getFRRconfig(f'router bgp {ASN}') + frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit', daemon=bgp_daemon) self.assertIn(f'router bgp {ASN}', frrconfig) self.assertIn(f' address-family ipv4 unicast', frrconfig) @@ -714,7 +716,7 @@ def test_bgp_05_afi_ipv6(self): self.cli_commit() # Verify FRR bgpd configuration - frrconfig = self.getFRRconfig(f'router bgp {ASN}') + frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit', daemon=bgp_daemon) self.assertIn(f'router bgp {ASN}', frrconfig) self.assertIn(f' address-family ipv6 unicast', frrconfig) # T2100: By default ebgp-requires-policy is disabled to keep VyOS @@ -756,7 +758,7 @@ def test_bgp_06_listen_range(self): self.cli_commit() # Verify FRR bgpd configuration - frrconfig = self.getFRRconfig(f'router bgp {ASN}') + frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit', daemon=bgp_daemon) self.assertIn(f'router bgp {ASN}', frrconfig) self.assertIn(f' neighbor {peer_group} peer-group', frrconfig) self.assertIn(f' neighbor {peer_group} remote-as {ASN}', frrconfig) @@ -791,7 +793,7 @@ def test_bgp_07_l2vpn_evpn(self): self.cli_commit() # Verify FRR bgpd configuration - frrconfig = self.getFRRconfig(f'router bgp {ASN}') + frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit', daemon=bgp_daemon) self.assertIn(f'router bgp {ASN}', frrconfig) self.assertIn(f' address-family l2vpn evpn', frrconfig) self.assertIn(f' advertise-all-vni', frrconfig) @@ -804,7 +806,7 @@ def test_bgp_07_l2vpn_evpn(self): self.assertIn(f' flooding disable', frrconfig) self.assertIn(f' mac-vrf soo {soo}', frrconfig) for vni in vnis: - vniconfig = self.getFRRconfig(f' vni {vni}') + vniconfig = self.getFRRconfig(f' vni {vni}', endsection='^exit-vni') self.assertIn(f'vni {vni}', vniconfig) self.assertIn(f' advertise-default-gw', vniconfig) self.assertIn(f' advertise-svi-ip', vniconfig) @@ -1383,7 +1385,7 @@ def test_bgp_99_bmp(self): # let the bgpd process recover sleep(10) # update daemon PID - this was a planned daemon restart - self.daemon_pid = process_named_running(PROCESS_NAME) + self.daemon_pid = process_named_running(bgp_daemon) # set bmp config but not set address self.cli_set(target_path + ['port', target_port]) diff --git a/smoketest/scripts/cli/test_protocols_isis.py b/smoketest/scripts/cli/test_protocols_isis.py index 2fddbfebaf..bde32a1ca8 100755 --- a/smoketest/scripts/cli/test_protocols_isis.py +++ b/smoketest/scripts/cli/test_protocols_isis.py @@ -22,7 +22,6 @@ from vyos.utils.process import process_named_running from vyos.frrender import isis_daemon -PROCESS_NAME = 'isisd' base_path = ['protocols', 'isis'] domain = 'VyOS' @@ -35,7 +34,7 @@ def setUpClass(cls): # call base-classes classmethod super(TestProtocolsISIS, cls).setUpClass() # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same - cls.daemon_pid = process_named_running(PROCESS_NAME) + cls.daemon_pid = process_named_running(isis_daemon) # ensure we can also run this test on a live system - so lets clean # out the current configuration :) cls.cli_delete(cls, base_path) @@ -50,7 +49,7 @@ def tearDown(self): self.cli_commit() # check process health and continuity - self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) + self.assertEqual(self.daemon_pid, process_named_running(isis_daemon)) def isis_base_config(self): self.cli_set(base_path + ['net', net]) diff --git a/smoketest/scripts/cli/test_protocols_mpls.py b/smoketest/scripts/cli/test_protocols_mpls.py index 0c1599f9b2..88528973de 100755 --- a/smoketest/scripts/cli/test_protocols_mpls.py +++ b/smoketest/scripts/cli/test_protocols_mpls.py @@ -19,9 +19,9 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section +from vyos.frrender import ldpd_daemon from vyos.utils.process import process_named_running -PROCESS_NAME = 'ldpd' base_path = ['protocols', 'mpls', 'ldp'] peers = { @@ -71,7 +71,7 @@ def setUpClass(cls): super(TestProtocolsMPLS, cls).setUpClass() # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same - cls.daemon_pid = process_named_running(PROCESS_NAME) + cls.daemon_pid = process_named_running(ldpd_daemon) # ensure we can also run this test on a live system - so lets clean # out the current configuration :) @@ -82,7 +82,7 @@ def tearDown(self): self.cli_commit() # check process health and continuity - self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) + self.assertEqual(self.daemon_pid, process_named_running(ldpd_daemon)) def test_mpls_basic(self): router_id = '1.2.3.4' @@ -106,12 +106,12 @@ def test_mpls_basic(self): self.cli_commit() # Validate configuration - frrconfig = self.getFRRconfig('mpls ldp', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('mpls ldp', daemon=ldpd_daemon) self.assertIn(f'mpls ldp', frrconfig) self.assertIn(f' router-id {router_id}', frrconfig) # Validate AFI IPv4 - afiv4_config = self.getFRRconfig(' address-family ipv4', daemon=PROCESS_NAME) + afiv4_config = self.getFRRconfig(' address-family ipv4', daemon=ldpd_daemon) self.assertIn(f' discovery transport-address {transport_ipv4_addr}', afiv4_config) for interface in interfaces: self.assertIn(f' interface {interface}', afiv4_config) diff --git a/smoketest/scripts/cli/test_protocols_openfabric.py b/smoketest/scripts/cli/test_protocols_openfabric.py index 3e99656ecd..f1372d7ab1 100644 --- a/smoketest/scripts/cli/test_protocols_openfabric.py +++ b/smoketest/scripts/cli/test_protocols_openfabric.py @@ -21,7 +21,6 @@ from vyos.utils.process import process_named_running from vyos.frrender import openfabric_daemon -PROCESS_NAME = 'fabricd' base_path = ['protocols', 'openfabric'] domain = 'VyOS' @@ -37,7 +36,7 @@ def setUpClass(cls): # call base-classes classmethod super(TestProtocolsOpenFabric, cls).setUpClass() # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same - cls.daemon_pid = process_named_running(PROCESS_NAME) + cls.daemon_pid = process_named_running(openfabric_daemon) # ensure we can also run this test on a live system - so lets clean # out the current configuration :) cls.cli_delete(cls, base_path) @@ -47,7 +46,7 @@ def tearDown(self): self.cli_commit() # check process health and continuity - self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) + self.assertEqual(self.daemon_pid, process_named_running(openfabric_daemon)) def openfabric_base_config(self): self.cli_set(['interfaces', 'dummy', dummy_if]) diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py index 97543048fb..2bc9cf2a50 100755 --- a/smoketest/scripts/cli/test_protocols_ospf.py +++ b/smoketest/scripts/cli/test_protocols_ospf.py @@ -21,10 +21,10 @@ from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section +from vyos.frrender import ospf_daemon from vyos.utils.process import process_named_running from vyos.xml_ref import default_value -PROCESS_NAME = 'ospfd' base_path = ['protocols', 'ospf'] route_map = 'foo-bar-baz10' @@ -36,7 +36,7 @@ def setUpClass(cls): super(TestProtocolsOSPF, cls).setUpClass() # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same - cls.daemon_pid = process_named_running(PROCESS_NAME) + cls.daemon_pid = process_named_running(ospf_daemon) cls.cli_set(cls, ['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit']) cls.cli_set(cls, ['policy', 'route-map', route_map, 'rule', '20', 'action', 'permit']) @@ -56,11 +56,11 @@ def tearDown(self): self.cli_delete(base_path) self.cli_commit() - frrconfig = self.getFRRconfig('router ospf') + frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) self.assertNotIn(f'router ospf', frrconfig) # check process health and continuity - self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) + self.assertEqual(self.daemon_pid, process_named_running(ospf_daemon)) def test_ospf_01_defaults(self): # commit changes @@ -68,7 +68,7 @@ def test_ospf_01_defaults(self): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) self.assertIn(f'router ospf', frrconfig) self.assertIn(f' auto-cost reference-bandwidth 100', frrconfig) self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults @@ -96,7 +96,7 @@ def test_ospf_02_simple(self): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) self.assertIn(f'router ospf', frrconfig) self.assertIn(f' compatible rfc1583', frrconfig) self.assertIn(f' auto-cost reference-bandwidth {bandwidth}', frrconfig) @@ -128,7 +128,7 @@ def test_ospf_03_access_list(self): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) self.assertIn(f'router ospf', frrconfig) self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults for ptotocol in protocols: @@ -149,7 +149,7 @@ def test_ospf_04_default_originate(self): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) self.assertIn(f'router ospf', frrconfig) self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults self.assertIn(f' default-information originate metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) @@ -159,7 +159,7 @@ def test_ospf_04_default_originate(self): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) self.assertIn(f' default-information originate always metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) @@ -201,7 +201,7 @@ def test_ospf_05_options(self): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) self.assertIn(f'router ospf', frrconfig) self.assertIn(f' mpls-te on', frrconfig) self.assertIn(f' mpls-te router-address 0.0.0.0', frrconfig) # default @@ -224,7 +224,7 @@ def test_ospf_05_options(self): self.cli_set(base_path + ['distance', 'ospf', 'inter-area', inter_area]) self.cli_commit() - frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) self.assertIn(f' distance ospf intra-area {intra_area} inter-area {inter_area} external {external}', frrconfig) # https://github.com/FRRouting/frr/issues/17011 @@ -247,7 +247,7 @@ def test_ospf_06_neighbor(self): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) self.assertIn(f'router ospf', frrconfig) for neighbor in neighbors: self.assertIn(f' neighbor {neighbor} priority {priority} poll-interval {poll_interval}', frrconfig) # default @@ -266,7 +266,7 @@ def test_ospf_07_redistribute(self): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) self.assertIn(f'router ospf', frrconfig) for protocol in redistribute: self.assertIn(f' redistribute {protocol} metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) @@ -294,7 +294,7 @@ def test_ospf_08_virtual_link(self): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) self.assertIn(f'router ospf', frrconfig) self.assertIn(f' area {area} shortcut {shortcut}', frrconfig) self.assertIn(f' area {area} virtual-link {virtual_link} hello-interval {hello} retransmit-interval {retransmit} retransmit-window {window_default} transmit-delay {transmit} dead-interval {dead}', frrconfig) @@ -326,7 +326,7 @@ def test_ospf_09_interface_configuration(self): # commit changes self.cli_commit() - frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) self.assertIn(f'router ospf', frrconfig) self.assertIn(f' passive-interface default', frrconfig) @@ -350,7 +350,7 @@ def test_ospf_09_interface_configuration(self): for interface in interfaces: # T5467: It must also be removed from FRR config - frrconfig = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig(f'interface {interface}', daemon=ospf_daemon) self.assertNotIn(f'interface {interface}', frrconfig) # There should be no OSPF related command at all under the interface self.assertNotIn(f' ip ospf', frrconfig) @@ -371,11 +371,11 @@ def test_ospf_11_interface_area(self): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) self.assertIn(f'router ospf', frrconfig) for interface in interfaces: - config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + config = self.getFRRconfig(f'interface {interface}', daemon=ospf_daemon) self.assertIn(f'interface {interface}', config) self.assertIn(f' ip ospf area {area}', config) @@ -398,17 +398,17 @@ def test_ospf_12_vrfs(self): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) self.assertIn(f'router ospf', frrconfig) self.assertIn(f' auto-cost reference-bandwidth 100', frrconfig) self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults - frrconfig = self.getFRRconfig(f'router ospf vrf {vrf}', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig(f'router ospf vrf {vrf}', daemon=ospf_daemon) self.assertIn(f'router ospf vrf {vrf}', frrconfig) self.assertIn(f' auto-cost reference-bandwidth 100', frrconfig) self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults - frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=ospf_daemon) self.assertIn(f'interface {vrf_iface}', frrconfig) self.assertIn(f' ip ospf area {area}', frrconfig) @@ -418,7 +418,7 @@ def test_ospf_12_vrfs(self): self.cli_commit() # T5467: It must also be removed from FRR config - frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=ospf_daemon) self.assertNotIn(f'interface {vrf_iface}', frrconfig) # There should be no OSPF related command at all under the interface self.assertNotIn(f' ip ospf', frrconfig) @@ -444,7 +444,7 @@ def test_ospf_13_export_list(self): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) self.assertIn(f'router ospf', frrconfig) self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # default self.assertIn(f' network {network} area {area}', frrconfig) @@ -477,7 +477,7 @@ def test_ospf_14_segment_routing_configuration(self): self.cli_commit() # Verify all changes - frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) self.assertIn(f' segment-routing on', frrconfig) self.assertIn(f' segment-routing global-block {global_block_low} {global_block_high} local-block {local_block_low} {local_block_high}', frrconfig) self.assertIn(f' segment-routing node-msd {maximum_stack_size}', frrconfig) @@ -495,7 +495,7 @@ def test_ospf_15_ldp_sync(self): self.cli_commit() # Verify main OSPF changes - frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) self.assertIn(f'router ospf', frrconfig) self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) self.assertIn(f' mpls ldp-sync holddown {holddown}', frrconfig) @@ -508,7 +508,7 @@ def test_ospf_15_ldp_sync(self): for interface in interfaces: # Verify interface changes for holddown - config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + config = self.getFRRconfig(f'interface {interface}', daemon=ospf_daemon) self.assertIn(f'interface {interface}', config) self.assertIn(f' ip ospf dead-interval 40', config) self.assertIn(f' ip ospf mpls ldp-sync', config) @@ -522,7 +522,7 @@ def test_ospf_15_ldp_sync(self): for interface in interfaces: # Verify interface changes for disable - config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + config = self.getFRRconfig(f'interface {interface}', daemon=ospf_daemon) self.assertIn(f'interface {interface}', config) self.assertIn(f' ip ospf dead-interval 40', config) self.assertNotIn(f' ip ospf mpls ldp-sync', config) @@ -544,7 +544,7 @@ def test_ospf_16_graceful_restart(self): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) self.assertIn(f'router ospf', frrconfig) self.assertIn(f' capability opaque', frrconfig) self.assertIn(f' graceful-restart grace-period {period}', frrconfig) @@ -570,7 +570,7 @@ def test_ospf_17_duplicate_area_network(self): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) # Required to prevent the race condition T6761 retry_count = 0 max_retries = 60 @@ -582,7 +582,7 @@ def test_ospf_17_duplicate_area_network(self): retry_count += 1 sleep(1) - frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) if not frrconfig: print("Failed to retrieve FRR config after 60 seconds") diff --git a/smoketest/scripts/cli/test_protocols_ospfv3.py b/smoketest/scripts/cli/test_protocols_ospfv3.py index f0892d5765..d961a4fdc4 100755 --- a/smoketest/scripts/cli/test_protocols_ospfv3.py +++ b/smoketest/scripts/cli/test_protocols_ospfv3.py @@ -20,9 +20,9 @@ from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section +from vyos.frrender import ospf6_daemon from vyos.utils.process import process_named_running -PROCESS_NAME = 'ospf6d' base_path = ['protocols', 'ospfv3'] route_map = 'foo-bar-baz-0815' @@ -36,7 +36,7 @@ def setUpClass(cls): super(TestProtocolsOSPFv3, cls).setUpClass() # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same - cls.daemon_pid = process_named_running(PROCESS_NAME) + cls.daemon_pid = process_named_running(ospf6_daemon) cls.cli_set(cls, ['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit']) cls.cli_set(cls, ['policy', 'route-map', route_map, 'rule', '20', 'action', 'permit']) @@ -54,11 +54,11 @@ def tearDown(self): self.cli_delete(base_path) self.cli_commit() - frrconfig = self.getFRRconfig('router ospf6') + frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon) self.assertNotIn(f'router ospf6', frrconfig) # check process health and continuity - self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) + self.assertEqual(self.daemon_pid, process_named_running(ospf6_daemon)) def test_ospfv3_01_basic(self): seq = '10' @@ -81,7 +81,7 @@ def test_ospfv3_01_basic(self): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon) self.assertIn(f'router ospf6', frrconfig) self.assertIn(f' area {default_area} range {prefix}', frrconfig) self.assertIn(f' ospf6 router-id {router_id}', frrconfig) @@ -89,7 +89,7 @@ def test_ospfv3_01_basic(self): self.assertIn(f' area {default_area} export-list {acl_name}', frrconfig) for interface in interfaces: - if_config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + if_config = self.getFRRconfig(f'interface {interface}', daemon=ospf6_daemon) self.assertIn(f'ipv6 ospf6 area {default_area}', if_config) self.cli_delete(['policy', 'access-list6', acl_name]) @@ -110,7 +110,7 @@ def test_ospfv3_02_distance(self): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon) self.assertIn(f'router ospf6', frrconfig) self.assertIn(f' distance {dist_global}', frrconfig) self.assertIn(f' distance ospf6 intra-area {dist_intra_area} inter-area {dist_inter_area} external {dist_external}', frrconfig) @@ -134,7 +134,7 @@ def test_ospfv3_03_redistribute(self): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon) self.assertIn(f'router ospf6', frrconfig) for protocol in redistribute: self.assertIn(f' redistribute {protocol} metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) @@ -165,13 +165,13 @@ def test_ospfv3_04_interfaces(self): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon) self.assertIn(f'router ospf6', frrconfig) cost = '100' priority = '10' for interface in interfaces: - if_config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + if_config = self.getFRRconfig(f'interface {interface}', daemon=ospf6_daemon) self.assertIn(f'interface {interface}', if_config) self.assertIn(f' ipv6 ospf6 bfd', if_config) self.assertIn(f' ipv6 ospf6 bfd profile {bfd_profile}', if_config) @@ -188,7 +188,7 @@ def test_ospfv3_04_interfaces(self): self.cli_commit() for interface in interfaces: - if_config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + if_config = self.getFRRconfig(f'interface {interface}', daemon=ospf6_daemon) # There should be no OSPF6 configuration at all after interface removal self.assertNotIn(f' ipv6 ospf6', if_config) @@ -204,7 +204,7 @@ def test_ospfv3_05_area_stub(self): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon) self.assertIn(f'router ospf6', frrconfig) self.assertIn(f' area {area_stub} stub', frrconfig) self.assertIn(f' area {area_stub_nosum} stub no-summary', frrconfig) @@ -230,7 +230,7 @@ def test_ospfv3_06_area_nssa(self): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon) self.assertIn(f'router ospf6', frrconfig) self.assertIn(f' area {area_nssa} nssa', frrconfig) self.assertIn(f' area {area_nssa_nosum} nssa default-information-originate no-summary', frrconfig) @@ -250,7 +250,7 @@ def test_ospfv3_07_default_originate(self): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon) self.assertIn(f'router ospf6', frrconfig) self.assertIn(f' default-information originate metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) @@ -259,7 +259,7 @@ def test_ospfv3_07_default_originate(self): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon) self.assertIn(f' default-information originate always metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) @@ -285,15 +285,15 @@ def test_ospfv3_08_vrfs(self): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon) self.assertIn(f'router ospf6', frrconfig) self.assertIn(f' ospf6 router-id {router_id}', frrconfig) - frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=ospf6_daemon) self.assertIn(f'interface {vrf_iface}', frrconfig) self.assertIn(f' ipv6 ospf6 bfd', frrconfig) - frrconfig = self.getFRRconfig(f'router ospf6 vrf {vrf}', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig(f'router ospf6 vrf {vrf}', daemon=ospf6_daemon) self.assertIn(f'router ospf6 vrf {vrf}', frrconfig) self.assertIn(f' ospf6 router-id {router_id_vrf}', frrconfig) @@ -303,7 +303,7 @@ def test_ospfv3_08_vrfs(self): self.cli_commit() # T5467: It must also be removed from FRR config - frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=ospf6_daemon) self.assertNotIn(f'interface {vrf_iface}', frrconfig) # There should be no OSPF related command at all under the interface self.assertNotIn(f' ipv6 ospf6', frrconfig) @@ -329,7 +329,7 @@ def test_ospfv3_09_graceful_restart(self): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon) self.assertIn(f'router ospf6', frrconfig) self.assertIn(f' graceful-restart grace-period {period}', frrconfig) self.assertIn(f' graceful-restart helper planned-only', frrconfig) diff --git a/smoketest/scripts/cli/test_protocols_pim.py b/smoketest/scripts/cli/test_protocols_pim.py index a2cc4f1ed8..98b9d57aaf 100755 --- a/smoketest/scripts/cli/test_protocols_pim.py +++ b/smoketest/scripts/cli/test_protocols_pim.py @@ -23,19 +23,18 @@ from vyos.ifconfig import Section from vyos.utils.process import process_named_running -PROCESS_NAME = pim_daemon base_path = ['protocols', 'pim'] class TestProtocolsPIM(VyOSUnitTestSHIM.TestCase): def tearDown(self): # pimd process must be running - self.assertTrue(process_named_running(PROCESS_NAME)) + self.assertTrue(process_named_running(pim_daemon)) self.cli_delete(base_path) self.cli_commit() # pimd process must be stopped by now - self.assertFalse(process_named_running(PROCESS_NAME)) + self.assertFalse(process_named_running(pim_daemon)) def test_01_pim_basic(self): rp = '127.0.0.1' @@ -58,11 +57,11 @@ def test_01_pim_basic(self): self.cli_commit() # Verify FRR pimd configuration - frrconfig = self.getFRRconfig('router pim', endsection='^exit', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router pim', endsection='^exit', daemon=pim_daemon) self.assertIn(f' rp {rp} {group}', frrconfig) for interface in interfaces: - frrconfig = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig(f'interface {interface}', daemon=pim_daemon) self.assertIn(f'interface {interface}', frrconfig) self.assertIn(f' ip pim', frrconfig) self.assertIn(f' ip pim bfd', frrconfig) @@ -109,7 +108,7 @@ def test_02_pim_advanced(self): self.cli_commit() # Verify FRR pimd configuration - frrconfig = self.getFRRconfig('router pim', endsection='^exit', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig('router pim', endsection='^exit', daemon=pim_daemon) self.assertIn(f' no send-v6-secondary', frrconfig) self.assertIn(f' rp {rp} {group}', frrconfig) self.assertIn(f' register-suppress-time {register_suppress_time}', frrconfig) @@ -171,11 +170,11 @@ def test_04_igmp(self): self.cli_commit() - frrconfig = self.getFRRconfig(daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig(daemon=pim_daemon) self.assertIn(f'ip igmp watermark-warn {watermark_warning}', frrconfig) for interface in interfaces: - frrconfig = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + frrconfig = self.getFRRconfig(f'interface {interface}', daemon=pim_daemon) self.assertIn(f'interface {interface}', frrconfig) self.assertIn(f' ip igmp', frrconfig) self.assertIn(f' ip igmp version {version}', frrconfig) diff --git a/smoketest/scripts/cli/test_protocols_pim6.py b/smoketest/scripts/cli/test_protocols_pim6.py index 4b9ae6161f..f69eb4ae1c 100755 --- a/smoketest/scripts/cli/test_protocols_pim6.py +++ b/smoketest/scripts/cli/test_protocols_pim6.py @@ -19,9 +19,9 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section +from vyos.frrender import pim6_daemon from vyos.utils.process import process_named_running -PROCESS_NAME = 'pim6d' base_path = ['protocols', 'pim6'] class TestProtocolsPIMv6(VyOSUnitTestSHIM.TestCase): @@ -30,7 +30,7 @@ def setUpClass(cls): # call base-classes classmethod super(TestProtocolsPIMv6, cls).setUpClass() # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same - cls.daemon_pid = process_named_running(PROCESS_NAME) + cls.daemon_pid = process_named_running(pim6_daemon) # ensure we can also run this test on a live system - so lets clean # out the current configuration :) cls.cli_delete(cls, base_path) @@ -40,7 +40,7 @@ def tearDown(self): self.cli_commit() # check process health and continuity - self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) + self.assertEqual(self.daemon_pid, process_named_running(pim6_daemon)) def test_pim6_01_mld_simple(self): # commit changes @@ -52,7 +52,7 @@ def test_pim6_01_mld_simple(self): # Verify FRR pim6d configuration for interface in interfaces: - config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + config = self.getFRRconfig(f'interface {interface}', daemon=pim6_daemon) self.assertIn(f'interface {interface}', config) self.assertIn(f' ipv6 mld', config) self.assertNotIn(f' ipv6 mld version 1', config) @@ -65,7 +65,7 @@ def test_pim6_01_mld_simple(self): # Verify FRR pim6d configuration for interface in interfaces: - config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + config = self.getFRRconfig(f'interface {interface}', daemon=pim6_daemon) self.assertIn(f'interface {interface}', config) self.assertIn(f' ipv6 mld', config) self.assertIn(f' ipv6 mld version 1', config) @@ -88,7 +88,7 @@ def test_pim6_02_mld_join(self): # Verify FRR pim6d configuration for interface in interfaces: - config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + config = self.getFRRconfig(f'interface {interface}', daemon=pim6_daemon) self.assertIn(f'interface {interface}', config) self.assertIn(f' ipv6 mld join-group ff18::1234', config) @@ -100,7 +100,7 @@ def test_pim6_02_mld_join(self): # Verify FRR pim6d configuration for interface in interfaces: - config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + config = self.getFRRconfig(f'interface {interface}', daemon=pim6_daemon) self.assertIn(f'interface {interface}', config) self.assertIn(f' ipv6 mld join-group ff38::5678 2001:db8::5678', config) @@ -128,14 +128,14 @@ def test_pim6_03_basic(self): self.cli_commit() # Verify FRR pim6d configuration - config = self.getFRRconfig('router pim6', endsection='^exit', daemon=PROCESS_NAME) + config = self.getFRRconfig('router pim6', endsection='^exit', daemon=pim6_daemon) self.assertIn(f' join-prune-interval {join_prune_interval}', config) self.assertIn(f' keep-alive-timer {keep_alive_timer}', config) self.assertIn(f' packets {packets}', config) self.assertIn(f' register-suppress-time {register_suppress_time}', config) for interface in interfaces: - config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + config = self.getFRRconfig(f'interface {interface}', daemon=pim6_daemon) self.assertIn(f' ipv6 pim drpriority {dr_priority}', config) self.assertIn(f' ipv6 pim hello {hello}', config) self.assertIn(f' no ipv6 pim bsm', config) diff --git a/smoketest/scripts/cli/test_protocols_rip.py b/smoketest/scripts/cli/test_protocols_rip.py index 48a1f877b9..33706a9c9d 100755 --- a/smoketest/scripts/cli/test_protocols_rip.py +++ b/smoketest/scripts/cli/test_protocols_rip.py @@ -19,9 +19,9 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.ifconfig import Section +from vyos.frrender import rip_daemon from vyos.utils.process import process_named_running -PROCESS_NAME = 'ripd' acl_in = '198' acl_out = '199' prefix_list_in = 'foo-prefix' @@ -35,7 +35,7 @@ class TestProtocolsRIP(VyOSUnitTestSHIM.TestCase): def setUpClass(cls): super(TestProtocolsRIP, cls).setUpClass() # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same - cls.daemon_pid = process_named_running(PROCESS_NAME) + cls.daemon_pid = process_named_running(rip_daemon) # ensure we can also run this test on a live system - so lets clean # out the current configuration :) cls.cli_delete(cls, base_path) @@ -70,7 +70,7 @@ def tearDown(self): self.assertNotIn(f'router rip', frrconfig) # check process health and continuity - self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) + self.assertEqual(self.daemon_pid, process_named_running(rip_daemon)) def test_rip_01_parameters(self): distance = '40' diff --git a/smoketest/scripts/cli/test_protocols_ripng.py b/smoketest/scripts/cli/test_protocols_ripng.py index c266bdfd27..b10df9679f 100755 --- a/smoketest/scripts/cli/test_protocols_ripng.py +++ b/smoketest/scripts/cli/test_protocols_ripng.py @@ -19,9 +19,9 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.ifconfig import Section +from vyos.frrender import ripng_daemon from vyos.utils.process import process_named_running -PROCESS_NAME = 'ripngd' acl_in = '198' acl_out = '199' prefix_list_in = 'foo-prefix' @@ -36,7 +36,7 @@ def setUpClass(cls): # call base-classes classmethod super(TestProtocolsRIPng, cls).setUpClass() # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same - cls.daemon_pid = process_named_running(PROCESS_NAME) + cls.daemon_pid = process_named_running(ripng_daemon) # ensure we can also run this test on a live system - so lets clean # out the current configuration :) cls.cli_delete(cls, base_path) @@ -70,7 +70,7 @@ def tearDown(self): self.assertNotIn(f'router ripng', frrconfig) # check process health and continuity - self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) + self.assertEqual(self.daemon_pid, process_named_running(ripng_daemon)) def test_ripng_01_parameters(self): metric = '8' diff --git a/smoketest/scripts/cli/test_protocols_rpki.py b/smoketest/scripts/cli/test_protocols_rpki.py index c6a6adf58e..23738717a8 100755 --- a/smoketest/scripts/cli/test_protocols_rpki.py +++ b/smoketest/scripts/cli/test_protocols_rpki.py @@ -19,12 +19,11 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError +from vyos.frrender import bgp_daemon from vyos.utils.file import read_file from vyos.utils.process import process_named_running base_path = ['protocols', 'rpki'] -PROCESS_NAME = 'bgpd' - rpki_key_name = 'rpki-smoketest' rpki_key_type = 'ssh-rsa' @@ -108,7 +107,7 @@ def setUpClass(cls): # call base-classes classmethod super(TestProtocolsRPKI, cls).setUpClass() # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same - cls.daemon_pid = process_named_running(PROCESS_NAME) + cls.daemon_pid = process_named_running(bgp_daemon) # ensure we can also run this test on a live system - so lets clean # out the current configuration :) cls.cli_delete(cls, base_path) @@ -121,7 +120,7 @@ def tearDown(self): self.assertNotIn(f'rpki', frrconfig) # check process health and continuity - self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) + self.assertEqual(self.daemon_pid, process_named_running(bgp_daemon)) def test_rpki(self): expire_interval = '3600' diff --git a/smoketest/scripts/cli/test_protocols_segment-routing.py b/smoketest/scripts/cli/test_protocols_segment-routing.py index 8905d5e452..624985476e 100755 --- a/smoketest/scripts/cli/test_protocols_segment-routing.py +++ b/smoketest/scripts/cli/test_protocols_segment-routing.py @@ -25,7 +25,6 @@ from vyos.utils.system import sysctl_read base_path = ['protocols', 'segment-routing'] -PROCESS_NAME = 'zebra' class TestProtocolsSegmentRouting(VyOSUnitTestSHIM.TestCase): @classmethod @@ -33,7 +32,7 @@ def setUpClass(cls): # call base-classes classmethod super(TestProtocolsSegmentRouting, cls).setUpClass() # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same - cls.daemon_pid = process_named_running(PROCESS_NAME) + cls.daemon_pid = process_named_running(zebra_daemon) # ensure we can also run this test on a live system - so lets clean # out the current configuration :) cls.cli_delete(cls, base_path) @@ -43,7 +42,7 @@ def tearDown(self): self.cli_commit() # check process health and continuity - self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) + self.assertEqual(self.daemon_pid, process_named_running(zebra_daemon)) def test_srv6(self): interfaces = Section.interfaces('ethernet', vlan=False) From e803c5e1a8828a1a3f8fb2c15b9ddbc232e12eea Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sat, 14 Dec 2024 16:03:31 +0100 Subject: [PATCH 27/28] babel: T6746: remove superfluous "end" in daemon template --- data/templates/frr/babeld.frr.j2 | 2 -- 1 file changed, 2 deletions(-) diff --git a/data/templates/frr/babeld.frr.j2 b/data/templates/frr/babeld.frr.j2 index 344a5f988b..292bd9972e 100644 --- a/data/templates/frr/babeld.frr.j2 +++ b/data/templates/frr/babeld.frr.j2 @@ -45,7 +45,6 @@ exit {% endfor %} {% endif %} ! -{# Babel configuration #} router babel {% if parameters.diversity is vyos_defined %} babel diversity @@ -82,4 +81,3 @@ router babel {% endif %} exit ! -end From 90e9aa9df41c3b99f9f1421227a1f1474622b918 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Fri, 13 Dec 2024 13:57:14 +0100 Subject: [PATCH 28/28] frr: T6746: add guard time after cli_commit() and before getFRRconfig() As vyos-configd will take care about the commit via FRRender class, and FRR needs to internally process the configuration we might read it back via vtysh "to fast". Add a 5 seconds guard timer after each cli_commit() and before calling getFRRconfig(). Guard timer is reset every time, cli_commit() is called. --- smoketest/scripts/cli/base_vyostest_shim.py | 23 +++++++++++++++---- smoketest/scripts/cli/test_protocols_babel.py | 2 +- smoketest/scripts/cli/test_protocols_ospf.py | 18 +-------------- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/smoketest/scripts/cli/base_vyostest_shim.py b/smoketest/scripts/cli/base_vyostest_shim.py index affa538774..d95071d1a9 100644 --- a/smoketest/scripts/cli/base_vyostest_shim.py +++ b/smoketest/scripts/cli/base_vyostest_shim.py @@ -18,6 +18,7 @@ import pprint from time import sleep +from time import time from typing import Type from vyos.configsession import ConfigSession @@ -43,7 +44,7 @@ class TestCase(unittest.TestCase): # trigger the certain failure condition. # Use "self.debug = True" in derived classes setUp() method debug = False - + commit_guard = time() @classmethod def setUpClass(cls): cls._session = ConfigSession(os.getpid()) @@ -87,6 +88,8 @@ def cli_commit(self): # during a commit there is a process opening commit_lock, and run() returns 0 while run(f'sudo lsof -nP {commit_lock}') == 0: sleep(0.250) + # reset getFRRconfig() guard timer + self.commit_guard = time() def op_mode(self, path : list) -> None: """ @@ -101,17 +104,29 @@ def op_mode(self, path : list) -> None: pprint.pprint(out) return out - def getFRRconfig(self, string=None, end='$', endsection='^!', daemon=''): + def getFRRconfig(self, string=None, end='$', endsection='^!', daemon='', guard_time=10, empty_retry=0): """ Retrieve current "running configuration" from FRR """ # Sometimes FRR needs some time after reloading the configuration to - # appear in vtysh. This is a workaround addiung a 2 seconds guard timer - sleep(2) + # appear in vtysh. This is a workaround addiung a 10 second guard timer + # between the last cli_commit() and the first read of FRR config via vtysh + while (time() - self.commit_guard) < guard_time: + sleep(0.250) # wait 250 milliseconds command = f'vtysh -c "show run {daemon} no-header"' if string: command += f' | sed -n "/^{string}{end}/,/{endsection}/p"' out = cmd(command) if self.debug: print(f'\n\ncommand "{command}" returned:\n') pprint.pprint(out) + if empty_retry: + retry_count = 0 + while not out and retry_count < empty_retry: + if self.debug and retry_count % 10 == 0: + print(f"Attempt {retry_count}: FRR config is still empty. Retrying...") + retry_count += 1 + sleep(1) + out = cmd(command) + if not out: + print(f'FRR configuration still empty after {empty_retry} retires!') return out @staticmethod diff --git a/smoketest/scripts/cli/test_protocols_babel.py b/smoketest/scripts/cli/test_protocols_babel.py index 7a79c7b9e5..107e262cc9 100755 --- a/smoketest/scripts/cli/test_protocols_babel.py +++ b/smoketest/scripts/cli/test_protocols_babel.py @@ -105,7 +105,7 @@ def test_babel_redistribute(self): self.cli_commit() - frrconfig = self.getFRRconfig('router babel', endsection='^exit', daemon=babel_daemon) + frrconfig = self.getFRRconfig('router babel', endsection='^exit', daemon=babel_daemon, empty_retry=5) for protocol in ipv4_protos: self.assertIn(f' redistribute ipv4 {protocol}', frrconfig) for protocol in ipv6_protos: diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py index 2bc9cf2a50..599bf3930a 100755 --- a/smoketest/scripts/cli/test_protocols_ospf.py +++ b/smoketest/scripts/cli/test_protocols_ospf.py @@ -570,23 +570,7 @@ def test_ospf_17_duplicate_area_network(self): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) - # Required to prevent the race condition T6761 - retry_count = 0 - max_retries = 60 - - while not frrconfig and retry_count < max_retries: - # Log every 10 seconds - if retry_count % 10 == 0: - print(f"Attempt {retry_count}: FRR config is still empty. Retrying...") - - retry_count += 1 - sleep(1) - frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) - - if not frrconfig: - print("Failed to retrieve FRR config after 60 seconds") - + frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon, empty_retry=60) self.assertIn(f'router ospf', frrconfig) self.assertIn(f' network {network} area {area1}', frrconfig)