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/interfaces_bonding.py | 5 ++--- src/conf_mode/interfaces_ethernet.py | 7 ++----- src/conf_mode/policy.py | 11 ++++------- src/conf_mode/protocols_babel.py | 6 ++---- src/conf_mode/protocols_bfd.py | 6 ++---- src/conf_mode/protocols_bgp.py | 6 ++---- src/conf_mode/protocols_eigrp.py | 8 +++----- src/conf_mode/protocols_isis.py | 6 ++---- src/conf_mode/protocols_mpls.py | 6 ++---- src/conf_mode/protocols_openfabric.py | 6 ++---- src/conf_mode/protocols_ospf.py | 6 ++---- src/conf_mode/protocols_ospfv3.py | 6 ++---- src/conf_mode/protocols_pim.py | 7 +++---- src/conf_mode/protocols_pim6.py | 7 ++----- src/conf_mode/protocols_rip.py | 12 +++--------- src/conf_mode/protocols_ripng.py | 13 ++----------- src/conf_mode/protocols_rpki.py | 6 ++---- src/conf_mode/protocols_segment-routing.py | 6 ++---- src/conf_mode/protocols_static.py | 6 ++---- src/conf_mode/protocols_static_multicast.py | 6 ++---- src/conf_mode/service_snmp.py | 4 +++- src/conf_mode/system_ip.py | 5 ++--- src/conf_mode/system_ipv6.py | 9 +++------ src/conf_mode/vrf.py | 8 ++------ 24 files changed, 55 insertions(+), 113 deletions(-) (limited to 'src') diff --git a/src/conf_mode/interfaces_bonding.py b/src/conf_mode/interfaces_bonding.py index bbbfb0385..633fb797c 100755 --- a/src/conf_mode/interfaces_bonding.py +++ b/src/conf_mode/interfaces_bonding.py @@ -281,16 +281,15 @@ def apply(bond): raise ConfigError('Error in updating ethernet interface ' 'after deleting it from bond') - zebra_daemon = 'zebra' # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() # The route-map used for the FIB (zebra) is part of the zebra daemon - frr_cfg.load_configuration(zebra_daemon) + frr_cfg.load_configuration(frr.mgmt_daemon) frr_cfg.modify_section(f'^interface {ifname}', stop_pattern='^exit', remove_stop_mark=True) if 'frr_zebra_config' in bond: frr_cfg.add_before(frr.default_add_before, bond['frr_zebra_config']) - frr_cfg.commit_configuration(zebra_daemon) + frr_cfg.commit_configuration() return None diff --git a/src/conf_mode/interfaces_ethernet.py b/src/conf_mode/interfaces_ethernet.py index 34ce7bc47..edbbb00c9 100755 --- a/src/conf_mode/interfaces_ethernet.py +++ b/src/conf_mode/interfaces_ethernet.py @@ -337,16 +337,13 @@ def apply(ethernet): else: e.update(ethernet) - zebra_daemon = 'zebra' # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() - - # The route-map used for the FIB (zebra) is part of the zebra daemon - frr_cfg.load_configuration(zebra_daemon) + frr_cfg.load_configuration(frr.mgmt_daemon) frr_cfg.modify_section(f'^interface {ifname}', stop_pattern='^exit', remove_stop_mark=True) if 'frr_zebra_config' in ethernet: frr_cfg.add_before(frr.default_add_before, ethernet['frr_zebra_config']) - frr_cfg.commit_configuration(zebra_daemon) + frr_cfg.commit_configuration() if __name__ == '__main__': try: diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py index a5963e72c..aef9b96c4 100755 --- a/src/conf_mode/policy.py +++ b/src/conf_mode/policy.py @@ -279,14 +279,11 @@ def generate(policy): def apply(policy): - bgp_daemon = 'bgpd' - zebra_daemon = 'zebra' - # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() # The route-map used for the FIB (zebra) is part of the zebra daemon - frr_cfg.load_configuration(bgp_daemon) + frr_cfg.load_configuration(frr.bgp_daemon) frr_cfg.modify_section(r'^bgp as-path access-list .*') frr_cfg.modify_section(r'^bgp community-list .*') frr_cfg.modify_section(r'^bgp extcommunity-list .*') @@ -295,10 +292,10 @@ def apply(policy): remove_stop_mark=True) if 'new_frr_config' in policy: frr_cfg.add_before(frr.default_add_before, policy['new_frr_config']) - frr_cfg.commit_configuration(bgp_daemon) + frr_cfg.commit_configuration(frr.bgp_daemon) # The route-map used for the FIB (zebra) is part of the zebra daemon - frr_cfg.load_configuration(zebra_daemon) + frr_cfg.load_configuration(frr.zebra_daemon) frr_cfg.modify_section(r'^access-list .*') frr_cfg.modify_section(r'^ipv6 access-list .*') frr_cfg.modify_section(r'^ip prefix-list .*') @@ -307,7 +304,7 @@ def apply(policy): remove_stop_mark=True) if 'new_frr_config' in policy: frr_cfg.add_before(frr.default_add_before, policy['new_frr_config']) - frr_cfg.commit_configuration(zebra_daemon) + frr_cfg.commit_configuration(frr.zebra_daemon) return None diff --git a/src/conf_mode/protocols_babel.py b/src/conf_mode/protocols_babel.py index 90b6e4a31..06fd9b9b6 100755 --- a/src/conf_mode/protocols_babel.py +++ b/src/conf_mode/protocols_babel.py @@ -128,12 +128,10 @@ def generate(babel): return None def apply(babel): - babel_daemon = 'babeld' - # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(babel_daemon) + frr_cfg.load_configuration(frr.babel_daemon) frr_cfg.modify_section('^router babel', stop_pattern='^exit', remove_stop_mark=True) for key in ['interface', 'interface_removed']: @@ -144,7 +142,7 @@ def apply(babel): if 'new_frr_config' in babel: frr_cfg.add_before(frr.default_add_before, babel['new_frr_config']) - frr_cfg.commit_configuration(babel_daemon) + frr_cfg.commit_configuration(frr.babel_daemon) return None diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index 1361bb1a9..d94ec6a0d 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -89,15 +89,13 @@ def generate(bfd): bfd['new_frr_config'] = render_to_string('frr/bfdd.frr.j2', bfd) def apply(bfd): - bfd_daemon = 'bfdd' - # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(bfd_daemon) + frr_cfg.load_configuration(frr.bfd_daemon) frr_cfg.modify_section('^bfd', stop_pattern='^exit', remove_stop_mark=True) if 'new_frr_config' in bfd: frr_cfg.add_before(frr.default_add_before, bfd['new_frr_config']) - frr_cfg.commit_configuration(bfd_daemon) + frr_cfg.commit_configuration(frr.bfd_daemon) return None diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index 22f020099..e5c46aee6 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -617,8 +617,6 @@ def apply(bgp): if {'vrf', 'vni'} <= set(bgp): call('vtysh -c "conf t" -c "vrf {vrf}" -c "no vni {vni}"'.format(**bgp)) - bgp_daemon = 'bgpd' - # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() @@ -628,7 +626,7 @@ def apply(bgp): if 'vrf' in bgp: vrf = ' vrf ' + bgp['vrf'] - frr_cfg.load_configuration(bgp_daemon) + frr_cfg.load_configuration(frr.bgp_daemon) # Remove interface specific config for key in ['interface', 'interface_removed']: @@ -640,7 +638,7 @@ def apply(bgp): frr_cfg.modify_section(f'^router bgp \d+{vrf}', stop_pattern='^exit', remove_stop_mark=True) if 'frr_bgpd_config' in bgp: frr_cfg.add_before(frr.default_add_before, bgp['frr_bgpd_config']) - frr_cfg.commit_configuration(bgp_daemon) + frr_cfg.commit_configuration(frr.bgp_daemon) return None diff --git a/src/conf_mode/protocols_eigrp.py b/src/conf_mode/protocols_eigrp.py index c13e52a3d..a948b47da 100755 --- a/src/conf_mode/protocols_eigrp.py +++ b/src/conf_mode/protocols_eigrp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022 VyOS maintainers and contributors +# Copyright (C) 2022-2024 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -89,8 +89,6 @@ def generate(eigrp): eigrp['frr_eigrpd_config'] = render_to_string('frr/eigrpd.frr.j2', eigrp) def apply(eigrp): - eigrp_daemon = 'eigrpd' - # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() @@ -100,11 +98,11 @@ def apply(eigrp): if 'vrf' in eigrp: vrf = ' vrf ' + eigrp['vrf'] - frr_cfg.load_configuration(eigrp_daemon) + frr_cfg.load_configuration(frr.eigrp_daemon) frr_cfg.modify_section(f'^router eigrp \d+{vrf}', stop_pattern='^exit', remove_stop_mark=True) if 'frr_eigrpd_config' in eigrp: frr_cfg.add_before(frr.default_add_before, eigrp['frr_eigrpd_config']) - frr_cfg.commit_configuration(eigrp_daemon) + frr_cfg.commit_configuration(frr.eigrp_daemon) return None diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py index ba2f3cf0d..9b70c7329 100755 --- a/src/conf_mode/protocols_isis.py +++ b/src/conf_mode/protocols_isis.py @@ -274,8 +274,6 @@ def generate(isis): return None def apply(isis): - isis_daemon = 'isisd' - # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() @@ -285,7 +283,7 @@ def apply(isis): if 'vrf' in isis: vrf = ' vrf ' + isis['vrf'] - frr_cfg.load_configuration(isis_daemon) + frr_cfg.load_configuration(frr.isis_daemon) frr_cfg.modify_section(f'^router isis VyOS{vrf}', stop_pattern='^exit', remove_stop_mark=True) for key in ['interface', 'interface_removed']: @@ -297,7 +295,7 @@ def apply(isis): if 'frr_isisd_config' in isis: frr_cfg.add_before(frr.default_add_before, isis['frr_isisd_config']) - frr_cfg.commit_configuration(isis_daemon) + frr_cfg.commit_configuration(frr.isis_daemon) return None diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py index ad164db9f..4a05b8044 100755 --- a/src/conf_mode/protocols_mpls.py +++ b/src/conf_mode/protocols_mpls.py @@ -77,17 +77,15 @@ def generate(mpls): return None def apply(mpls): - ldpd_damon = 'ldpd' - # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(ldpd_damon) + frr_cfg.load_configuration(frr.ldpd_daemon) frr_cfg.modify_section(f'^mpls ldp', stop_pattern='^exit', remove_stop_mark=True) if 'frr_ldpd_config' in mpls: frr_cfg.add_before(frr.default_add_before, mpls['frr_ldpd_config']) - frr_cfg.commit_configuration(ldpd_damon) + frr_cfg.commit_configuration(frr.ldpd_daemon) # Set number of entries in the platform label tables labels = '0' diff --git a/src/conf_mode/protocols_openfabric.py b/src/conf_mode/protocols_openfabric.py index 8e8c50c06..b60117a02 100644 --- a/src/conf_mode/protocols_openfabric.py +++ b/src/conf_mode/protocols_openfabric.py @@ -115,12 +115,10 @@ def generate(openfabric): return None def apply(openfabric): - openfabric_daemon = 'fabricd' - # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(openfabric_daemon) + frr_cfg.load_configuration(frr.openfabric_daemon) for domain in openfabric['domains_all']: frr_cfg.modify_section(f'^router openfabric {domain}', stop_pattern='^exit', remove_stop_mark=True) @@ -130,7 +128,7 @@ def apply(openfabric): if 'frr_fabricd_config' in openfabric: frr_cfg.add_before(frr.default_add_before, openfabric['frr_fabricd_config']) - frr_cfg.commit_configuration(openfabric_daemon) + frr_cfg.commit_configuration(frr.openfabric_daemon) return None diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py index 7347c4faa..44817b9b7 100755 --- a/src/conf_mode/protocols_ospf.py +++ b/src/conf_mode/protocols_ospf.py @@ -252,8 +252,6 @@ def generate(ospf): return None def apply(ospf): - ospf_daemon = 'ospfd' - # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() @@ -263,7 +261,7 @@ def apply(ospf): if 'vrf' in ospf: vrf = ' vrf ' + ospf['vrf'] - frr_cfg.load_configuration(ospf_daemon) + frr_cfg.load_configuration(frr.ospf_daemon) frr_cfg.modify_section(f'^router ospf{vrf}', stop_pattern='^exit', remove_stop_mark=True) for key in ['interface', 'interface_removed']: @@ -275,7 +273,7 @@ def apply(ospf): if 'frr_ospfd_config' in ospf: frr_cfg.add_before(frr.default_add_before, ospf['frr_ospfd_config']) - frr_cfg.commit_configuration(ospf_daemon) + frr_cfg.commit_configuration(frr.ospf_daemon) return None diff --git a/src/conf_mode/protocols_ospfv3.py b/src/conf_mode/protocols_ospfv3.py index 60c2a9b16..7bdab3017 100755 --- a/src/conf_mode/protocols_ospfv3.py +++ b/src/conf_mode/protocols_ospfv3.py @@ -153,8 +153,6 @@ def generate(ospfv3): return None def apply(ospfv3): - ospf6_daemon = 'ospf6d' - # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() @@ -164,7 +162,7 @@ def apply(ospfv3): if 'vrf' in ospfv3: vrf = ' vrf ' + ospfv3['vrf'] - frr_cfg.load_configuration(ospf6_daemon) + frr_cfg.load_configuration(frr.ospf6_daemon) frr_cfg.modify_section(f'^router ospf6{vrf}', stop_pattern='^exit', remove_stop_mark=True) for key in ['interface', 'interface_removed']: @@ -176,7 +174,7 @@ def apply(ospfv3): if 'new_frr_config' in ospfv3: frr_cfg.add_before(frr.default_add_before, ospfv3['new_frr_config']) - frr_cfg.commit_configuration(ospf6_daemon) + frr_cfg.commit_configuration(frr.ospf6_daemon) return None diff --git a/src/conf_mode/protocols_pim.py b/src/conf_mode/protocols_pim.py index 79294a1f0..9ef734eff 100755 --- a/src/conf_mode/protocols_pim.py +++ b/src/conf_mode/protocols_pim.py @@ -131,8 +131,7 @@ def generate(pim): return None def apply(pim): - pim_daemon = 'pimd' - pim_pid = process_named_running(pim_daemon) + pim_pid = process_named_running(frr.pim_daemon) if not pim or 'deleted' in pim: if 'deleted' in pim: @@ -146,7 +145,7 @@ def apply(pim): # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(pim_daemon) + frr_cfg.load_configuration(frr.pim_daemon) frr_cfg.modify_section(f'^ip pim') frr_cfg.modify_section(f'^ip igmp') @@ -158,7 +157,7 @@ def apply(pim): if 'frr_pimd_config' in pim: frr_cfg.add_before(frr.default_add_before, pim['frr_pimd_config']) - frr_cfg.commit_configuration(pim_daemon) + frr_cfg.commit_configuration(frr.pim_daemon) return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_pim6.py b/src/conf_mode/protocols_pim6.py index 581ffe238..1abc256fe 100755 --- a/src/conf_mode/protocols_pim6.py +++ b/src/conf_mode/protocols_pim6.py @@ -104,12 +104,9 @@ def apply(pim6): if pim6 is None: return - pim6_daemon = 'pim6d' - # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() - - frr_cfg.load_configuration(pim6_daemon) + frr_cfg.load_configuration(frr.pim6_daemon) for key in ['interface', 'interface_removed']: if key not in pim6: @@ -119,7 +116,7 @@ def apply(pim6): if 'new_frr_config' in pim6: frr_cfg.add_before(frr.default_add_before, pim6['new_frr_config']) - frr_cfg.commit_configuration(pim6_daemon) + frr_cfg.commit_configuration(frr.pim6_daemon) return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_rip.py b/src/conf_mode/protocols_rip.py index 9afac544d..994007137 100755 --- a/src/conf_mode/protocols_rip.py +++ b/src/conf_mode/protocols_rip.py @@ -101,18 +101,12 @@ def generate(rip): return None def apply(rip): - rip_daemon = 'ripd' - zebra_daemon = 'zebra' - # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() - # The route-map used for the FIB (zebra) is part of the zebra daemon - frr_cfg.load_configuration(zebra_daemon) + # The route-map used for the FIB (zebra) is part of the mgmt daemon as of FRR 10.1 + frr_cfg.load_configuration(frr.mgmt_daemon) frr_cfg.modify_section('^ip protocol rip route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') - frr_cfg.commit_configuration(zebra_daemon) - - frr_cfg.load_configuration(rip_daemon) frr_cfg.modify_section('^key chain \S+', stop_pattern='^exit', remove_stop_mark=True) frr_cfg.modify_section('^router rip', stop_pattern='^exit', remove_stop_mark=True) @@ -124,7 +118,7 @@ def apply(rip): if 'new_frr_config' in rip: frr_cfg.add_before(frr.default_add_before, rip['new_frr_config']) - frr_cfg.commit_configuration(rip_daemon) + frr_cfg.commit_configuration() return None diff --git a/src/conf_mode/protocols_ripng.py b/src/conf_mode/protocols_ripng.py index 23416ff96..9d4447d1f 100755 --- a/src/conf_mode/protocols_ripng.py +++ b/src/conf_mode/protocols_ripng.py @@ -85,31 +85,22 @@ def verify(ripng): def generate(ripng): if not ripng: - ripng['new_frr_config'] = '' return None - ripng['new_frr_config'] = render_to_string('frr/ripngd.frr.j2', ripng) - return None def apply(ripng): - ripng_daemon = 'ripngd' - zebra_daemon = 'zebra' - # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() # The route-map used for the FIB (zebra) is part of the zebra daemon - frr_cfg.load_configuration(zebra_daemon) + frr_cfg.load_configuration(frr.mgmt_daemon) frr_cfg.modify_section('^ipv6 protocol ripng route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') - frr_cfg.commit_configuration(zebra_daemon) - - frr_cfg.load_configuration(ripng_daemon) frr_cfg.modify_section('key chain \S+', stop_pattern='^exit', remove_stop_mark=True) frr_cfg.modify_section('interface \S+', stop_pattern='^exit', remove_stop_mark=True) frr_cfg.modify_section('^router ripng', stop_pattern='^exit', remove_stop_mark=True) if 'new_frr_config' in ripng: frr_cfg.add_before(frr.default_add_before, ripng['new_frr_config']) - frr_cfg.commit_configuration(ripng_daemon) + frr_cfg.commit_configuration() return None diff --git a/src/conf_mode/protocols_rpki.py b/src/conf_mode/protocols_rpki.py index a59ecf3e4..bec0cda91 100755 --- a/src/conf_mode/protocols_rpki.py +++ b/src/conf_mode/protocols_rpki.py @@ -107,16 +107,14 @@ def generate(rpki): return None def apply(rpki): - bgp_daemon = 'bgpd' - # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(bgp_daemon) + frr_cfg.load_configuration(frr.bgp_daemon) frr_cfg.modify_section('^rpki', stop_pattern='^exit', remove_stop_mark=True) if 'new_frr_config' in rpki: frr_cfg.add_before(frr.default_add_before, rpki['new_frr_config']) - frr_cfg.commit_configuration(bgp_daemon) + frr_cfg.commit_configuration(frr.bgp_daemon) return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_segment-routing.py b/src/conf_mode/protocols_segment-routing.py index 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 diff --git a/src/conf_mode/protocols_static.py b/src/conf_mode/protocols_static.py index 430cc69d4..c5dc77fd2 100755 --- a/src/conf_mode/protocols_static.py +++ b/src/conf_mode/protocols_static.py @@ -102,11 +102,9 @@ def generate(static): return None def apply(static): - static_daemon = 'staticd' - # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(static_daemon) + frr_cfg.load_configuration(frr.mgmt_daemon) if 'vrf' in static: vrf = static['vrf'] @@ -117,7 +115,7 @@ def apply(static): if 'new_frr_config' in static: frr_cfg.add_before(frr.default_add_before, static['new_frr_config']) - frr_cfg.commit_configuration(static_daemon) + frr_cfg.commit_configuration() return None diff --git a/src/conf_mode/protocols_static_multicast.py b/src/conf_mode/protocols_static_multicast.py index c8894fd41..4393f3ed3 100755 --- a/src/conf_mode/protocols_static_multicast.py +++ b/src/conf_mode/protocols_static_multicast.py @@ -103,10 +103,9 @@ def generate(mroute): def apply(mroute): if mroute is None: return None - static_daemon = 'staticd' frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(static_daemon) + frr_cfg.load_configuration(frr.mgmt_daemon) if 'old_mroute' in mroute: for route_gr in mroute['old_mroute']: @@ -119,8 +118,7 @@ def apply(mroute): if 'new_frr_config' in mroute: frr_cfg.add_before(frr.default_add_before, mroute['new_frr_config']) - frr_cfg.commit_configuration(static_daemon) - + frr_cfg.commit_configuration() return None diff --git a/src/conf_mode/service_snmp.py b/src/conf_mode/service_snmp.py index c9c0ed9a0..134662f85 100755 --- a/src/conf_mode/service_snmp.py +++ b/src/conf_mode/service_snmp.py @@ -34,6 +34,7 @@ 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() @@ -265,7 +266,8 @@ def apply(snmp): # This should be done for each daemon individually because common command # works only if all the daemons started with SNMP support # Following daemons from FRR 9.0/stable have SNMP module compiled in VyOS - frr_daemons_list = ['zebra', 'bgpd', 'ospf6d', 'ospfd', 'ripd', 'isisd', 'ldpd'] + frr_daemons_list = [frr.zebra_daemon, frr.bgp_daemon, frr.ospf_daemon, frr.ospf6_daemon, + frr.rip_daemon, frr.isis_daemon, frr.ldpd_daemon] for frr_daemon in frr_daemons_list: call(f'vtysh -c "configure terminal" -d {frr_daemon} -c "agentx" >/dev/null') diff --git a/src/conf_mode/system_ip.py b/src/conf_mode/system_ip.py index c8a91fd2f..5afb57404 100755 --- a/src/conf_mode/system_ip.py +++ b/src/conf_mode/system_ip.py @@ -121,17 +121,16 @@ def apply(opt): # running when this script is called first. Skip this part and wait for initial # commit of the configuration to trigger this statement if is_systemd_service_active('frr.service'): - zebra_daemon = 'zebra' # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() # The route-map used for the FIB (zebra) is part of the zebra daemon - frr_cfg.load_configuration(zebra_daemon) + frr_cfg.load_configuration(frr.mgmt_daemon) frr_cfg.modify_section(r'no ip nht resolve-via-default') frr_cfg.modify_section(r'ip protocol \w+ route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') if 'frr_zebra_config' in opt: frr_cfg.add_before(frr.default_add_before, opt['frr_zebra_config']) - frr_cfg.commit_configuration(zebra_daemon) + frr_cfg.commit_configuration() call_dependents() diff --git a/src/conf_mode/system_ipv6.py b/src/conf_mode/system_ipv6.py index a2442d009..90d5100d7 100755 --- a/src/conf_mode/system_ipv6.py +++ b/src/conf_mode/system_ipv6.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2023 VyOS maintainers and contributors +# Copyright (C) 2019-2024 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -105,17 +105,14 @@ def apply(opt): # running when this script is called first. Skip this part and wait for initial # commit of the configuration to trigger this statement if is_systemd_service_active('frr.service'): - zebra_daemon = 'zebra' # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() - - # The route-map used for the FIB (zebra) is part of the zebra daemon - frr_cfg.load_configuration(zebra_daemon) + frr_cfg.load_configuration(frr.mgmt_daemon) frr_cfg.modify_section(r'no ipv6 nht resolve-via-default') frr_cfg.modify_section(r'ipv6 protocol \w+ route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') if 'frr_zebra_config' in opt: frr_cfg.add_before(frr.default_add_before, opt['frr_zebra_config']) - frr_cfg.commit_configuration(zebra_daemon) + frr_cfg.commit_configuration() call_dependents() diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index 72b178c89..6d17c192c 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -339,17 +339,13 @@ def apply(vrf): if has_rule(afi, 2000, 'l3mdev'): call(f'ip {afi} rule del pref 2000 l3mdev unreachable') - # Apply FRR filters - zebra_daemon = 'zebra' # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() - - # The route-map used for the FIB (zebra) is part of the zebra daemon - frr_cfg.load_configuration(zebra_daemon) + frr_cfg.load_configuration(frr.mgmt_daemon) frr_cfg.modify_section(f'^vrf .+', stop_pattern='^exit-vrf', remove_stop_mark=True) if 'frr_zebra_config' in vrf: frr_cfg.add_before(frr.default_add_before, vrf['frr_zebra_config']) - frr_cfg.commit_configuration(zebra_daemon) + frr_cfg.commit_configuration() return None -- cgit v1.2.3 From 8b21fb0aecf46b6da4bac091d380d3c4927100bd Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sun, 6 Oct 2024 15:19:28 +0200 Subject: vrf: T6747: ensure VNIs are unique on the system --- src/conf_mode/vrf.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'src') diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index 6d17c192c..3d3845fdf 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -158,6 +158,7 @@ def verify(vrf): reserved_names = ["add", "all", "broadcast", "default", "delete", "dev", "get", "inet", "mtu", "link", "type", "vrf"] table_ids = [] + vnis = [] for name, vrf_config in vrf['name'].items(): # Reserved VRF names if name in reserved_names: @@ -178,6 +179,13 @@ def verify(vrf): raise ConfigError(f'VRF "{name}" table id is not unique!') table_ids.append(vrf_config['table']) + # VRF VNIs must be unique on the system + if 'vni' in vrf_config: + vni = vrf_config['vni'] + if vni in vnis: + raise ConfigError(f'VRF "{name}" VNI "{vni}" is not unique!') + vnis.append(vni) + tmp = dict_search('ip.protocol', vrf_config) if tmp != None: for protocol, protocol_options in tmp.items(): -- cgit v1.2.3 From 69540c75594a692fd72e3f75b1608628a94366ff Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sun, 8 Dec 2024 08:28:08 +0100 Subject: pim: T6747: adjust template to new "router pim" FRR CLI --- data/templates/frr/pimd.frr.j2 | 36 +++++----- smoketest/scripts/cli/test_protocols_pim.py | 35 +++++----- src/conf_mode/protocols_pim.py | 105 +++++++--------------------- 3 files changed, 62 insertions(+), 114 deletions(-) (limited to 'src') diff --git a/data/templates/frr/pimd.frr.j2 b/data/templates/frr/pimd.frr.j2 index 68edf4a5c..d474d8495 100644 --- a/data/templates/frr/pimd.frr.j2 +++ b/data/templates/frr/pimd.frr.j2 @@ -39,10 +39,10 @@ interface {{ iface }} {% for join, join_config in iface_config.igmp.join.items() %} {% if join_config.source_address is vyos_defined %} {% for source_address in join_config.source_address %} - ip igmp join {{ join }} {{ source_address }} + ip igmp join-group {{ join }} {{ source_address }} {% endfor %} {% else %} - ip igmp join {{ join }} + ip igmp join-group {{ join }} {% endif %} {% endfor %} {% endif %} @@ -51,45 +51,47 @@ exit {% endfor %} {% endif %} ! +{% if igmp.watermark_warning is vyos_defined %} +ip igmp watermark-warn {{ igmp.watermark_warning }} +{% endif %} +! +router pim {% if ecmp is vyos_defined %} -ip pim ecmp {{ 'rebalance' if ecmp.rebalance is vyos_defined }} + ecmp {{ 'rebalance' if ecmp.rebalance is vyos_defined }} {% endif %} {% if join_prune_interval is vyos_defined %} -ip pim join-prune-interval {{ join_prune_interval }} + join-prune-interval {{ join_prune_interval }} {% endif %} {% if keep_alive_timer is vyos_defined %} -ip pim keep-alive-timer {{ keep_alive_timer }} + keep-alive-timer {{ keep_alive_timer }} {% endif %} {% if packets is vyos_defined %} -ip pim packets {{ packets }} + packets {{ packets }} {% endif %} {% if register_accept_list.prefix_list is vyos_defined %} -ip pim register-accept-list {{ register_accept_list.prefix_list }} + register-accept-list {{ register_accept_list.prefix_list }} {% endif %} {% if register_suppress_time is vyos_defined %} -ip pim register-suppress-time {{ register_suppress_time }} + register-suppress-time {{ register_suppress_time }} {% endif %} {% if rp.address is vyos_defined %} {% for address, address_config in rp.address.items() %} {% for group in address_config.group %} -ip pim rp {{ address }} {{ group }} + rp {{ address }} {{ group }} {% endfor %} {% endfor %} {% endif %} {% if rp.keep_alive_timer is vyos_defined %} -ip pim rp keep-alive-timer {{ rp.keep_alive_timer }} + rp keep-alive-timer {{ rp.keep_alive_timer }} {% endif %} {% if no_v6_secondary is vyos_defined %} -no ip pim send-v6-secondary + no send-v6-secondary {% endif %} {% if spt_switchover.infinity_and_beyond is vyos_defined %} -ip pim spt-switchover infinity-and-beyond {{ 'prefix-list ' ~ spt_switchover.infinity_and_beyond.prefix_list if spt_switchover.infinity_and_beyond.prefix_list is defined }} + spt-switchover infinity-and-beyond {{ 'prefix-list ' ~ spt_switchover.infinity_and_beyond.prefix_list if spt_switchover.infinity_and_beyond.prefix_list is defined }} {% endif %} {% if ssm.prefix_list is vyos_defined %} -ip pim ssm prefix-list {{ ssm.prefix_list }} -{% endif %} -! -{% if igmp.watermark_warning is vyos_defined %} -ip igmp watermark-warn {{ igmp.watermark_warning }} + ssm prefix-list {{ ssm.prefix_list }} {% endif %} +exit ! diff --git a/smoketest/scripts/cli/test_protocols_pim.py b/smoketest/scripts/cli/test_protocols_pim.py index ccfced138..a2cc4f1ed 100755 --- a/smoketest/scripts/cli/test_protocols_pim.py +++ b/smoketest/scripts/cli/test_protocols_pim.py @@ -19,10 +19,11 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError +from vyos.frrender import pim_daemon from vyos.ifconfig import Section from vyos.utils.process import process_named_running -PROCESS_NAME = 'pimd' +PROCESS_NAME = pim_daemon base_path = ['protocols', 'pim'] class TestProtocolsPIM(VyOSUnitTestSHIM.TestCase): @@ -57,8 +58,8 @@ class TestProtocolsPIM(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR pimd configuration - frrconfig = self.getFRRconfig(daemon=PROCESS_NAME) - self.assertIn(f'ip pim rp {rp} {group}', frrconfig) + frrconfig = self.getFRRconfig('router pim', endsection='^exit', daemon=PROCESS_NAME) + self.assertIn(f' rp {rp} {group}', frrconfig) for interface in interfaces: frrconfig = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) @@ -108,18 +109,18 @@ class TestProtocolsPIM(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR pimd configuration - frrconfig = self.getFRRconfig(daemon=PROCESS_NAME) - self.assertIn(f'ip pim rp {rp} {group}', frrconfig) - self.assertIn(f'ip pim rp keep-alive-timer {rp_keep_alive_timer}', frrconfig) - self.assertIn(f'ip pim ecmp rebalance', frrconfig) - self.assertIn(f'ip pim join-prune-interval {join_prune_interval}', frrconfig) - self.assertIn(f'ip pim keep-alive-timer {keep_alive_timer}', frrconfig) - self.assertIn(f'ip pim packets {packets}', frrconfig) - self.assertIn(f'ip pim register-accept-list {prefix_list}', frrconfig) - self.assertIn(f'ip pim register-suppress-time {register_suppress_time}', frrconfig) - self.assertIn(f'no ip pim send-v6-secondary', frrconfig) - self.assertIn(f'ip pim spt-switchover infinity-and-beyond prefix-list {prefix_list}', frrconfig) - self.assertIn(f'ip pim ssm prefix-list {prefix_list}', frrconfig) + frrconfig = self.getFRRconfig('router pim', endsection='^exit', daemon=PROCESS_NAME) + self.assertIn(f' no send-v6-secondary', frrconfig) + self.assertIn(f' rp {rp} {group}', frrconfig) + self.assertIn(f' register-suppress-time {register_suppress_time}', frrconfig) + self.assertIn(f' join-prune-interval {join_prune_interval}', frrconfig) + self.assertIn(f' packets {packets}', frrconfig) + self.assertIn(f' keep-alive-timer {keep_alive_timer}', frrconfig) + self.assertIn(f' rp keep-alive-timer {rp_keep_alive_timer}', frrconfig) + self.assertIn(f' ssm prefix-list {prefix_list}', frrconfig) + self.assertIn(f' register-accept-list {prefix_list}', frrconfig) + self.assertIn(f' spt-switchover infinity-and-beyond prefix-list {prefix_list}', frrconfig) + self.assertIn(f' ecmp rebalance', frrconfig) def test_03_pim_igmp_proxy(self): igmp_proxy = ['protocols', 'igmp-proxy'] @@ -184,9 +185,9 @@ class TestProtocolsPIM(VyOSUnitTestSHIM.TestCase): for join, join_config in igmp_join.items(): if 'source' in join_config: for source in join_config['source']: - self.assertIn(f' ip igmp join {join} {source}', frrconfig) + self.assertIn(f' ip igmp join-group {join} {source}', frrconfig) else: - self.assertIn(f' ip igmp join {join}', frrconfig) + self.assertIn(f' ip igmp join-group {join}', frrconfig) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/protocols_pim.py b/src/conf_mode/protocols_pim.py index 9ef734eff..1ff7203b2 100755 --- a/src/conf_mode/protocols_pim.py +++ b/src/conf_mode/protocols_pim.py @@ -22,19 +22,17 @@ from signal import SIGTERM from sys import exit from vyos.config import Config -from vyos.config import config_dict_merge -from vyos.configdict import node_changed +from vyos.configdict import get_frrender_dict from vyos.configverify import verify_interface_exists +from vyos.configverify import has_frr_protocol_in_dict +from vyos.frrender import FRRender +from vyos.frrender import pim_daemon from vyos.utils.process import process_named_running from vyos.utils.process import call -from vyos.template import render_to_string from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() - -RESERVED_MC_NET = '224.0.0.0/24' - +frrender = FRRender() def get_config(config=None): if config: @@ -42,52 +40,15 @@ def get_config(config=None): else: conf = Config() - base = ['protocols', 'pim'] - - pim = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) - - # We can not run both IGMP proxy and PIM at the same time - get IGMP - # proxy status - if conf.exists(['protocols', 'igmp-proxy']): - pim.update({'igmp_proxy_enabled' : {}}) - - # FRR has VRF support for different routing daemons. As interfaces belong - # to VRFs - or the global VRF, we need to check for changed interfaces so - # that they will be properly rendered for the FRR config. Also this eases - # removal of interfaces from the running configuration. - interfaces_removed = node_changed(conf, base + ['interface']) - if interfaces_removed: - pim['interface_removed'] = list(interfaces_removed) - - # Bail out early if configuration tree does no longer exist. this must - # be done after retrieving the list of interfaces to be removed. - if not conf.exists(base): - pim.update({'deleted' : ''}) - return pim - - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = conf.get_config_defaults(**pim.kwargs, recursive=True) - - # We have to cleanup the default dict, as default values could enable features - # which are not explicitly enabled on the CLI. Example: default-information - # originate comes with a default metric-type of 2, which will enable the - # entire default-information originate tree, even when not set via CLI so we - # need to check this first and probably drop that key. - for interface in pim.get('interface', []): - # We need to reload the defaults on every pass b/c of - # hello-multiplier dependency on dead-interval - # If hello-multiplier is set, we need to remove the default from - # dead-interval. - if 'igmp' not in pim['interface'][interface]: - del default_values['interface'][interface]['igmp'] - - pim = config_dict_merge(default_values, pim) - return pim - -def verify(pim): - if not pim or 'deleted' in pim: + return get_frrender_dict(conf) + +def verify(config_dict): + if not has_frr_protocol_in_dict(config_dict, 'pim'): + return None + + pim = config_dict['pim'] + + if 'deleted' in pim: return None if 'igmp_proxy_enabled' in pim: @@ -96,6 +57,7 @@ def verify(pim): if 'interface' not in pim: raise ConfigError('PIM require defined interfaces!') + RESERVED_MC_NET = '224.0.0.0/24' for interface, interface_config in pim['interface'].items(): verify_interface_exists(pim, interface) @@ -124,40 +86,23 @@ def verify(pim): raise ConfigError(f'{pim_base_error} must be unique!') unique.append(gr_addr) -def generate(pim): - if not pim or 'deleted' in pim: - return None - pim['frr_pimd_config'] = render_to_string('frr/pimd.frr.j2', pim) - return None - -def apply(pim): - pim_pid = process_named_running(frr.pim_daemon) +def generate(config_dict): + frrender.generate(config_dict) - if not pim or 'deleted' in pim: - if 'deleted' in pim: - os.kill(int(pim_pid), SIGTERM) +def apply(config_dict): + if not has_frr_protocol_in_dict(config_dict, 'pim'): + return None + pim_pid = process_named_running(pim_daemon) + pim = config_dict['pim'] + if 'deleted' in pim: + os.kill(int(pim_pid), SIGTERM) return None if not pim_pid: call('/usr/lib/frr/pimd -d -F traditional --daemon -A 127.0.0.1') - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - - frr_cfg.load_configuration(frr.pim_daemon) - frr_cfg.modify_section(f'^ip pim') - frr_cfg.modify_section(f'^ip igmp') - - for key in ['interface', 'interface_removed']: - if key not in pim: - continue - for interface in pim[key]: - frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True) - - if 'frr_pimd_config' in pim: - frr_cfg.add_before(frr.default_add_before, pim['frr_pimd_config']) - frr_cfg.commit_configuration(frr.pim_daemon) + frrender.apply() return None if __name__ == '__main__': -- cgit v1.2.3 From 655ecf7715688de3463c258ea5a635dcf00c7150 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sun, 8 Dec 2024 08:28:25 +0100 Subject: pim6: T6747: adjust template to new "router pim6" FRR CLI --- data/templates/frr/pim6d.frr.j2 | 21 +++++---- smoketest/scripts/cli/test_protocols_pim6.py | 14 +++--- src/conf_mode/protocols_pim6.py | 68 ++++++---------------------- 3 files changed, 34 insertions(+), 69 deletions(-) (limited to 'src') diff --git a/data/templates/frr/pim6d.frr.j2 b/data/templates/frr/pim6d.frr.j2 index bac716fcc..d4144a2f9 100644 --- a/data/templates/frr/pim6d.frr.j2 +++ b/data/templates/frr/pim6d.frr.j2 @@ -40,10 +40,10 @@ interface {{ iface }} {% for group, group_config in iface_config.mld.join.items() %} {% if group_config.source is vyos_defined %} {% for source in group_config.source %} - ipv6 mld join {{ group }} {{ source }} + ipv6 mld join-group {{ group }} {{ source }} {% endfor %} {% else %} - ipv6 mld join {{ group }} + ipv6 mld join-group {{ group }} {% endif %} {% endfor %} {% endif %} @@ -52,30 +52,33 @@ exit {% endfor %} {% endif %} ! +router pim6 {% if join_prune_interval is vyos_defined %} -ipv6 pim join-prune-interval {{ join_prune_interval }} + join-prune-interval {{ join_prune_interval }} {% endif %} {% if keep_alive_timer is vyos_defined %} -ipv6 pim keep-alive-timer {{ keep_alive_timer }} + keep-alive-timer {{ keep_alive_timer }} {% endif %} {% if packets is vyos_defined %} -ipv6 pim packets {{ packets }} + packets {{ packets }} {% endif %} {% if register_suppress_time is vyos_defined %} -ipv6 pim register-suppress-time {{ register_suppress_time }} + register-suppress-time {{ register_suppress_time }} {% endif %} {% if rp.address is vyos_defined %} {% for address, address_config in rp.address.items() %} {% if address_config.group is vyos_defined %} {% for group in address_config.group %} -ipv6 pim rp {{ address }} {{ group }} + rp {{ address }} {{ group }} {% endfor %} {% endif %} {% if address_config.prefix_list6 is vyos_defined %} -ipv6 pim rp {{ address }} prefix-list {{ address_config.prefix_list6 }} + rp {{ address }} prefix-list {{ address_config.prefix_list6 }} {% endif %} {% endfor %} {% endif %} {% if rp.keep_alive_timer is vyos_defined %} -ipv6 pim rp keep-alive-timer {{ rp.keep_alive_timer }} + rp keep-alive-timer {{ rp.keep_alive_timer }} {% endif %} +exit +! diff --git a/smoketest/scripts/cli/test_protocols_pim6.py b/smoketest/scripts/cli/test_protocols_pim6.py index ba24edca2..4b9ae6161 100755 --- a/smoketest/scripts/cli/test_protocols_pim6.py +++ b/smoketest/scripts/cli/test_protocols_pim6.py @@ -90,7 +90,7 @@ class TestProtocolsPIMv6(VyOSUnitTestSHIM.TestCase): for interface in interfaces: config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) self.assertIn(f'interface {interface}', config) - self.assertIn(f' ipv6 mld join ff18::1234', config) + self.assertIn(f' ipv6 mld join-group ff18::1234', config) # Join a source-specific multicast group for interface in interfaces: @@ -102,7 +102,7 @@ class TestProtocolsPIMv6(VyOSUnitTestSHIM.TestCase): for interface in interfaces: config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) self.assertIn(f'interface {interface}', config) - self.assertIn(f' ipv6 mld join ff38::5678 2001:db8::5678', config) + self.assertIn(f' ipv6 mld join-group ff38::5678 2001:db8::5678', config) def test_pim6_03_basic(self): interfaces = Section.interfaces('ethernet') @@ -128,11 +128,11 @@ class TestProtocolsPIMv6(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR pim6d configuration - config = self.getFRRconfig(daemon=PROCESS_NAME) - self.assertIn(f'ipv6 pim join-prune-interval {join_prune_interval}', config) - self.assertIn(f'ipv6 pim keep-alive-timer {keep_alive_timer}', config) - self.assertIn(f'ipv6 pim packets {packets}', config) - self.assertIn(f'ipv6 pim register-suppress-time {register_suppress_time}', config) + config = self.getFRRconfig('router pim6', endsection='^exit', daemon=PROCESS_NAME) + self.assertIn(f' join-prune-interval {join_prune_interval}', config) + self.assertIn(f' keep-alive-timer {keep_alive_timer}', config) + self.assertIn(f' packets {packets}', config) + self.assertIn(f' register-suppress-time {register_suppress_time}', config) for interface in interfaces: config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) diff --git a/src/conf_mode/protocols_pim6.py b/src/conf_mode/protocols_pim6.py index 1abc256fe..b3a4099d2 100755 --- a/src/conf_mode/protocols_pim6.py +++ b/src/conf_mode/protocols_pim6.py @@ -19,48 +19,29 @@ from ipaddress import IPv6Network from sys import exit from vyos.config import Config -from vyos.config import config_dict_merge -from vyos.configdict import node_changed +from vyos.configdict import get_frrender_dict +from vyos.configverify import has_frr_protocol_in_dict from vyos.configverify import verify_interface_exists -from vyos.template import render_to_string +from vyos.frrender import FRRender from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() +frrender = FRRender() def get_config(config=None): if config: conf = config else: conf = Config() - base = ['protocols', 'pim6'] - pim6 = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, with_recursive_defaults=True) + return get_frrender_dict(conf) - # FRR has VRF support for different routing daemons. As interfaces belong - # to VRFs - or the global VRF, we need to check for changed interfaces so - # that they will be properly rendered for the FRR config. Also this eases - # removal of interfaces from the running configuration. - interfaces_removed = node_changed(conf, base + ['interface']) - if interfaces_removed: - pim6['interface_removed'] = list(interfaces_removed) +def verify(config_dict): + if not has_frr_protocol_in_dict(config_dict, 'pim6'): + return None - # Bail out early if configuration tree does no longer exist. this must - # be done after retrieving the list of interfaces to be removed. - if not conf.exists(base): - pim6.update({'deleted' : ''}) - return pim6 - - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = conf.get_config_defaults(**pim6.kwargs, recursive=True) - - pim6 = config_dict_merge(default_values, pim6) - return pim6 - -def verify(pim6): - if not pim6 or 'deleted' in pim6: - return + pim6 = config_dict['pim6'] + if 'deleted' in pim6: + return None for interface, interface_config in pim6.get('interface', {}).items(): verify_interface_exists(pim6, interface) @@ -94,30 +75,11 @@ def verify(pim6): raise ConfigError(f'{pim_base_error} must be unique!') unique.append(gr_addr) -def generate(pim6): - if not pim6 or 'deleted' in pim6: - return - pim6['new_frr_config'] = render_to_string('frr/pim6d.frr.j2', pim6) - return None - -def apply(pim6): - if pim6 is None: - return - - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(frr.pim6_daemon) - - for key in ['interface', 'interface_removed']: - if key not in pim6: - continue - for interface in pim6[key]: - frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True) +def generate(config_dict): + frrender.generate(config_dict) - if 'new_frr_config' in pim6: - frr_cfg.add_before(frr.default_add_before, pim6['new_frr_config']) - frr_cfg.commit_configuration(frr.pim6_daemon) - return None +def apply(config_dict): + frrender.apply() if __name__ == '__main__': try: -- 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') 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 13baad691410009d1e5f7e6f6bf5afe72afd2f73 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sun, 8 Dec 2024 09:11:35 +0100 Subject: multicast: T6746: flatten CLI by merging "multicast route" to "mroute" CLI tagNode This will save an entire level for the configuration and there is no need for a parent "multicast" node, as it will only have "route" as tagNode below. Move set protocols static multicast route to: * set protocols static mroute --- data/templates/frr/staticd.frr.j2 | 8 +-- interface-definitions/protocols_static.xml.in | 79 ++++++++++++-------------- smoketest/scripts/cli/test_protocols_static.py | 4 +- src/tests/test_initial_setup.py | 4 +- 4 files changed, 43 insertions(+), 52 deletions(-) (limited to 'src') diff --git a/data/templates/frr/staticd.frr.j2 b/data/templates/frr/staticd.frr.j2 index c662b7650..227807c62 100644 --- a/data/templates/frr/staticd.frr.j2 +++ b/data/templates/frr/staticd.frr.j2 @@ -128,15 +128,13 @@ exit-vrf {% endif %} ! {# Multicast route #} -{% if multicast is vyos_defined %} +{% if mroute is vyos_defined %} {% set ip_prefix = 'ip m' %} {# IPv4 multicast routing #} -{% if multicast.route is vyos_defined %} -{% for prefix, prefix_config in multicast.route.items() %} +{% for prefix, prefix_config in mroute.items() %} {{ static_routes(ip_prefix, prefix, prefix_config) }} {# j2lint: disable=jinja-statements-delimeter #} -{%- endfor %} -{% endif %} +{%- endfor %} {% endif %} ! {% if route_map is vyos_defined %} diff --git a/interface-definitions/protocols_static.xml.in b/interface-definitions/protocols_static.xml.in index 407e56553..d8e0ee56b 100644 --- a/interface-definitions/protocols_static.xml.in +++ b/interface-definitions/protocols_static.xml.in @@ -11,62 +11,55 @@ 480 - + - Multicast static route + Static IPv4 route for Multicast RIB + + ipv4net + Network + + + + - + + + Next-hop IPv4 router address + + ipv4 + Next-hop router address + + + + + + + #include + #include + + + - Configure static unicast route into MRIB for multicast RPF lookup + Next-hop IPv4 router interface + + + - ipv4net - Network + txt + Gateway interface name - + #include - - - Next-hop IPv4 router address - - ipv4 - Next-hop router address - - - - - - - #include - #include - - - - - Next-hop IPv4 router interface - - - - - txt - Gateway interface name - - - #include - - - - #include - #include - - + #include + #include - + #include #include #include diff --git a/smoketest/scripts/cli/test_protocols_static.py b/smoketest/scripts/cli/test_protocols_static.py index 980bc6e7f..086235086 100755 --- a/smoketest/scripts/cli/test_protocols_static.py +++ b/smoketest/scripts/cli/test_protocols_static.py @@ -528,7 +528,7 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase): def test_04_static_multicast(self): for route, route_config in multicast_routes.items(): if 'next_hop' in route_config: - base = base_path + ['multicast', 'route', route] + base = base_path + ['mroute', route] for next_hop, next_hop_config in route_config['next_hop'].items(): self.cli_set(base + ['next-hop', next_hop]) if 'distance' in next_hop_config: @@ -537,7 +537,7 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase): self.cli_set(base + ['next-hop', next_hop, 'disable']) if 'interface' in route_config: - base = base_path + ['multicast', 'route', route] + base = base_path + ['mroute', route] for next_hop, next_hop_config in route_config['interface'].items(): self.cli_set(base + ['interface', next_hop]) if 'distance' in next_hop_config: diff --git a/src/tests/test_initial_setup.py b/src/tests/test_initial_setup.py index 4cd5fb169..7737f9df5 100644 --- a/src/tests/test_initial_setup.py +++ b/src/tests/test_initial_setup.py @@ -92,8 +92,8 @@ class TestInitialSetup(TestCase): vis.set_default_gateway(self.config, '192.0.2.1') self.assertTrue(self.config.exists(['protocols', 'static', 'route', '0.0.0.0/0', 'next-hop', '192.0.2.1'])) - self.assertTrue(self.xml.is_tag(['protocols', 'static', 'multicast', 'route', '0.0.0.0/0', 'next-hop'])) - self.assertTrue(self.xml.is_tag(['protocols', 'static', 'multicast', 'route'])) + self.assertTrue(self.xml.is_tag(['protocols', 'static', 'mroute', '0.0.0.0/0', 'next-hop'])) + self.assertTrue(self.xml.is_tag(['protocols', 'static', 'mroute'])) if __name__ == "__main__": unittest.main() -- cgit v1.2.3 From 1666e3d99b8de700c880f09ce6ad5ea8bc7f4568 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sun, 8 Dec 2024 15:41:53 +0100 Subject: frr: T6746: add default entry for config debug file A lot of services have dynamic debug capabilities which will be turned on by creating a file in /tmp. These scripts have the path hardcoded and sometimes accross multiple places (bad). This commit introduces vyos.defaults.frr_debug_enable to get the path for the debug file from a single location. --- python/vyos/configdict.py | 10 ++++++---- python/vyos/defaults.py | 1 + python/vyos/frr.py | 3 ++- python/vyos/frrender.py | 4 ++-- src/init/vyos-router | 3 ++- 5 files changed, 13 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 88d131573..c7384f71d 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -19,6 +19,7 @@ A library for retrieving value dicts from VyOS configs in a declarative fashion. import os import json +from vyos.defaults import frr_debug_enable from vyos.utils.dict import dict_search from vyos.utils.process import cmd @@ -1124,8 +1125,9 @@ def get_frrender_dict(conf) -> dict: dict.update({'vrf' : vrf}) - print('======== < > ==========') - import pprint - pprint.pprint(dict) - print('======== < > ==========') + if os.path.exists(frr_debug_enable): + print('======== < BEGIN > ==========') + import pprint + pprint.pprint(dict) + print('========= < END > ===========') return dict diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py index 425990967..9757a34df 100644 --- a/python/vyos/defaults.py +++ b/python/vyos/defaults.py @@ -42,6 +42,7 @@ directories = { config_status = '/tmp/vyos-config-status' api_config_state = '/run/http-api-state' +frr_debug_enable = '/tmp/vyos.frr.debug' cfg_group = 'vyattacfg' diff --git a/python/vyos/frr.py b/python/vyos/frr.py index 183805e13..67279a6f7 100644 --- a/python/vyos/frr.py +++ b/python/vyos/frr.py @@ -69,6 +69,7 @@ import tempfile import re from vyos import ConfigError +from vyos.defaults import frr_debug_enable from vyos.utils.process import cmd from vyos.utils.process import popen from vyos.utils.process import STDOUT @@ -144,7 +145,7 @@ class ConfigSectionNotFound(FrrError): def init_debugging(): global DEBUG - DEBUG = os.path.exists('/tmp/vyos.frr.debug') + DEBUG = os.path.exists(frr_debug_enable) if DEBUG: LOG.setLevel(logging.DEBUG) diff --git a/python/vyos/frrender.py b/python/vyos/frrender.py index 015596a8f..2069930a9 100644 --- a/python/vyos/frrender.py +++ b/python/vyos/frrender.py @@ -19,13 +19,13 @@ Library used to interface with FRRs mgmtd introduced in version 10.0 import os +from vyos.defaults import frr_debug_enable from vyos.utils.file import write_file from vyos.utils.process import rc_cmd from vyos.template import render_to_string from vyos import ConfigError -DEBUG_ON = os.path.exists('/tmp/vyos.frr.debug') -DEBUG_ON = True +DEBUG_ON = os.path.exists(frr_debug_enable) def debug(message): if not DEBUG_ON: diff --git a/src/init/vyos-router b/src/init/vyos-router index e2e964656..00136309b 100755 --- a/src/init/vyos-router +++ b/src/init/vyos-router @@ -474,9 +474,10 @@ start () # enable some debugging before loading the configuration if grep -q vyos-debug /proc/cmdline; then log_action_begin_msg "Enable runtime debugging options" + FRR_DEBUG=$(python3 -c "from vyos.defaults import frr_debug_enable; print(frr_debug_enable)") + touch $FRR_DEBUG touch /tmp/vyos.container.debug touch /tmp/vyos.ifconfig.debug - touch /tmp/vyos.frr.debug touch /tmp/vyos.container.debug touch /tmp/vyos.smoketest.debug fi -- 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') 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') 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 2e4725cd77a33f91e091d86d94056f43dafd153d Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Wed, 11 Dec 2024 21:07:28 +0100 Subject: frr: T6746: handle "system ip" and "system ipv6" with FRRender class FRR 10.2 will use "[no] ip forwarding" and "[no] ipv6 forwarding" to enable or disable IP(v6) forwarding. We no longer rely on sysctl as this was overridden by FRR later on. Remove code path for sysctl setting and solely rely on FRR. --- data/templates/frr/zebra.route-map.frr.j2 | 2 + python/vyos/configdict.py | 9 ++++ python/vyos/frrender.py | 14 ++++++- smoketest/scripts/cli/test_system_ip.py | 55 +++++++++++++----------- smoketest/scripts/cli/test_system_ipv6.py | 57 ++++++++++++++----------- src/conf_mode/system_ip.py | 69 +++++++++++-------------------- src/conf_mode/system_ipv6.py | 65 +++++++++++------------------ 7 files changed, 136 insertions(+), 135 deletions(-) (limited to 'src') diff --git a/data/templates/frr/zebra.route-map.frr.j2 b/data/templates/frr/zebra.route-map.frr.j2 index 669d58354..70a810f43 100644 --- a/data/templates/frr/zebra.route-map.frr.j2 +++ b/data/templates/frr/zebra.route-map.frr.j2 @@ -1,4 +1,6 @@ ! +{{ 'no ' if disable_forwarding is vyos_defined }}{{ afi }} forwarding +! {% if nht.no_resolve_via_default is vyos_defined %} no {{ afi }} nht resolve-via-default {% endif %} diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index f5e84267e..9522d8fcc 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -775,6 +775,15 @@ def get_frrender_dict(conf, argv=None) -> dict: # At least one participating EVPN interface found, add to result dict if tmp: dict['interfaces'] = tmp + # Zebra prefix exchange for Kernel IP/IPv6 and routing protocols + for ip_version in ['ip', 'ipv6']: + ip_cli_path = ['system', ip_version] + ip_dict = conf.get_config_dict(ip_cli_path, key_mangling=('-', '_'), + get_first_key=True, with_recursive_defaults=True) + if ip_dict: + ip_dict['afi'] = ip_version + dict.update({ip_version : ip_dict}) + # Enable SNMP agentx support # SNMP AgentX support cannot be disabled once enabled if conf.exists(['service', 'snmp']): diff --git a/python/vyos/frrender.py b/python/vyos/frrender.py index f1bb39094..7a0b661a3 100644 --- a/python/vyos/frrender.py +++ b/python/vyos/frrender.py @@ -32,12 +32,16 @@ def debug(message): return print(message) -pim_daemon = 'pimd' - frr_protocols = ['babel', 'bfd', 'bgp', 'eigrp', 'isis', 'mpls', 'nhrp', 'openfabric', 'ospf', 'ospfv3', 'pim', 'pim6', 'rip', 'ripng', 'rpki', 'segment_routing', 'static'] +bgp_daemon = 'bgpd' +isis_daemon = 'isisd' +mgmt_daemon = 'mgmtd' +pim_daemon = 'pimd' +zebra_daemon = 'zebra' + class FRRender: def __init__(self): self._frr_conf = '/run/frr/config/frr.conf' @@ -100,6 +104,12 @@ class FRRender: if 'static' in config_dict and 'deleted' not in config_dict['static']: output += render_to_string('frr/staticd.frr.j2', config_dict['static']) output += '\n' + if 'ip' in config_dict and 'deleted' not in config_dict['ip']: + output += render_to_string('frr/zebra.route-map.frr.j2', config_dict['ip']) + output += '\n' + if 'ipv6' in config_dict and 'deleted' not in config_dict['ipv6']: + output += render_to_string('frr/zebra.route-map.frr.j2', config_dict['ipv6']) + output += '\n' return output debug('======< RENDERING CONFIG >======') diff --git a/smoketest/scripts/cli/test_system_ip.py b/smoketest/scripts/cli/test_system_ip.py index 4ab5e8181..7d730f7b2 100755 --- a/smoketest/scripts/cli/test_system_ip.py +++ b/smoketest/scripts/cli/test_system_ip.py @@ -18,12 +18,21 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError -from vyos.utils.file import read_file -from vyos.frr import mgmt_daemon +from vyos.utils.system import sysctl_read +from vyos.xml_ref import default_value +from vyos.frrender import mgmt_daemon +from vyos.frrender import zebra_daemon base_path = ['system', 'ip'] class TestSystemIP(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestSystemIP, cls).setUpClass() + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + def tearDown(self): self.cli_delete(base_path) self.cli_commit() @@ -31,47 +40,45 @@ class TestSystemIP(VyOSUnitTestSHIM.TestCase): def test_system_ip_forwarding(self): # Test if IPv4 forwarding can be disabled globally, default is '1' # which means forwarding enabled - all_forwarding = '/proc/sys/net/ipv4/conf/all/forwarding' - self.assertEqual(read_file(all_forwarding), '1') + self.assertEqual(sysctl_read('net.ipv4.conf.all.forwarding'), '1') self.cli_set(base_path + ['disable-forwarding']) self.cli_commit() + self.assertEqual(sysctl_read('net.ipv4.conf.all.forwarding'), '0') + frrconfig = self.getFRRconfig('', end='', daemon=zebra_daemon) + self.assertIn('no ip forwarding', frrconfig) - self.assertEqual(read_file(all_forwarding), '0') + self.cli_delete(base_path + ['disable-forwarding']) + self.cli_commit() + self.assertEqual(sysctl_read('net.ipv4.conf.all.forwarding'), '1') + frrconfig = self.getFRRconfig('', end='', daemon=zebra_daemon) + self.assertNotIn('no ip forwarding', frrconfig) def test_system_ip_multipath(self): # Test IPv4 multipathing options, options default to off -> '0' - use_neigh = '/proc/sys/net/ipv4/fib_multipath_use_neigh' - hash_policy = '/proc/sys/net/ipv4/fib_multipath_hash_policy' - - self.assertEqual(read_file(use_neigh), '0') - self.assertEqual(read_file(hash_policy), '0') + self.assertEqual(sysctl_read('net.ipv4.fib_multipath_use_neigh'), '0') + self.assertEqual(sysctl_read('net.ipv4.fib_multipath_hash_policy'), '0') self.cli_set(base_path + ['multipath', 'ignore-unreachable-nexthops']) self.cli_set(base_path + ['multipath', 'layer4-hashing']) self.cli_commit() - self.assertEqual(read_file(use_neigh), '1') - self.assertEqual(read_file(hash_policy), '1') + self.assertEqual(sysctl_read('net.ipv4.fib_multipath_use_neigh'), '1') + self.assertEqual(sysctl_read('net.ipv4.fib_multipath_hash_policy'), '1') def test_system_ip_arp_table_size(self): - # Maximum number of entries to keep in the ARP cache, the - # default is 8k + cli_default = int(default_value(base_path + ['arp', 'table-size'])) + def _verify_gc_thres(table_size): + self.assertEqual(sysctl_read('net.ipv4.neigh.default.gc_thresh3'), str(table_size)) + self.assertEqual(sysctl_read('net.ipv4.neigh.default.gc_thresh2'), str(table_size // 2)) + self.assertEqual(sysctl_read('net.ipv4.neigh.default.gc_thresh1'), str(table_size // 8)) - gc_thresh3 = '/proc/sys/net/ipv4/neigh/default/gc_thresh3' - gc_thresh2 = '/proc/sys/net/ipv4/neigh/default/gc_thresh2' - gc_thresh1 = '/proc/sys/net/ipv4/neigh/default/gc_thresh1' - self.assertEqual(read_file(gc_thresh3), '8192') - self.assertEqual(read_file(gc_thresh2), '4096') - self.assertEqual(read_file(gc_thresh1), '1024') + _verify_gc_thres(cli_default) for size in [1024, 2048, 4096, 8192, 16384, 32768]: self.cli_set(base_path + ['arp', 'table-size', str(size)]) self.cli_commit() - - self.assertEqual(read_file(gc_thresh3), str(size)) - self.assertEqual(read_file(gc_thresh2), str(size // 2)) - self.assertEqual(read_file(gc_thresh1), str(size // 8)) + _verify_gc_thres(size) def test_system_ip_protocol_route_map(self): protocols = ['any', 'babel', 'bgp', 'connected', 'eigrp', 'isis', diff --git a/smoketest/scripts/cli/test_system_ipv6.py b/smoketest/scripts/cli/test_system_ipv6.py index 1c778a11d..be9751c4d 100755 --- a/smoketest/scripts/cli/test_system_ipv6.py +++ b/smoketest/scripts/cli/test_system_ipv6.py @@ -19,17 +19,21 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError -from vyos.utils.file import read_file -from vyos.frr import mgmt_daemon +from vyos.utils.system import sysctl_read +from vyos.xml_ref import default_value +from vyos.frrender import mgmt_daemon +from vyos.frrender import zebra_daemon base_path = ['system', 'ipv6'] -file_forwarding = '/proc/sys/net/ipv6/conf/all/forwarding' -file_disable = '/proc/sys/net/ipv6/conf/all/disable_ipv6' -file_dad = '/proc/sys/net/ipv6/conf/all/accept_dad' -file_multipath = '/proc/sys/net/ipv6/fib_multipath_hash_policy' - class TestSystemIPv6(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestSystemIPv6, cls).setUpClass() + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + def tearDown(self): self.cli_delete(base_path) self.cli_commit() @@ -37,16 +41,23 @@ class TestSystemIPv6(VyOSUnitTestSHIM.TestCase): def test_system_ipv6_forwarding(self): # Test if IPv6 forwarding can be disabled globally, default is '1' # which means forwearding enabled - self.assertEqual(read_file(file_forwarding), '1') + self.assertEqual(sysctl_read('net.ipv6.conf.all.forwarding'), '1') self.cli_set(base_path + ['disable-forwarding']) self.cli_commit() + self.assertEqual(sysctl_read('net.ipv6.conf.all.forwarding'), '0') + frrconfig = self.getFRRconfig('', end='', daemon=zebra_daemon) + self.assertIn('no ipv6 forwarding', frrconfig) - self.assertEqual(read_file(file_forwarding), '0') + self.cli_delete(base_path + ['disable-forwarding']) + self.cli_commit() + self.assertEqual(sysctl_read('net.ipv6.conf.all.forwarding'), '1') + frrconfig = self.getFRRconfig('', end='', daemon=zebra_daemon) + self.assertNotIn('no ipv6 forwarding', frrconfig) def test_system_ipv6_strict_dad(self): # This defaults to 1 - self.assertEqual(read_file(file_dad), '1') + self.assertEqual(sysctl_read('net.ipv6.conf.all.accept_dad'), '1') # Do not assign any IPv6 address on interfaces, this requires a reboot # which can not be tested, but we can read the config file :) @@ -54,11 +65,11 @@ class TestSystemIPv6(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify configuration file - self.assertEqual(read_file(file_dad), '2') + self.assertEqual(sysctl_read('net.ipv6.conf.all.accept_dad'), '2') def test_system_ipv6_multipath(self): # This defaults to 0 - self.assertEqual(read_file(file_multipath), '0') + self.assertEqual(sysctl_read('net.ipv6.fib_multipath_hash_policy'), '0') # Do not assign any IPv6 address on interfaces, this requires a reboot # which can not be tested, but we can read the config file :) @@ -66,26 +77,24 @@ class TestSystemIPv6(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify configuration file - self.assertEqual(read_file(file_multipath), '1') + self.assertEqual(sysctl_read('net.ipv6.fib_multipath_hash_policy'), '1') def test_system_ipv6_neighbor_table_size(self): # Maximum number of entries to keep in the ARP cache, the # default is 8192 + cli_default = int(default_value(base_path + ['neighbor', 'table-size'])) - gc_thresh3 = '/proc/sys/net/ipv6/neigh/default/gc_thresh3' - gc_thresh2 = '/proc/sys/net/ipv6/neigh/default/gc_thresh2' - gc_thresh1 = '/proc/sys/net/ipv6/neigh/default/gc_thresh1' - self.assertEqual(read_file(gc_thresh3), '8192') - self.assertEqual(read_file(gc_thresh2), '4096') - self.assertEqual(read_file(gc_thresh1), '1024') + def _verify_gc_thres(table_size): + self.assertEqual(sysctl_read('net.ipv6.neigh.default.gc_thresh3'), str(table_size)) + self.assertEqual(sysctl_read('net.ipv6.neigh.default.gc_thresh2'), str(table_size // 2)) + self.assertEqual(sysctl_read('net.ipv6.neigh.default.gc_thresh1'), str(table_size // 8)) + + _verify_gc_thres(cli_default) for size in [1024, 2048, 4096, 8192, 16384, 32768]: self.cli_set(base_path + ['neighbor', 'table-size', str(size)]) self.cli_commit() - - self.assertEqual(read_file(gc_thresh3), str(size)) - self.assertEqual(read_file(gc_thresh2), str(size // 2)) - self.assertEqual(read_file(gc_thresh1), str(size // 8)) + _verify_gc_thres(size) def test_system_ipv6_protocol_route_map(self): protocols = ['any', 'babel', 'bgp', 'connected', 'isis', @@ -143,4 +152,4 @@ class TestSystemIPv6(VyOSUnitTestSHIM.TestCase): self.assertNotIn(f'no ipv6 nht resolve-via-default', frrconfig) if __name__ == '__main__': - unittest.main(verbosity=2) + unittest.main(verbosity=2, failfast=True) diff --git a/src/conf_mode/system_ip.py b/src/conf_mode/system_ip.py index 5afb57404..374e6e611 100755 --- a/src/conf_mode/system_ip.py +++ b/src/conf_mode/system_ip.py @@ -17,17 +17,16 @@ from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge +from vyos.configdep import set_dependents +from vyos.configdep import call_dependents +from vyos.configdict import get_frrender_dict +from vyos.configverify import has_frr_protocol_in_dict from vyos.configverify import verify_route_map -from vyos.template import render_to_string +from vyos.frrender import FRRender from vyos.utils.dict import dict_search -from vyos.utils.file import write_file from vyos.utils.process import is_systemd_service_active from vyos.utils.system import sysctl_write -from vyos.configdep import set_dependents -from vyos.configdep import call_dependents from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() @@ -36,42 +35,36 @@ def get_config(config=None): conf = config else: conf = Config() - base = ['system', 'ip'] - - opt = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, - with_recursive_defaults=True) - - # When working with FRR we need to know the corresponding address-family - opt['afi'] = 'ip' - - # We also need the route-map information from the config - # - # XXX: one MUST always call this without the key_mangling() option! See - # vyos.configverify.verify_common_route_maps() for more information. - tmp = {'policy' : {'route-map' : conf.get_config_dict(['policy', 'route-map'], - get_first_key=True)}} - # Merge policy dict into "regular" config dict - opt = dict_merge(tmp, opt) # If IPv4 ARP table size is set here and also manually in sysctl, the more # fine grained value from sysctl must win set_dependents('sysctl', conf) + return get_frrender_dict(conf) - return opt +def verify(config_dict): + if not has_frr_protocol_in_dict(config_dict, 'ip'): + return None + + opt = config_dict['ip'] + opt['policy'] = config_dict['policy'] -def verify(opt): if 'protocol' in opt: for protocol, protocol_options in opt['protocol'].items(): if 'route_map' in protocol_options: verify_route_map(protocol_options['route_map'], opt) return -def generate(opt): - opt['frr_zebra_config'] = render_to_string('frr/zebra.route-map.frr.j2', opt) - return +def generate(config_dict): + if config_dict and 'frrender_cls' not in config_dict: + FRRender().generate(config_dict) + return None + +def apply(config_dict): + if not has_frr_protocol_in_dict(config_dict, 'ip'): + + return None + opt = config_dict['ip'] -def apply(opt): # Apply ARP threshold values # table_size has a default value - thus the key always exists size = int(dict_search('arp.table_size', opt)) @@ -82,11 +75,6 @@ def apply(opt): # Minimum number of stored records is indicated which is not cleared sysctl_write('net.ipv4.neigh.default.gc_thresh1', size // 8) - # enable/disable IPv4 forwarding - tmp = dict_search('disable_forwarding', opt) - value = '0' if (tmp != None) else '1' - write_file('/proc/sys/net/ipv4/conf/all/forwarding', value) - # configure multipath tmp = dict_search('multipath.ignore_unreachable_nexthops', opt) value = '1' if (tmp != None) else '0' @@ -121,18 +109,11 @@ def apply(opt): # running when this script is called first. Skip this part and wait for initial # commit of the configuration to trigger this statement if is_systemd_service_active('frr.service'): - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - - # The route-map used for the FIB (zebra) is part of the zebra daemon - frr_cfg.load_configuration(frr.mgmt_daemon) - frr_cfg.modify_section(r'no ip nht resolve-via-default') - frr_cfg.modify_section(r'ip protocol \w+ route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') - if 'frr_zebra_config' in opt: - frr_cfg.add_before(frr.default_add_before, opt['frr_zebra_config']) - frr_cfg.commit_configuration() + if config_dict and 'frrender_cls' not in config_dict: + FRRender().apply() call_dependents() + return None if __name__ == '__main__': try: diff --git a/src/conf_mode/system_ipv6.py b/src/conf_mode/system_ipv6.py index 90d5100d7..02c9a8201 100755 --- a/src/conf_mode/system_ipv6.py +++ b/src/conf_mode/system_ipv6.py @@ -18,17 +18,17 @@ import os from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge +from vyos.configdep import set_dependents +from vyos.configdep import call_dependents +from vyos.configdict import get_frrender_dict +from vyos.configverify import has_frr_protocol_in_dict from vyos.configverify import verify_route_map -from vyos.template import render_to_string +from vyos.frrender import FRRender from vyos.utils.dict import dict_search from vyos.utils.file import write_file from vyos.utils.process import is_systemd_service_active from vyos.utils.system import sysctl_write -from vyos.configdep import set_dependents -from vyos.configdep import call_dependents from vyos import ConfigError -from vyos import frr from vyos import airbag airbag.enable() @@ -37,42 +37,35 @@ def get_config(config=None): conf = config else: conf = Config() - base = ['system', 'ipv6'] - - opt = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, - with_recursive_defaults=True) - - # When working with FRR we need to know the corresponding address-family - opt['afi'] = 'ipv6' - - # We also need the route-map information from the config - # - # XXX: one MUST always call this without the key_mangling() option! See - # vyos.configverify.verify_common_route_maps() for more information. - tmp = {'policy' : {'route-map' : conf.get_config_dict(['policy', 'route-map'], - get_first_key=True)}} - # Merge policy dict into "regular" config dict - opt = dict_merge(tmp, opt) # If IPv6 neighbor table size is set here and also manually in sysctl, the more # fine grained value from sysctl must win set_dependents('sysctl', conf) + return get_frrender_dict(conf) + +def verify(config_dict): + if not has_frr_protocol_in_dict(config_dict, 'ipv6'): + return None - return opt + opt = config_dict['ipv6'] + opt['policy'] = config_dict['policy'] -def verify(opt): if 'protocol' in opt: for protocol, protocol_options in opt['protocol'].items(): if 'route_map' in protocol_options: verify_route_map(protocol_options['route_map'], opt) return -def generate(opt): - opt['frr_zebra_config'] = render_to_string('frr/zebra.route-map.frr.j2', opt) - return +def generate(config_dict): + if config_dict and 'frrender_cls' not in config_dict: + FRRender().generate(config_dict) + return None + +def apply(config_dict): + if not has_frr_protocol_in_dict(config_dict, 'ipv6'): + return None + opt = config_dict['ipv6'] -def apply(opt): # configure multipath tmp = dict_search('multipath.layer4_hashing', opt) value = '1' if (tmp != None) else '0' @@ -88,11 +81,6 @@ def apply(opt): # Minimum number of stored records is indicated which is not cleared sysctl_write('net.ipv6.neigh.default.gc_thresh1', size // 8) - # enable/disable IPv6 forwarding - tmp = dict_search('disable_forwarding', opt) - value = '0' if (tmp != None) else '1' - write_file('/proc/sys/net/ipv6/conf/all/forwarding', value) - # configure IPv6 strict-dad tmp = dict_search('strict_dad', opt) value = '2' if (tmp != None) else '1' @@ -105,16 +93,11 @@ def apply(opt): # running when this script is called first. Skip this part and wait for initial # commit of the configuration to trigger this statement if is_systemd_service_active('frr.service'): - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(frr.mgmt_daemon) - frr_cfg.modify_section(r'no ipv6 nht resolve-via-default') - frr_cfg.modify_section(r'ipv6 protocol \w+ route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') - if 'frr_zebra_config' in opt: - frr_cfg.add_before(frr.default_add_before, opt['frr_zebra_config']) - frr_cfg.commit_configuration() + if config_dict and 'frrender_cls' not in config_dict: + FRRender().apply() call_dependents() + return None if __name__ == '__main__': try: -- cgit v1.2.3 From 147751ec59527800e956b7ea689805ba80769abe Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Wed, 11 Dec 2024 21:22:41 +0100 Subject: static: T6746: migrate BFD CLI nodes Migrate "set protocols static route next-hop bfd multi-hop source profile " to: "set protocols static route next-hop bfd profile bar" FRR supports only one source IP address per BFD multi-hop session. VyOS had CLI cupport for multiple source addresses which made no sense. --- data/templates/frr/staticd.frr.j2 | 4 +- .../include/static/bfd-multi-hop.xml.i | 8 -- .../include/static/static-route.xml.i | 10 +- .../include/static/static-route6.xml.i | 10 +- .../include/version/quagga-version.xml.i | 2 +- smoketest/config-tests/static-route-basic | 35 +++++ smoketest/configs/static-route-basic | 148 +++++++++++++++++++++ smoketest/scripts/cli/test_protocols_static.py | 3 +- src/migration-scripts/quagga/11-to-12 | 54 ++++++++ 9 files changed, 257 insertions(+), 17 deletions(-) delete mode 100644 interface-definitions/include/static/bfd-multi-hop.xml.i create mode 100644 smoketest/config-tests/static-route-basic create mode 100644 smoketest/configs/static-route-basic create mode 100644 src/migration-scripts/quagga/11-to-12 (limited to 'src') diff --git a/data/templates/frr/staticd.frr.j2 b/data/templates/frr/staticd.frr.j2 index 227807c62..90d17ec14 100644 --- a/data/templates/frr/staticd.frr.j2 +++ b/data/templates/frr/staticd.frr.j2 @@ -15,8 +15,8 @@ {% set ip_route = ip_route ~ ' bfd' %} {% if config.bfd.multi_hop is vyos_defined %} {% set ip_route = ip_route ~ ' multi-hop' %} -{% if config.bfd.source_address is vyos_defined %} -{% set ip_route = ip_route ~ ' source ' ~ config.bfd.source_address %} +{% if config.bfd.multi_hop.source_address is vyos_defined %} +{% set ip_route = ip_route ~ ' source ' ~ config.bfd.multi_hop.source_address %} {% endif %} {% endif %} {% if config.bfd.profile is vyos_defined %} diff --git a/interface-definitions/include/static/bfd-multi-hop.xml.i b/interface-definitions/include/static/bfd-multi-hop.xml.i deleted file mode 100644 index e53994191..000000000 --- a/interface-definitions/include/static/bfd-multi-hop.xml.i +++ /dev/null @@ -1,8 +0,0 @@ - - - - Enable BFD multi-hop session (requires source-address) - - - - diff --git a/interface-definitions/include/static/static-route.xml.i b/interface-definitions/include/static/static-route.xml.i index fa1131118..fd7366286 100644 --- a/interface-definitions/include/static/static-route.xml.i +++ b/interface-definitions/include/static/static-route.xml.i @@ -57,8 +57,14 @@ #include - #include - #include + + + Configure BFD multi-hop session + + + #include + + diff --git a/interface-definitions/include/static/static-route6.xml.i b/interface-definitions/include/static/static-route6.xml.i index e75385dc7..6fcc18b8a 100644 --- a/interface-definitions/include/static/static-route6.xml.i +++ b/interface-definitions/include/static/static-route6.xml.i @@ -58,8 +58,14 @@ #include - #include - #include + + + Configure BFD multi-hop session + + + #include + + diff --git a/interface-definitions/include/version/quagga-version.xml.i b/interface-definitions/include/version/quagga-version.xml.i index 23d884cd4..10ca2816e 100644 --- a/interface-definitions/include/version/quagga-version.xml.i +++ b/interface-definitions/include/version/quagga-version.xml.i @@ -1,3 +1,3 @@ - + diff --git a/smoketest/config-tests/static-route-basic b/smoketest/config-tests/static-route-basic new file mode 100644 index 000000000..4416e6b19 --- /dev/null +++ b/smoketest/config-tests/static-route-basic @@ -0,0 +1,35 @@ +set interfaces ethernet eth0 duplex 'auto' +set interfaces ethernet eth0 speed 'auto' +set interfaces ethernet eth0 vif 203 address '172.18.203.10/24' +set interfaces ethernet eth1 duplex 'auto' +set interfaces ethernet eth1 speed 'auto' +set protocols static route 10.0.0.0/8 blackhole distance '200' +set protocols static route 10.0.0.0/8 blackhole tag '333' +set protocols static route 10.0.0.0/8 next-hop 192.0.2.140 bfd multi-hop source-address '192.0.2.10' +set protocols static route 10.0.0.0/8 next-hop 192.0.2.140 bfd profile 'vyos-test' +set protocols static route 10.0.0.0/8 next-hop 192.0.2.140 distance '123' +set protocols static route 10.0.0.0/8 next-hop 192.0.2.140 interface 'eth0' +set protocols static route 172.16.0.0/16 next-hop 172.18.203.254 bfd multi-hop source-address '172.18.203.254' +set protocols static route 172.16.0.0/16 next-hop 172.18.203.254 bfd profile 'foo' +set protocols static route6 2001:db8:1::/48 next-hop fe80::1 bfd multi-hop source-address 'fe80::1' +set protocols static route6 2001:db8:1::/48 next-hop fe80::1 bfd profile 'bar' +set protocols static route6 2001:db8:1::/48 next-hop fe80::1 interface 'eth0.203' +set protocols static route6 2001:db8:2::/48 next-hop fe80::1 bfd multi-hop source-address 'fe80::1' +set protocols static route6 2001:db8:2::/48 next-hop fe80::1 bfd profile 'bar' +set protocols static route6 2001:db8:2::/48 next-hop fe80::1 interface 'eth0.203' +set protocols static route6 2001:db8:3::/48 next-hop fe80::1 bfd +set protocols static route6 2001:db8:3::/48 next-hop fe80::1 interface 'eth0.203' +set service lldp interface all +set service ntp allow-client address '0.0.0.0/0' +set service ntp allow-client address '::/0' +set service ntp server 172.16.100.10 +set service ntp server 172.16.100.20 +set service ntp server 172.16.110.30 +set system config-management commit-revisions '100' +set system console device ttyS0 speed '115200' +set system host-name 'vyos' +set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0' +set system login user vyos authentication plaintext-password '' +set system syslog global facility all level 'info' +set system syslog global facility local7 level 'debug' +set system time-zone 'Asia/Macau' diff --git a/smoketest/configs/static-route-basic b/smoketest/configs/static-route-basic new file mode 100644 index 000000000..4bf114e33 --- /dev/null +++ b/smoketest/configs/static-route-basic @@ -0,0 +1,148 @@ +interfaces { + ethernet eth0 { + duplex "auto" + speed "auto" + vif 203 { + address "172.18.203.10/24" + } + } + ethernet eth1 { + duplex "auto" + speed "auto" + } +} +protocols { + static { + multicast { + interface-route 224.0.0.0/24 { + next-hop-interface eth0.203 { + distance "10" + } + } + route 224.0.0.0/24 { + next-hop 172.18.203.254 { + distance "20" + } + } + } + route 10.0.0.0/8 { + blackhole { + distance "200" + tag "333" + } + next-hop 192.0.2.140 { + bfd { + multi-hop { + source 192.0.2.10 { + profile "vyos-test" + } + } + } + distance "123" + interface "eth0" + } + } + route 172.16.0.0/16 { + next-hop 172.18.203.254 { + bfd { + multi-hop { + source 172.18.203.254 { + profile "foo" + } + } + } + } + } + route6 2001:db8:1::/48 { + next-hop fe80::1 { + bfd { + multi-hop { + source fe80::1 { + profile "bar" + } + } + } + interface eth0.203 + } + } + route6 2001:db8:2::/48 { + next-hop fe80::1 { + bfd { + multi-hop { + source fe80::1 { + profile "bar" + } + } + } + interface eth0.203 + } + } + route6 2001:db8:3::/48 { + next-hop fe80::1 { + bfd { + } + interface eth0.203 + } + } + } +} +service { + lldp { + interface all { + } + } + ntp { + allow-client { + address "0.0.0.0/0" + address "::/0" + } + server 172.16.100.10 { + } + server 172.16.100.20 { + } + server 172.16.110.30 { + } + } +} +system { + config-management { + commit-revisions 100 + } + console { + device ttyS0 { + speed 115200 + } + } + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0 + plaintext-password "" + } + } + } + ntp { + server 0.pool.ntp.org { + } + server 1.pool.ntp.org { + } + server 2.pool.ntp.org { + } + } + syslog { + global { + facility all { + level info + } + facility local7 { + level debug + } + } + } + time-zone "Asia/Macau" +} + +// Warning: Do not remove the following line. +// vyos-config-version: "bgp@5:broadcast-relay@1:cluster@2:config-management@1:conntrack@5:conntrack-sync@2:container@2:dhcp-relay@2:dhcp-server@8:dhcpv6-server@1:dns-dynamic@4:dns-forwarding@4:firewall@15:flow-accounting@1:https@6:ids@1:interfaces@32:ipoe-server@3:ipsec@13:isis@3:l2tp@9:lldp@2:mdns@1:monitoring@1:nat@8:nat66@3:ntp@3:openconnect@3:ospf@2:pim@1:policy@8:pppoe-server@10:pptp@5:qos@2:quagga@11:reverse-proxy@1:rip@1:rpki@2:salt@1:snmp@3:ssh@2:sstp@6:system@27:vrf@3:vrrp@4:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2" +// Release version: 1.4.0 diff --git a/smoketest/scripts/cli/test_protocols_static.py b/smoketest/scripts/cli/test_protocols_static.py index 086235086..a2cde0237 100755 --- a/smoketest/scripts/cli/test_protocols_static.py +++ b/smoketest/scripts/cli/test_protocols_static.py @@ -202,8 +202,7 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase): if 'bfd_profile' in next_hop_config: self.cli_set(base + ['next-hop', next_hop, 'bfd', 'profile', next_hop_config['bfd_profile']]) if 'bfd_source' in next_hop_config: - self.cli_set(base + ['next-hop', next_hop, 'bfd', 'multi-hop']) - self.cli_set(base + ['next-hop', next_hop, 'bfd', 'source-address', next_hop_config['bfd_source']]) + self.cli_set(base + ['next-hop', next_hop, 'bfd', 'multi-hop', 'source-address', next_hop_config['bfd_source']]) if 'segments' in next_hop_config: self.cli_set(base + ['next-hop', next_hop, 'segments', next_hop_config['segments']]) diff --git a/src/migration-scripts/quagga/11-to-12 b/src/migration-scripts/quagga/11-to-12 new file mode 100644 index 000000000..becc44162 --- /dev/null +++ b/src/migration-scripts/quagga/11-to-12 @@ -0,0 +1,54 @@ +# Copyright 2024 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see . + +# T6747: +# - Migrate static BFD configuration to match FRR possibillities +# - Consolidate static multicast routing configuration under a new node + +from vyos.configtree import ConfigTree + +static_base = ['protocols', 'static'] + +def migrate(config: ConfigTree) -> None: + # Check for static route/route6 configuration + for route_route6 in ['route', 'route6']: + route_route6_base = static_base + [route_route6] + if not config.exists(route_route6_base): + continue + + for prefix in config.list_nodes(route_route6_base): + next_hop_base = route_route6_base + [prefix, 'next-hop'] + if not config.exists(next_hop_base): + continue + + for next_hop in config.list_nodes(next_hop_base): + multi_hop_base = next_hop_base + [next_hop, 'bfd', 'multi-hop'] + + if not config.exists(multi_hop_base): + continue + + mh_source_base = multi_hop_base + ['source'] + source = None + profile = None + for src_ip in config.list_nodes(mh_source_base): + source = src_ip + if config.exists(mh_source_base + [source, 'profile']): + profile = config.return_value(mh_source_base + [source, 'profile']) + # FRR only supports one source, we will use the first one + break + + config.delete(multi_hop_base) + config.set(multi_hop_base + ['source-address'], value=source) + config.set(next_hop_base + [next_hop, 'bfd', 'profile'], value=profile) -- cgit v1.2.3 From 4d0774e534766d70961975933635e6c54dcc1982 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Wed, 11 Dec 2024 21:35:54 +0100 Subject: vrf: T6746: bugfix change of VNI VNI was always retrieved via effective configuration and not active configuration. --- python/vyos/configdict.py | 4 ++-- smoketest/scripts/cli/test_vrf.py | 2 +- src/conf_mode/vrf.py | 15 ++------------- 3 files changed, 5 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 9522d8fcc..fb6365060 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -1100,8 +1100,8 @@ def get_frrender_dict(conf, argv=None) -> dict: vrf['name'][vrf_name]['protocols'].update({'static': {'deleted' : ''}}) vrf_vni_path = ['vrf', 'name', vrf_name, 'vni'] - if conf.exists_effective(vrf_vni_path): - vrf_config.update({'vni': conf.return_effective_value(vrf_vni_path)}) + if conf.exists(vrf_vni_path): + vrf_config.update({'vni': conf.return_value(vrf_vni_path)}) dict.update({'vrf' : vrf}) elif conf.exists_effective(vrf_cli_path): diff --git a/smoketest/scripts/cli/test_vrf.py b/smoketest/scripts/cli/test_vrf.py index 3cab5248e..f4ed1a61f 100755 --- a/smoketest/scripts/cli/test_vrf.py +++ b/smoketest/scripts/cli/test_vrf.py @@ -23,7 +23,7 @@ from json import loads from jmespath import search from vyos.configsession import ConfigSessionError -from vyos.frr import mgmt_daemon +from vyos.frrender import mgmt_daemon from vyos.ifconfig import Interface from vyos.ifconfig import Section from vyos.utils.file import read_file diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index 6eea9af4d..1b19c55d2 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -20,7 +20,6 @@ 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 @@ -135,16 +134,6 @@ def get_config(config=None): # We need to merge the FRR rendering dict into the VRF dict # this is required to get the route-map information to FRR vrf.update({'frr_dict' : get_frrender_dict(conf)}) - - # We also need the route-map information from the config - # - # XXX: one MUST always call this without the key_mangling() option! See - # vyos.configverify.verify_common_route_maps() for more information. - tmp = {'policy' : {'route-map' : conf.get_config_dict(['policy', 'route-map'], - get_first_key=True)}} - - # Merge policy dict into "regular" config dict - vrf = dict_merge(tmp, vrf) return vrf def verify(vrf): @@ -194,13 +183,13 @@ def verify(vrf): if tmp != None: for protocol, protocol_options in tmp.items(): if 'route_map' in protocol_options: - verify_route_map(protocol_options['route_map'], vrf) + verify_route_map(protocol_options['route_map'], vrf['frr_dict']) tmp = dict_search('ipv6.protocol', vrf_config) if tmp != None: for protocol, protocol_options in tmp.items(): if 'route_map' in protocol_options: - verify_route_map(protocol_options['route_map'], vrf) + verify_route_map(protocol_options['route_map'], vrf['frr_dict']) return None -- cgit v1.2.3 From a8da54f50df5d60b18a8cdf0ea63c71f82faf14e Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Thu, 12 Dec 2024 16:12:43 +0100 Subject: rpki: T6746: FRRender needs to calculate SSH key path --- python/vyos/configdict.py | 5 +++++ src/conf_mode/protocols_rpki.py | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index fb6365060..cbcbf9f72 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -944,6 +944,11 @@ def get_frrender_dict(conf, argv=None) -> dict: rpki = conf.get_config_dict(rpki_cli_path, key_mangling=('-', '_'), get_first_key=True, with_pki=True, with_recursive_defaults=True) + rpki_ssh_key_base = '/run/frr/id_rpki' + for cache, cache_config in rpki.get('cache',{}).items(): + if 'ssh' in cache_config: + cache_config['ssh']['public_key_file'] = f'{rpki_ssh_key_base}_{cache}.pub' + cache_config['ssh']['private_key_file'] = f'{rpki_ssh_key_base}_{cache}' dict.update({'rpki' : rpki}) elif conf.exists_effective(rpki_cli_path): dict.update({'rpki' : {'deleted' : ''}}) diff --git a/src/conf_mode/protocols_rpki.py b/src/conf_mode/protocols_rpki.py index d3f515feb..4aefbe36c 100755 --- a/src/conf_mode/protocols_rpki.py +++ b/src/conf_mode/protocols_rpki.py @@ -26,6 +26,7 @@ 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 import ConfigError from vyos import airbag @@ -94,12 +95,12 @@ def generate(config_dict): write_file(cache_config['ssh']['public_key_file'], wrap_openssh_public_key(public_key_data, public_key_type)) write_file(cache_config['ssh']['private_key_file'], wrap_openssh_private_key(private_key_data)) - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().generate(config_dict) return None def apply(config_dict): - if config_dict and 'frrender_cls' not in config_dict: + if config_dict and not is_systemd_service_running('vyos-configd.service'): FRRender().apply() return None -- cgit v1.2.3 From d58e7ace7075e24c64cfe5e56ffcaad1688446e9 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Thu, 12 Dec 2024 20:57:29 +0100 Subject: multicast: T6746: migrate CLI to to mimic unicast IPv4 routes syntax Consolidate "multicast interface-route" and "multicast route" under common "mroute " CLI node. --- smoketest/config-tests/static-route-basic | 2 ++ smoketest/configs/static-route-basic | 4 ++-- src/migration-scripts/quagga/11-to-12 | 21 +++++++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/smoketest/config-tests/static-route-basic b/smoketest/config-tests/static-route-basic index 4416e6b19..d2d33d043 100644 --- a/smoketest/config-tests/static-route-basic +++ b/smoketest/config-tests/static-route-basic @@ -3,6 +3,8 @@ set interfaces ethernet eth0 speed 'auto' set interfaces ethernet eth0 vif 203 address '172.18.203.10/24' set interfaces ethernet eth1 duplex 'auto' set interfaces ethernet eth1 speed 'auto' +set protocols static mroute 224.1.0.0/24 interface eth0.203 distance '10' +set protocols static mroute 224.2.0.0/24 next-hop 172.18.203.254 distance '20' set protocols static route 10.0.0.0/8 blackhole distance '200' set protocols static route 10.0.0.0/8 blackhole tag '333' set protocols static route 10.0.0.0/8 next-hop 192.0.2.140 bfd multi-hop source-address '192.0.2.10' diff --git a/smoketest/configs/static-route-basic b/smoketest/configs/static-route-basic index 4bf114e33..648e19676 100644 --- a/smoketest/configs/static-route-basic +++ b/smoketest/configs/static-route-basic @@ -14,12 +14,12 @@ interfaces { protocols { static { multicast { - interface-route 224.0.0.0/24 { + interface-route 224.1.0.0/24 { next-hop-interface eth0.203 { distance "10" } } - route 224.0.0.0/24 { + route 224.2.0.0/24 { next-hop 172.18.203.254 { distance "20" } diff --git a/src/migration-scripts/quagga/11-to-12 b/src/migration-scripts/quagga/11-to-12 index becc44162..8ae2023a1 100644 --- a/src/migration-scripts/quagga/11-to-12 +++ b/src/migration-scripts/quagga/11-to-12 @@ -23,6 +23,7 @@ static_base = ['protocols', 'static'] def migrate(config: ConfigTree) -> None: # Check for static route/route6 configuration + # Migrate static BFD configuration to match FRR possibillities for route_route6 in ['route', 'route6']: route_route6_base = static_base + [route_route6] if not config.exists(route_route6_base): @@ -52,3 +53,23 @@ def migrate(config: ConfigTree) -> None: config.delete(multi_hop_base) config.set(multi_hop_base + ['source-address'], value=source) config.set(next_hop_base + [next_hop, 'bfd', 'profile'], value=profile) + + # Consolidate static multicast routing configuration under a new node + if config.exists(static_base + ['multicast']): + for mroute in ['interface-route', 'route']: + mroute_base = static_base + ['multicast', mroute] + if not config.exists(mroute_base): + continue + config.set(static_base + ['mroute']) + config.set_tag(static_base + ['mroute']) + for route in config.list_nodes(mroute_base): + config.copy(mroute_base + [route], static_base + ['mroute', route]) + + mroute_base = static_base + ['mroute'] + if config.exists(mroute_base): + for mroute in config.list_nodes(mroute_base): + interface_path = mroute_base + [mroute, 'next-hop-interface'] + if config.exists(interface_path): + config.rename(interface_path, 'interface') + + config.delete(static_base + ['multicast']) -- 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') 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