summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/frr/daemons.frr.tmpl54
-rw-r--r--interface-definitions/system-frr.xml.in77
-rwxr-xr-xsmoketest/scripts/cli/test_system_frr.py146
-rwxr-xr-xsrc/conf_mode/snmp.py10
-rwxr-xr-xsrc/conf_mode/system_frr.py91
5 files changed, 377 insertions, 1 deletions
diff --git a/data/templates/frr/daemons.frr.tmpl b/data/templates/frr/daemons.frr.tmpl
new file mode 100644
index 000000000..ab7b14d6b
--- /dev/null
+++ b/data/templates/frr/daemons.frr.tmpl
@@ -0,0 +1,54 @@
+zebra=yes
+bgpd=yes
+ospfd=yes
+ospf6d=yes
+ripd=yes
+ripngd=yes
+isisd=yes
+pimd=no
+ldpd=yes
+nhrpd=no
+eigrpd=no
+babeld=no
+sharpd=no
+pbrd=no
+bfdd=yes
+staticd=yes
+
+vtysh_enable=yes
+zebra_options=" -s 90000000 --daemon -A 127.0.0.1
+{%- if irdp is defined %} -M irdp{% endif -%}
+{%- if snmp is defined and snmp.zebra is defined %} -M snmp{% endif -%}
+"
+bgpd_options=" --daemon -A 127.0.0.1
+{%- if bmp is defined %} -M bmp{% endif -%}
+{%- if snmp is defined and snmp.bgpd is defined %} -M snmp{% endif -%}
+"
+ospfd_options=" --daemon -A 127.0.0.1
+{%- if snmp is defined and snmp.ospfd is defined %} -M snmp{% endif -%}
+"
+ospf6d_options=" --daemon -A ::1
+{%- if snmp is defined and snmp.ospf6d is defined %} -M snmp{% endif -%}
+"
+ripd_options=" --daemon -A 127.0.0.1
+{%- if snmp is defined and snmp.ripd is defined %} -M snmp{% endif -%}
+"
+ripngd_options=" --daemon -A ::1"
+isisd_options=" --daemon -A 127.0.0.1
+{%- if snmp is defined and snmp.isisd is defined %} -M snmp{% endif -%}
+"
+pimd_options=" --daemon -A 127.0.0.1"
+ldpd_options=" --daemon -A 127.0.0.1
+{%- if snmp is defined and snmp.ldpd is defined %} -M snmp{% endif -%}
+"
+nhrpd_options=" --daemon -A 127.0.0.1"
+eigrpd_options=" --daemon -A 127.0.0.1"
+babeld_options=" --daemon -A 127.0.0.1"
+sharpd_options=" --daemon -A 127.0.0.1"
+pbrd_options=" --daemon -A 127.0.0.1"
+staticd_options=" --daemon -A 127.0.0.1"
+bfdd_options=" --daemon -A 127.0.0.1"
+
+watchfrr_enable=no
+valgrind_enable=no
+
diff --git a/interface-definitions/system-frr.xml.in b/interface-definitions/system-frr.xml.in
new file mode 100644
index 000000000..9fe23ed75
--- /dev/null
+++ b/interface-definitions/system-frr.xml.in
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interfaceDefinition>
+ <node name="system">
+ <children>
+ <node name="frr" owner="${vyos_conf_scripts_dir}/system_frr.py">
+ <properties>
+ <help>Configure FRR parameters</help>
+ <!-- Before components that use FRR -->
+ <priority>150</priority>
+ </properties>
+ <children>
+ <leafNode name="bmp">
+ <properties>
+ <help>Enable BGP Monitoring Protocol support</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="irdp">
+ <properties>
+ <help>Enable ICMP Router Discovery Protocol support</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <node name="snmp">
+ <properties>
+ <help>Enable SNMP integration for next daemons</help>
+ </properties>
+ <children>
+ <leafNode name="bgpd">
+ <properties>
+ <help>BGP</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="isisd">
+ <properties>
+ <help>IS-IS</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="ldpd">
+ <properties>
+ <help>LDP</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="ospf6d">
+ <properties>
+ <help>OSPFv3</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="ospfd">
+ <properties>
+ <help>OSPFv2</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="ripd">
+ <properties>
+ <help>RIP</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="zebra">
+ <properties>
+ <help>Zebra (IP routing manager)</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/smoketest/scripts/cli/test_system_frr.py b/smoketest/scripts/cli/test_system_frr.py
new file mode 100755
index 000000000..331133ed4
--- /dev/null
+++ b/smoketest/scripts/cli/test_system_frr.py
@@ -0,0 +1,146 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019-2020 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 <http://www.gnu.org/licenses/>.
+
+import re
+import unittest
+from base_vyostest_shim import VyOSUnitTestSHIM
+from vyos.util import read_file
+
+config_file = '/etc/frr/daemons'
+base_path = ['system', 'frr']
+
+
+def daemons_config_parse(daemons_config):
+ # create regex for parsing daemons options
+ regex_daemon_config = re.compile(
+ r'^(?P<daemon_name>\w+)_options="(?P<daemon_options>.*)"$', re.M)
+ # create empty dict for config
+ daemons_config_dict = {}
+ # fill dictionary with actual config
+ for daemon in regex_daemon_config.finditer(daemons_config):
+ daemon_name = daemon.group('daemon_name')
+ daemon_options = daemon.group('daemon_options')
+ daemons_config_dict[daemon_name] = daemon_options
+
+ # return daemons config
+ return (daemons_config_dict)
+
+
+class TestSystemFRR(VyOSUnitTestSHIM.TestCase):
+
+ def tearDown(self):
+ self.cli_delete(base_path)
+ self.cli_commit()
+
+ def test_frr_snmp_multipledaemons(self):
+ # test SNMP integration for multiple daemons
+ test_daemon_names = ['ospfd', 'bgpd']
+ for test_daemon_name in test_daemon_names:
+ self.cli_set(base_path + ['snmp', test_daemon_name])
+ self.cli_commit()
+
+ # read the config file and check content
+ daemons_config = read_file(config_file)
+ daemons_config_dict = daemons_config_parse(daemons_config)
+ # prepare regex for matching SNMP integration
+ regex_snmp = re.compile(r'^.* -M snmp.*$')
+ for (daemon_name, daemon_options) in daemons_config_dict.items():
+ snmp_enabled = regex_snmp.match(daemon_options)
+ if daemon_name in test_daemon_names:
+ self.assertTrue(snmp_enabled)
+ else:
+ self.assertFalse(snmp_enabled)
+
+ def test_frr_snmp_addandremove(self):
+ # test enabling and disabling of SNMP integration
+ test_daemon_names = ['ospfd', 'bgpd']
+ for test_daemon_name in test_daemon_names:
+ self.cli_set(base_path + ['snmp', test_daemon_name])
+ self.cli_commit()
+
+ self.cli_delete(base_path)
+ self.cli_commit()
+
+ # read the config file and check content
+ daemons_config = read_file(config_file)
+ daemons_config_dict = daemons_config_parse(daemons_config)
+ # prepare regex for matching SNMP integration
+ regex_snmp = re.compile(r'^.* -M snmp.*$')
+ for test_daemon_name in test_daemon_names:
+ snmp_enabled = regex_snmp.match(
+ daemons_config_dict[test_daemon_name])
+ self.assertFalse(snmp_enabled)
+
+ def test_frr_snmp_empty(self):
+ # test empty config section
+ self.cli_set(base_path + ['snmp'])
+ self.cli_commit()
+
+ # read the config file and check content
+ daemons_config = read_file(config_file)
+ daemons_config_dict = daemons_config_parse(daemons_config)
+ # prepare regex for matching SNMP integration
+ regex_snmp = re.compile(r'^.* -M snmp.*$')
+ for daemon_options in daemons_config_dict.values():
+ snmp_enabled = regex_snmp.match(daemon_options)
+ self.assertFalse(snmp_enabled)
+
+ def test_frr_bmp(self):
+ # test BMP
+ self.cli_set(base_path + ['bmp'])
+ self.cli_commit()
+
+ # read the config file and check content
+ daemons_config = read_file(config_file)
+ daemons_config_dict = daemons_config_parse(daemons_config)
+ # prepare regex
+ regex_bmp = re.compile(r'^.* -M bmp.*$')
+ bmp_enabled = regex_bmp.match(daemons_config_dict['bgpd'])
+ self.assertTrue(bmp_enabled)
+
+ def test_frr_irdp(self):
+ # test IRDP
+ self.cli_set(base_path + ['irdp'])
+ self.cli_commit()
+
+ # read the config file and check content
+ daemons_config = read_file(config_file)
+ daemons_config_dict = daemons_config_parse(daemons_config)
+ # prepare regex
+ regex_irdp = re.compile(r'^.* -M irdp.*$')
+ irdp_enabled = regex_irdp.match(daemons_config_dict['zebra'])
+ self.assertTrue(irdp_enabled)
+
+ def test_frr_bmpandsnmp(self):
+ # test empty config section
+ self.cli_set(base_path + ['bmp'])
+ self.cli_set(base_path + ['snmp', 'bgpd'])
+ self.cli_commit()
+
+ # read the config file and check content
+ daemons_config = read_file(config_file)
+ daemons_config_dict = daemons_config_parse(daemons_config)
+ # prepare regex
+ regex_snmp = re.compile(r'^.* -M bmp.*$')
+ regex_snmp = re.compile(r'^.* -M snmp.*$')
+ bmp_enabled = regex_snmp.match(daemons_config_dict['bgpd'])
+ snmp_enabled = regex_snmp.match(daemons_config_dict['bgpd'])
+ self.assertTrue(bmp_enabled)
+ self.assertTrue(snmp_enabled)
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2, failfast=True)
diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py
index ae060580d..5cd24db32 100755
--- a/src/conf_mode/snmp.py
+++ b/src/conf_mode/snmp.py
@@ -293,7 +293,15 @@ def apply(snmp):
call(f'systemctl restart {systemd_service}')
# Enable AgentX in FRR
- call('vtysh -c "configure terminal" -c "agentx" >/dev/null')
+ # This should be done for each daemon individually because common command
+ # works only if all the daemons started with SNMP support
+ frr_daemons_list = [
+ 'bgpd', 'ospf6d', 'ospfd', 'ripd', 'ripngd', 'isisd', 'ldpd', 'zebra'
+ ]
+ for frr_daemon in frr_daemons_list:
+ call(
+ f'vtysh -c "configure terminal" -d {frr_daemon} -c "agentx" >/dev/null'
+ )
return None
diff --git a/src/conf_mode/system_frr.py b/src/conf_mode/system_frr.py
new file mode 100755
index 000000000..1af0055f6
--- /dev/null
+++ b/src/conf_mode/system_frr.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 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 <http://www.gnu.org/licenses/>.
+
+from pathlib import Path
+from sys import exit
+
+from vyos import ConfigError
+from vyos import airbag
+from vyos.config import Config
+from vyos.logger import syslog
+from vyos.template import render_to_string
+from vyos.util import read_file, write_file, run
+airbag.enable()
+
+# path to daemons config and config status files
+config_file = '/etc/frr/daemons'
+vyos_status_file = '/tmp/vyos-config-status'
+# path to watchfrr for FRR control
+watchfrr = '/usr/lib/frr/watchfrr.sh'
+
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+
+ base = ['system', 'frr']
+ frr_config = conf.get_config_dict(base, get_first_key=True)
+
+ return frr_config
+
+
+def verify(frr_config):
+ # Nothing to verify here
+ pass
+
+
+def generate(frr_config):
+ # read daemons config file
+ daemons_config_current = read_file(config_file)
+ # generate new config file
+ daemons_config_new = render_to_string('frr/daemons.frr.tmpl', frr_config)
+ # update configuration file if this is necessary
+ if daemons_config_new != daemons_config_current:
+ syslog.warning('FRR daemons configuration file need to be changed')
+ write_file(config_file, daemons_config_new)
+ frr_config['config_file_changed'] = True
+
+
+def apply(frr_config):
+ # check if this is initial commit during boot or intiated by CLI
+ # if the file exists, this must be CLI commit
+ commit_type_cli = Path(vyos_status_file).exists()
+ # display warning to user
+ if commit_type_cli and frr_config.get('config_file_changed'):
+ # Since FRR restart is not safe thing, better to give
+ # control over this to users
+ print('''
+ You need to reboot a router (preferred) or restart FRR
+ to apply changes in modules settings
+ ''')
+ # restart FRR automatically. DUring the initial boot this should be
+ # safe in most cases
+ if not commit_type_cli and frr_config.get('config_file_changed'):
+ syslog.warning('Restarting FRR to apply changes in modules')
+ run(f'{watchfrr} restart')
+
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)