From 81923562a8a164f7ff9761e976c20420a585907a Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sun, 6 Oct 2024 15:17:17 +0200 Subject: frr: T6747: make daemon definitions re-usable for both conf-mode and smoketests --- src/conf_mode/protocols_segment-routing.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'src/conf_mode/protocols_segment-routing.py') diff --git a/src/conf_mode/protocols_segment-routing.py b/src/conf_mode/protocols_segment-routing.py index b36c2ca11..67f8005ef 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 -- cgit v1.2.3 From 3c79477adf3cd4f4efb302b58542ddd668b562ac Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sun, 8 Dec 2024 08:34:41 +0100 Subject: 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 +- smoketest/scripts/cli/test_protocols_static.py | 110 ++++- .../scripts/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 (limited to 'src/conf_mode/protocols_segment-routing.py') diff --git a/data/templates/frr/bgpd.frr.j2 b/data/templates/frr/bgpd.frr.j2 index 71716250d..51a3f2564 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 03aaac44b..2fd7b7c09 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 8f2ae6466..3a0646eb8 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 54b2790b0..000000000 --- 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 db31700c5..000000000 --- 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 68e3f21f9..c662b7650 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 8ebb82511..656b31deb 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 052678113..aa0b083c7 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 000000000..a27955b0c --- /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 000000000..e53994191 --- /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 d588b369f..000000000 --- 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 51e45c6f7..fa1131118 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 4468c8025..e75385dc7 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 ca4ca2d74..407e56553 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 caf95ed7c..000000000 --- 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 5a353b110..88d131573 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 92996f2ee..4450dc16b 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 000000000..015596a8f --- /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 a0c6ab055..7ea1b610e 100755 --- a/smoketest/scripts/cli/test_policy.py +++ b/smoketest/scripts/cli/test_policy.py @@ -1945,7 +1945,7 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): 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 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): 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 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): 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 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): 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 716d0a806..9f178c821 100755 --- a/smoketest/scripts/cli/test_protocols_bfd.py +++ b/smoketest/scripts/cli/test_protocols_bfd.py @@ -22,6 +22,7 @@ from vyos.utils.process import process_named_running PROCESS_NAME = 'bfdd' base_path = ['protocols', 'bfd'] +frr_endsection = '^ exit' dum_if = 'dum1001' vrf_name = 'red' @@ -130,7 +131,7 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase): 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 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase): 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 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase): # 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 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase): 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 f676e2a52..980bc6e7f 100755 --- a/smoketest/scripts/cli/test_protocols_static.py +++ b/smoketest/scripts/cli/test_protocols_static.py @@ -33,7 +33,11 @@ routes = { '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 @@ routes = { }, } +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 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase): 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 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase): 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 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase): 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 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase): 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 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase): 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 9fdda236f..000000000 --- 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 633fb797c..adea8fc63 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_mirror_redirect 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_address_configured 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 edbbb00c9..6a035e9e9 100755 --- a/src/conf_mode/interfaces_ethernet.py +++ b/src/conf_mode/interfaces_ethernet.py @@ -20,6 +20,7 @@ from sys import exit 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_vrf 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 aef9b96c4..e6b6d474a 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 06fd9b9b6..f9d5e45a0 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 d94ec6a0d..a0d7fdfb5 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 e5c46aee6..989cf9b5c 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -19,92 +19,35 @@ from sys import argv 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 4393f3ed3..000000000 --- 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 134662f85..1174b1238 100755 --- a/src/conf_mode/service_snmp.py +++ b/src/conf_mode/service_snmp.py @@ -34,7 +34,6 @@ from vyos.utils.process import call 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 3d3845fdf..a13bb8b1e 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -19,13 +19,14 @@ from jmespath import search 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 cmd 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 -- cgit v1.2.3 From 779f311e7fe81e3c85de28f13e4e12e33b255483 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sun, 8 Dec 2024 16:33:45 +0100 Subject: 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(-) (limited to 'src/conf_mode/protocols_segment-routing.py') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index c7384f71d..baffd94dd 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -1125,6 +1125,12 @@ def get_frrender_dict(conf) -> dict: 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 2069930a9..e02094bbb 100644 --- a/python/vyos/frrender.py +++ b/python/vyos/frrender.py @@ -136,7 +136,7 @@ class FRRender: 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 adea8fc63..5f839b33c 100755 --- a/src/conf_mode/interfaces_bonding.py +++ b/src/conf_mode/interfaces_bonding.py @@ -45,7 +45,6 @@ from vyos.configdep import set_dependents, call_dependents 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 6a035e9e9..accfb6b8e 100755 --- a/src/conf_mode/interfaces_ethernet.py +++ b/src/conf_mode/interfaces_ethernet.py @@ -44,7 +44,6 @@ from vyos.utils.dict import dict_delete 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 e6b6d474a..2122cb032 100755 --- a/src/conf_mode/policy.py +++ b/src/conf_mode/policy.py @@ -26,8 +26,6 @@ from vyos import ConfigError 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 f9d5e45a0..f458493c2 100755 --- a/src/conf_mode/protocols_babel.py +++ b/src/conf_mode/protocols_babel.py @@ -27,8 +27,6 @@ from vyos import ConfigError 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 a0d7fdfb5..d8b19fa0e 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -25,8 +25,6 @@ from vyos import ConfigError 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 989cf9b5c..2a4bcbadf 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -35,8 +35,6 @@ from vyos import ConfigError 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 5d60e6bfd..4f56d2b94 100755 --- a/src/conf_mode/protocols_eigrp.py +++ b/src/conf_mode/protocols_eigrp.py @@ -25,7 +25,6 @@ from vyos.frrender import FRRender 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 e812770bc..9e494ecc8 100755 --- a/src/conf_mode/protocols_isis.py +++ b/src/conf_mode/protocols_isis.py @@ -30,8 +30,6 @@ from vyos import ConfigError 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 2a691f4a4..12899f0b2 100755 --- a/src/conf_mode/protocols_mpls.py +++ b/src/conf_mode/protocols_mpls.py @@ -31,8 +31,6 @@ from vyos import ConfigError 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 3dc06ee68..9fdcf4b50 100644 --- a/src/conf_mode/protocols_openfabric.py +++ b/src/conf_mode/protocols_openfabric.py @@ -24,9 +24,7 @@ from vyos.configverify import has_frr_protocol_in_dict 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 32ad5e497..07e6a5860 100755 --- a/src/conf_mode/protocols_ospf.py +++ b/src/conf_mode/protocols_ospf.py @@ -30,7 +30,6 @@ from vyos.utils.network import get_interface_config 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 038fcd2b4..9af85cabf 100755 --- a/src/conf_mode/protocols_ospfv3.py +++ b/src/conf_mode/protocols_ospfv3.py @@ -31,8 +31,6 @@ from vyos import ConfigError 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 1ff7203b2..df0e82d69 100755 --- a/src/conf_mode/protocols_pim.py +++ b/src/conf_mode/protocols_pim.py @@ -32,7 +32,6 @@ from vyos.utils.process import call 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 b3a4099d2..a5d612814 100755 --- a/src/conf_mode/protocols_pim6.py +++ b/src/conf_mode/protocols_pim6.py @@ -26,7 +26,6 @@ from vyos.frrender import FRRender 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 3862530a2..7eb060504 100755 --- a/src/conf_mode/protocols_rip.py +++ b/src/conf_mode/protocols_rip.py @@ -28,8 +28,6 @@ from vyos import ConfigError 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 327a0d239..5884e61f9 100755 --- a/src/conf_mode/protocols_ripng.py +++ b/src/conf_mode/protocols_ripng.py @@ -28,8 +28,6 @@ from vyos import ConfigError 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 c75f95860..33696a742 100755 --- a/src/conf_mode/protocols_rpki.py +++ b/src/conf_mode/protocols_rpki.py @@ -30,7 +30,6 @@ from vyos.utils.file import write_file 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 0fad968e1..a776d1038 100755 --- a/src/conf_mode/protocols_segment-routing.py +++ b/src/conf_mode/protocols_segment-routing.py @@ -27,7 +27,6 @@ from vyos.utils.system import sysctl_write 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 1e498b256..69500377c 100755 --- a/src/conf_mode/protocols_static.py +++ b/src/conf_mode/protocols_static.py @@ -28,7 +28,6 @@ from vyos.template import render 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 a13bb8b1e..6eea9af4d 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -38,7 +38,6 @@ from vyos.utils.system import sysctl_write 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 d977ba2cb..21d91005a 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}') -- cgit v1.2.3 From 55683a8406e17408021437cb35b57c48bd8b2ab1 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Wed, 11 Dec 2024 20:14:45 +0100 Subject: 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(-) (limited to 'src/conf_mode/protocols_segment-routing.py') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index baffd94dd..f5e84267e 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 get_frrender_dict(conf) -> dict: 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 get_frrender_dict(conf) -> dict: 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 4450dc16b..4084425b1 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 e02094bbb..f1bb39094 100644 --- a/python/vyos/frrender.py +++ b/python/vyos/frrender.py @@ -44,7 +44,8 @@ class FRRender: 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 @@ class FRRender: 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 2122cb032..4abb150ac 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 f458493c2..af0751e02 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 d8b19fa0e..623801897 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 2a4bcbadf..db3123bd3 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -35,17 +35,13 @@ from vyos import ConfigError 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 4f56d2b94..33812d350 100755 --- a/src/conf_mode/protocols_eigrp.py +++ b/src/conf_mode/protocols_eigrp.py @@ -26,23 +26,22 @@ from vyos import ConfigError 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 9e494ecc8..7a54685bb 100755 --- a/src/conf_mode/protocols_isis.py +++ b/src/conf_mode/protocols_isis.py @@ -30,65 +30,22 @@ from vyos import ConfigError 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 12899f0b2..bab9648c4 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 9fdcf4b50..48c89d742 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 07e6a5860..9d35aa007 100755 --- a/src/conf_mode/protocols_ospf.py +++ b/src/conf_mode/protocols_ospf.py @@ -31,23 +31,22 @@ from vyos import ConfigError 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 9af85cabf..c6b042b54 100755 --- a/src/conf_mode/protocols_ospfv3.py +++ b/src/conf_mode/protocols_ospfv3.py @@ -31,23 +31,22 @@ from vyos import ConfigError 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 df0e82d69..c40b9d86a 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 a5d612814..ff8fa38c3 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 7eb060504..38108c9f5 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 5884e61f9..46f3ce014 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 33696a742..d3f515feb 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 a776d1038..cc42e5ac7 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 69500377c..29fa530f4 100755 --- a/src/conf_mode/protocols_static.py +++ b/src/conf_mode/protocols_static.py @@ -29,10 +29,6 @@ from vyos import ConfigError 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 21d91005a..ecad85801 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: -- cgit v1.2.3 From 7d99257902c2d638dbf9a8a095660d6aa0d92e38 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Thu, 12 Dec 2024 21:25:42 +0100 Subject: 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(-) (limited to 'src/conf_mode/protocols_segment-routing.py') diff --git a/src/conf_mode/interfaces_bonding.py b/src/conf_mode/interfaces_bonding.py index 5f839b33c..0844d2913 100755 --- a/src/conf_mode/interfaces_bonding.py +++ b/src/conf_mode/interfaces_bonding.py @@ -39,9 +39,11 @@ from vyos.utils.assertion import assert_mac 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 accfb6b8e..5024e6982 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_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.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 @@ if __name__ == '__main__': 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 4abb150ac..5e71a612d 100755 --- a/src/conf_mode/policy.py +++ b/src/conf_mode/policy.py @@ -22,6 +22,7 @@ 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.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 af0751e02..48b7ae734 100755 --- a/src/conf_mode/protocols_babel.py +++ b/src/conf_mode/protocols_babel.py @@ -23,6 +23,7 @@ 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.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 623801897..2e7d40676 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -21,6 +21,7 @@ from vyos.configverify import has_frr_protocol_in_dict 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 db3123bd3..ae32dd839 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -30,6 +30,7 @@ from vyos.template import is_interface 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 33812d350..8f49bb151 100755 --- a/src/conf_mode/protocols_eigrp.py +++ b/src/conf_mode/protocols_eigrp.py @@ -21,6 +21,7 @@ from vyos.config import Config 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 7a54685bb..1e5f0d6e8 100755 --- a/src/conf_mode/protocols_isis.py +++ b/src/conf_mode/protocols_isis.py @@ -26,6 +26,7 @@ from vyos.frrender import FRRender 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 bab9648c4..e8097b7ff 100755 --- a/src/conf_mode/protocols_mpls.py +++ b/src/conf_mode/protocols_mpls.py @@ -25,6 +25,7 @@ from vyos.configverify import has_frr_protocol_in_dict 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 48c89d742..41c5d9544 100644 --- a/src/conf_mode/protocols_openfabric.py +++ b/src/conf_mode/protocols_openfabric.py @@ -21,6 +21,7 @@ from vyos.config import Config 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 9d35aa007..f2c95a63c 100755 --- a/src/conf_mode/protocols_ospf.py +++ b/src/conf_mode/protocols_ospf.py @@ -27,6 +27,7 @@ from vyos.configverify import has_frr_protocol_in_dict 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 c6b042b54..ac189c378 100755 --- a/src/conf_mode/protocols_ospfv3.py +++ b/src/conf_mode/protocols_ospfv3.py @@ -27,6 +27,7 @@ from vyos.frrender import FRRender 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 c40b9d86a..477895b0b 100755 --- a/src/conf_mode/protocols_pim.py +++ b/src/conf_mode/protocols_pim.py @@ -27,6 +27,7 @@ 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 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 ff8fa38c3..3a9b876cc 100755 --- a/src/conf_mode/protocols_pim6.py +++ b/src/conf_mode/protocols_pim6.py @@ -22,6 +22,7 @@ from vyos.config import Config 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 38108c9f5..39743f965 100755 --- a/src/conf_mode/protocols_rip.py +++ b/src/conf_mode/protocols_rip.py @@ -24,6 +24,7 @@ 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.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 46f3ce014..14f038444 100755 --- a/src/conf_mode/protocols_ripng.py +++ b/src/conf_mode/protocols_ripng.py @@ -24,6 +24,7 @@ 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.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 4aefbe36c..5ad656586 100755 --- a/src/conf_mode/protocols_rpki.py +++ b/src/conf_mode/protocols_rpki.py @@ -26,8 +26,8 @@ from vyos.frrender import FRRender 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 cc42e5ac7..99cf87556 100755 --- a/src/conf_mode/protocols_segment-routing.py +++ b/src/conf_mode/protocols_segment-routing.py @@ -23,6 +23,7 @@ from vyos.configverify import has_frr_protocol_in_dict 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 29fa530f4..9d02db6dd 100755 --- a/src/conf_mode/protocols_static.py +++ b/src/conf_mode/protocols_static.py @@ -24,6 +24,7 @@ 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.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 374e6e611..86843eb78 100755 --- a/src/conf_mode/system_ip.py +++ b/src/conf_mode/system_ip.py @@ -25,6 +25,7 @@ from vyos.configverify import verify_route_map 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 02c9a8201..593b8f7f3 100755 --- a/src/conf_mode/system_ipv6.py +++ b/src/conf_mode/system_ipv6.py @@ -27,6 +27,7 @@ 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.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 1b19c55d2..6533f493f 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -32,6 +32,7 @@ from vyos.utils.network import get_vrf_members 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 -- cgit v1.2.3