summaryrefslogtreecommitdiff
path: root/python/vyos/frrender.py
diff options
context:
space:
mode:
authorChristian Breunig <christian@breunig.cc>2024-12-08 08:34:41 +0100
committerChristian Breunig <christian@breunig.cc>2024-12-16 22:24:36 +0100
commit3c79477adf3cd4f4efb302b58542ddd668b562ac (patch)
tree316d03a81f1c9c37b48b658af7826eaf11413130 /python/vyos/frrender.py
parent328f354677a4fd2f60b95473698816e99aaa20a7 (diff)
downloadveeos-1x-3c79477adf3cd4f4efb302b58542ddd668b562ac.tar.gz
veeos-1x-3c79477adf3cd4f4efb302b58542ddd668b562ac.zip
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 <x.x.x.x/x> interface To have an identical look and feel with regular static routes.
Diffstat (limited to 'python/vyos/frrender.py')
-rw-r--r--python/vyos/frrender.py156
1 files changed, 156 insertions, 0 deletions
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 <maintainers@vyos.io>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+"""
+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')