diff options
-rw-r--r-- | data/templates/frr/daemons.frr.tmpl | 2 | ||||
-rw-r--r-- | data/templates/frr/fabricd.frr.j2 | 72 | ||||
-rw-r--r-- | interface-definitions/include/isis/protocol-common-config.xml.i | 20 | ||||
-rw-r--r-- | interface-definitions/include/log-adjacency-changes.xml.i | 8 | ||||
-rw-r--r-- | interface-definitions/include/net.xml.i | 14 | ||||
-rw-r--r-- | interface-definitions/include/openfabric/password.xml.i | 20 | ||||
-rw-r--r-- | interface-definitions/protocols_openfabric.xml.in | 218 | ||||
-rw-r--r-- | op-mode-definitions/include/show-route-openfabric.xml.i | 8 | ||||
-rw-r--r-- | op-mode-definitions/monitor-log.xml.in | 6 | ||||
-rw-r--r-- | op-mode-definitions/restart-frr.xml.in | 6 | ||||
-rw-r--r-- | op-mode-definitions/show-ip-route.xml.in | 1 | ||||
-rw-r--r-- | op-mode-definitions/show-ipv6-route.xml.in | 1 | ||||
-rw-r--r-- | op-mode-definitions/show-log.xml.in | 6 | ||||
-rw-r--r-- | op-mode-definitions/show-openfabric.xml.in | 51 | ||||
-rw-r--r-- | python/vyos/frr.py | 2 | ||||
-rw-r--r-- | smoketest/scripts/cli/test_protocols_openfabric.py | 186 | ||||
-rw-r--r-- | src/conf_mode/protocols_openfabric.py | 145 | ||||
-rwxr-xr-x | src/op_mode/restart_frr.py | 2 |
18 files changed, 747 insertions, 21 deletions
diff --git a/data/templates/frr/daemons.frr.tmpl b/data/templates/frr/daemons.frr.tmpl index 339b4e52f..3506528d2 100644 --- a/data/templates/frr/daemons.frr.tmpl +++ b/data/templates/frr/daemons.frr.tmpl @@ -36,7 +36,7 @@ babeld=yes sharpd=no pbrd=no bfdd=yes -fabricd=no +fabricd=yes vrrpd=no pathd=no diff --git a/data/templates/frr/fabricd.frr.j2 b/data/templates/frr/fabricd.frr.j2 new file mode 100644 index 000000000..8f2ae6466 --- /dev/null +++ b/data/templates/frr/fabricd.frr.j2 @@ -0,0 +1,72 @@ +! +{% for name, router_config in domain.items() %} +{% if router_config.interface is vyos_defined %} +{% for iface, iface_config in router_config.interface.items() %} +interface {{ iface }} +{% if iface_config.address_family.ipv4 is vyos_defined %} + ip router openfabric {{ name }} +{% endif %} +{% if iface_config.address_family.ipv6 is vyos_defined %} + ipv6 router openfabric {{ name }} +{% endif %} +{% if iface_config.csnp_interval is vyos_defined %} + openfabric csnp-interval {{ iface_config.csnp_interval }} +{% endif %} +{% if iface_config.hello_interval is vyos_defined %} + openfabric hello-interval {{ iface_config.hello_interval }} +{% endif %} +{% if iface_config.hello_multiplier is vyos_defined %} + openfabric hello-multiplier {{ iface_config.hello_multiplier }} +{% endif %} +{% if iface_config.metric is vyos_defined %} + openfabric metric {{ iface_config.metric }} +{% endif %} +{% if iface_config.passive is vyos_defined or iface == 'lo' %} + openfabric passive +{% endif %} +{% if iface_config.password.md5 is vyos_defined %} + openfabric password md5 {{ iface_config.password.md5 }} +{% elif iface_config.password.plaintext_password is vyos_defined %} + openfabric password clear {{ iface_config.password.plaintext_password }} +{% endif %} +{% if iface_config.psnp_interval is vyos_defined %} + openfabric psnp-interval {{ iface_config.psnp_interval }} +{% endif %} +exit +! +{% endfor %} +{% endif %} +router openfabric {{ name }} + net {{ net }} +{% if router_config.domain_password.md5 is vyos_defined %} + domain-password md5 {{ router_config.domain_password.plaintext_password }} +{% elif router_config.domain_password.plaintext_password is vyos_defined %} + domain-password clear {{ router_config.domain_password.plaintext_password }} +{% endif %} +{% if router_config.log_adjacency_changes is vyos_defined %} + log-adjacency-changes +{% endif %} +{% if router_config.set_overload_bit is vyos_defined %} + set-overload-bit +{% endif %} +{% if router_config.purge_originator is vyos_defined %} + purge-originator +{% endif %} +{% if router_config.fabric_tier is vyos_defined %} + fabric-tier {{ router_config.fabric_tier }} +{% endif %} +{% if router_config.lsp_gen_interval is vyos_defined %} + lsp-gen-interval {{ router_config.lsp_gen_interval }} +{% endif %} +{% if router_config.lsp_refresh_interval is vyos_defined %} + lsp-refresh-interval {{ router_config.lsp_refresh_interval }} +{% endif %} +{% if router_config.max_lsp_lifetime is vyos_defined %} + max-lsp-lifetime {{ router_config.max_lsp_lifetime }} +{% endif %} +{% if router_config.spf_interval is vyos_defined %} + spf-interval {{ router_config.spf_interval }} +{% endif %} +exit +! +{% endfor %} diff --git a/interface-definitions/include/isis/protocol-common-config.xml.i b/interface-definitions/include/isis/protocol-common-config.xml.i index 0e79ca5f2..35ce80be9 100644 --- a/interface-definitions/include/isis/protocol-common-config.xml.i +++ b/interface-definitions/include/isis/protocol-common-config.xml.i @@ -86,12 +86,7 @@ </constraint> </properties> </leafNode> -<leafNode name="log-adjacency-changes"> - <properties> - <help>Log adjacency state changes</help> - <valueless/> - </properties> -</leafNode> +#include <include/log-adjacency-changes.xml.i> <leafNode name="lsp-gen-interval"> <properties> <help>Minimum interval between regenerating same LSP</help> @@ -208,18 +203,7 @@ #include <include/isis/lfa-protocol.xml.i> </children> </node> -<leafNode name="net"> - <properties> - <help>A Network Entity Title for this process (ISO only)</help> - <valueHelp> - <format>XX.XXXX. ... .XXX.XX</format> - <description>Network entity title (NET)</description> - </valueHelp> - <constraint> - <regex>[a-fA-F0-9]{2}(\.[a-fA-F0-9]{4}){3,9}\.[a-fA-F0-9]{2}</regex> - </constraint> - </properties> -</leafNode> +#include <include/net.xml.i> <leafNode name="purge-originator"> <properties> <help>Use the RFC 6232 purge-originator</help> diff --git a/interface-definitions/include/log-adjacency-changes.xml.i b/interface-definitions/include/log-adjacency-changes.xml.i new file mode 100644 index 000000000..a0628b8e2 --- /dev/null +++ b/interface-definitions/include/log-adjacency-changes.xml.i @@ -0,0 +1,8 @@ +<!-- include start from log-adjacency-changes.xml.i --> +<leafNode name="log-adjacency-changes"> + <properties> + <help>Log changes in adjacency state</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/net.xml.i b/interface-definitions/include/net.xml.i new file mode 100644 index 000000000..10b54ee49 --- /dev/null +++ b/interface-definitions/include/net.xml.i @@ -0,0 +1,14 @@ +<!-- include start from net.xml.i --> +<leafNode name="net"> + <properties> + <help>A Network Entity Title for the process (ISO only)</help> + <valueHelp> + <format>XX.XXXX. ... .XXX.XX</format> + <description>Network entity title (NET)</description> + </valueHelp> + <constraint> + <regex>[a-fA-F0-9]{2}(\.[a-fA-F0-9]{4}){3,9}\.[a-fA-F0-9]{2}</regex> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/openfabric/password.xml.i b/interface-definitions/include/openfabric/password.xml.i new file mode 100644 index 000000000..fa34a4dab --- /dev/null +++ b/interface-definitions/include/openfabric/password.xml.i @@ -0,0 +1,20 @@ +<!-- include start from openfabric/password.xml.i --> +<leafNode name="plaintext-password"> + <properties> + <help>Use plain text password</help> + <valueHelp> + <format>txt</format> + <description>Authentication password</description> + </valueHelp> + </properties> +</leafNode> +<leafNode name="md5"> + <properties> + <help>Use MD5 hash authentication</help> + <valueHelp> + <format>txt</format> + <description>Authentication password</description> + </valueHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/protocols_openfabric.xml.in b/interface-definitions/protocols_openfabric.xml.in new file mode 100644 index 000000000..81200360e --- /dev/null +++ b/interface-definitions/protocols_openfabric.xml.in @@ -0,0 +1,218 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interfaceDefinition> + <node name="protocols"> + <children> + <node name="openfabric" owner="${vyos_conf_scripts_dir}/protocols_openfabric.py"> + <properties> + <help>OpenFabric protocol</help> + <priority>680</priority> + </properties> + <children> + #include <include/net.xml.i> + <tagNode name="domain"> + <properties> + <help>OpenFabric process name</help> + <valueHelp> + <format>txt</format> + <description>Domain name</description> + </valueHelp> + </properties> + <children> + <tagNode name="interface"> + <properties> + <help>Interface params</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface name</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> + <children> + <node name="address-family"> + <properties> + <help>Openfabric address family</help> + </properties> + <children> + <leafNode name="ipv4"> + <properties> + <help>IPv4 OpenFabric</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="ipv6"> + <properties> + <help>IPv6 OpenFabric</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <leafNode name="csnp-interval"> + <properties> + <help>Complete Sequence Number Packets (CSNP) interval</help> + <valueHelp> + <format>u32:1-600</format> + <description>CSNP interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-600"/> + </constraint> + </properties> + </leafNode> + <leafNode name="hello-interval"> + <properties> + <help>Hello interval</help> + <valueHelp> + <format>u32:1-600</format> + <description>Hello interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-600"/> + </constraint> + </properties> + </leafNode> + <leafNode name="hello-multiplier"> + <properties> + <help>Multiplier for Hello holding time</help> + <valueHelp> + <format>u32:2-100</format> + <description>Multiplier for Hello holding time</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 2-100"/> + </constraint> + </properties> + </leafNode> + <leafNode name="metric"> + <properties> + <help>Interface metric value</help> + <valueHelp> + <format>u32:0-16777215</format> + <description>Interface metric value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-16777215"/> + </constraint> + </properties> + </leafNode> + <leafNode name="passive"> + <properties> + <help>Do not initiate adjacencies to the interface</help> + <valueless/> + </properties> + </leafNode> + <node name="password"> + <properties> + <help>Authentication password for the interface</help> + </properties> + <children> + #include <include/openfabric/password.xml.i> + </children> + </node> + <leafNode name="psnp-interval"> + <properties> + <help>Partial Sequence Number Packets (PSNP) interval</help> + <valueHelp> + <format>u32:0-120</format> + <description>PSNP interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-120"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + <node name="domain-password"> + <properties> + <help>Authentication password for a routing domain</help> + </properties> + <children> + #include <include/openfabric/password.xml.i> + </children> + </node> + #include <include/log-adjacency-changes.xml.i> + <leafNode name="set-overload-bit"> + <properties> + <help>Overload bit to avoid any transit traffic</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="purge-originator"> + <properties> + <help>RFC 6232 purge originator identification</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="fabric-tier"> + <properties> + <help>Static tier number to advertise as location in the fabric</help> + <valueHelp> + <format>u32:0-14</format> + <description>Static tier number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-14"/> + </constraint> + </properties> + </leafNode> + <leafNode name="lsp-gen-interval"> + <properties> + <help>Minimum interval between regenerating same link-state packet (LSP)</help> + <valueHelp> + <format>u32:1-120</format> + <description>Minimum interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-120"/> + </constraint> + </properties> + </leafNode> + <leafNode name="lsp-refresh-interval"> + <properties> + <help>Link-state packet (LSP) refresh interval</help> + <valueHelp> + <format>u32:1-65235</format> + <description>LSP refresh interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65235"/> + </constraint> + </properties> + </leafNode> + <leafNode name="max-lsp-lifetime"> + <properties> + <help>Maximum link-state packet lifetime</help> + <valueHelp> + <format>u32:360-65535</format> + <description>Maximum LSP lifetime in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 360-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="spf-interval"> + <properties> + <help>Minimum interval between SPF calculations</help> + <valueHelp> + <format>u32:1-120</format> + <description>Interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-120"/> + </constraint> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/include/show-route-openfabric.xml.i b/op-mode-definitions/include/show-route-openfabric.xml.i new file mode 100644 index 000000000..ae1ef380e --- /dev/null +++ b/op-mode-definitions/include/show-route-openfabric.xml.i @@ -0,0 +1,8 @@ +<!-- included start from show-route-openfabric.xml.i --> +<leafNode name="openfabric"> + <properties> + <help>OpenFabric routes</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> +</leafNode> +<!-- included end --> diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in index a2d5d924a..6a2b7e53b 100644 --- a/op-mode-definitions/monitor-log.xml.in +++ b/op-mode-definitions/monitor-log.xml.in @@ -237,6 +237,12 @@ </properties> <command>journalctl --follow --no-hostname --boot /usr/lib/frr/isisd</command> </leafNode> + <leafNode name="openfabric"> + <properties> + <help>Monitor log for OpenFabric</help> + </properties> + <command>journalctl --follow --no-hostname --boot /usr/lib/frr/fabricd</command> + </leafNode> <leafNode name="nhrp"> <properties> <help>Monitor log for NHRP</help> diff --git a/op-mode-definitions/restart-frr.xml.in b/op-mode-definitions/restart-frr.xml.in index 2c9d4b1cc..4772e8dd2 100644 --- a/op-mode-definitions/restart-frr.xml.in +++ b/op-mode-definitions/restart-frr.xml.in @@ -56,6 +56,12 @@ </properties> <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon isisd</command> </leafNode> + <leafNode name="openfabric"> + <properties> + <help>Restart OpenFabric routing daemon</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon fabricd</command> + </leafNode> <leafNode name="pim6"> <properties> <help>Restart IPv6 Protocol Independent Multicast (PIM) daemon</help> diff --git a/op-mode-definitions/show-ip-route.xml.in b/op-mode-definitions/show-ip-route.xml.in index c878bf712..37279d3d2 100644 --- a/op-mode-definitions/show-ip-route.xml.in +++ b/op-mode-definitions/show-ip-route.xml.in @@ -46,6 +46,7 @@ <command>ip -s route list $5</command> </tagNode> #include <include/show-route-isis.xml.i> + #include <include/show-route-openfabric.xml.i> #include <include/show-route-kernel.xml.i> #include <include/show-route-ospf.xml.i> #include <include/show-route-rip.xml.i> diff --git a/op-mode-definitions/show-ipv6-route.xml.in b/op-mode-definitions/show-ipv6-route.xml.in index d73fb46b4..f68a94971 100644 --- a/op-mode-definitions/show-ipv6-route.xml.in +++ b/op-mode-definitions/show-ipv6-route.xml.in @@ -46,6 +46,7 @@ <command>ip -s -f inet6 route list $5</command> </tagNode> #include <include/show-route-isis.xml.i> + #include <include/show-route-openfabric.xml.i> #include <include/show-route-kernel.xml.i> #include <include/show-route-ospfv3.xml.i> #include <include/show-route-ripng.xml.i> diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in index 7ae3b890b..f0fad63d2 100644 --- a/op-mode-definitions/show-log.xml.in +++ b/op-mode-definitions/show-log.xml.in @@ -642,6 +642,12 @@ </properties> <command>journalctl --boot /usr/lib/frr/isisd</command> </leafNode> + <leafNode name="openfabric"> + <properties> + <help>Show log for OpenFabric</help> + </properties> + <command>journalctl --boot /usr/lib/frr/fabricd</command> + </leafNode> <leafNode name="nhrp"> <properties> <help>Show log for NHRP</help> diff --git a/op-mode-definitions/show-openfabric.xml.in b/op-mode-definitions/show-openfabric.xml.in new file mode 100644 index 000000000..2f489866e --- /dev/null +++ b/op-mode-definitions/show-openfabric.xml.in @@ -0,0 +1,51 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="openfabric"> + <properties> + <help>Show OpenFabric routing protocol</help> + </properties> + <children> + <node name="database"> + <properties> + <help>Show OpenFabric link state database</help> + </properties> + <children> + #include <include/vtysh-generic-detail.xml.i> + </children> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </node> + <node name="interface"> + <properties> + <help>Show OpenFabric interfaces</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + </properties> + <children> + #include <include/vtysh-generic-detail.xml.i> + </children> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </node> + #include <include/vtysh-generic-interface-tagNode.xml.i> + <node name="neighbor"> + <properties> + <help>Show OpenFabric neighbor adjacencies</help> + </properties> + <children> + #include <include/vtysh-generic-detail.xml.i> + </children> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </node> + <leafNode name="summary"> + <properties> + <help>Show OpenFabric information summary</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/python/vyos/frr.py b/python/vyos/frr.py index e7743e9d5..6fb81803f 100644 --- a/python/vyos/frr.py +++ b/python/vyos/frr.py @@ -87,7 +87,7 @@ LOG.addHandler(ch) LOG.addHandler(ch2) _frr_daemons = ['zebra', 'staticd', 'bgpd', 'ospfd', 'ospf6d', 'ripd', 'ripngd', - 'isisd', 'pimd', 'pim6d', 'ldpd', 'eigrpd', 'babeld', 'bfdd'] + 'isisd', 'pimd', 'pim6d', 'ldpd', 'eigrpd', 'babeld', 'bfdd', 'fabricd'] path_vtysh = '/usr/bin/vtysh' path_frr_reload = '/usr/lib/frr/frr-reload.py' diff --git a/smoketest/scripts/cli/test_protocols_openfabric.py b/smoketest/scripts/cli/test_protocols_openfabric.py new file mode 100644 index 000000000..e37aed456 --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_openfabric.py @@ -0,0 +1,186 @@ +#!/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 <http://www.gnu.org/licenses/>. + +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM +from vyos.configsession import ConfigSessionError +from vyos.utils.process import process_named_running + +PROCESS_NAME = 'fabricd' +base_path = ['protocols', 'openfabric'] + +domain = 'VyOS' +net = '49.0001.1111.1111.1111.00' +dummy_if = 'dum1234' +address_families = ['ipv4', 'ipv6'] + +path = base_path + ['domain', domain] + +class TestProtocolsOpenFabric(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + # call base-classes classmethod + super(TestProtocolsOpenFabric, cls).setUpClass() + # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same + cls.daemon_pid = process_named_running(PROCESS_NAME) + # 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() + + # check process health and continuity + self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) + + def openfabric_base_config(self): + self.cli_set(['interfaces', 'dummy', dummy_if]) + self.cli_set(base_path + ['net', net]) + for family in address_families: + self.cli_set(path + ['interface', dummy_if, 'address-family', family]) + + def test_openfabric_01_router_params(self): + fabric_tier = '5' + lsp_gen_interval = '20' + + self.cli_set(base_path) + + # verify() - net id and domain name are mandatory + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.openfabric_base_config() + + self.cli_set(path + ['log-adjacency-changes']) + self.cli_set(path + ['set-overload-bit']) + self.cli_set(path + ['fabric-tier', fabric_tier]) + self.cli_set(path + ['lsp-gen-interval', lsp_gen_interval]) + + # Commit all changes + self.cli_commit() + + # Verify all changes + tmp = self.getFRRconfig(f'router openfabric {domain}', daemon='fabricd') + self.assertIn(f' net {net}', tmp) + self.assertIn(f' log-adjacency-changes', tmp) + self.assertIn(f' set-overload-bit', tmp) + self.assertIn(f' fabric-tier {fabric_tier}', tmp) + self.assertIn(f' lsp-gen-interval {lsp_gen_interval}', tmp) + + tmp = self.getFRRconfig(f'interface {dummy_if}', daemon='fabricd') + self.assertIn(f' ip router openfabric {domain}', tmp) + self.assertIn(f' ipv6 router openfabric {domain}', tmp) + + def test_openfabric_02_loopback_interface(self): + interface = 'lo' + hello_interval = '100' + metric = '24478' + + self.openfabric_base_config() + self.cli_set(path + ['interface', interface, 'address-family', 'ipv4']) + + self.cli_set(path + ['interface', interface, 'hello-interval', hello_interval]) + self.cli_set(path + ['interface', interface, 'metric', metric]) + + # Commit all changes + self.cli_commit() + + # Verify FRR openfabric configuration + tmp = self.getFRRconfig(f'router openfabric {domain}', daemon='fabricd') + self.assertIn(f'router openfabric {domain}', tmp) + self.assertIn(f' net {net}', tmp) + + # Verify interface configuration + tmp = self.getFRRconfig(f'interface {interface}', daemon='fabricd') + self.assertIn(f' ip router openfabric {domain}', tmp) + # for lo interface 'openfabric passive' is implied + self.assertIn(f' openfabric passive', tmp) + self.assertIn(f' openfabric metric {metric}', tmp) + + def test_openfabric_03_password(self): + password = 'foo' + + self.openfabric_base_config() + + self.cli_set(path + ['interface', dummy_if, 'password', 'plaintext-password', f'{password}-{dummy_if}']) + self.cli_set(path + ['interface', dummy_if, 'password', 'md5', f'{password}-{dummy_if}']) + + # verify() - can not use both md5 and plaintext-password for password for the interface + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(path + ['interface', dummy_if, 'password', 'md5']) + + self.cli_set(path + ['domain-password', 'plaintext-password', password]) + self.cli_set(path + ['domain-password', 'md5', password]) + + # verify() - can not use both md5 and plaintext-password for domain-password + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(path + ['domain-password', 'md5']) + + # Commit all changes + self.cli_commit() + + # Verify all changes + tmp = self.getFRRconfig(f'router openfabric {domain}', daemon='fabricd') + self.assertIn(f' net {net}', tmp) + self.assertIn(f' domain-password clear {password}', tmp) + + tmp = self.getFRRconfig(f'interface {dummy_if}', daemon='fabricd') + self.assertIn(f' openfabric password clear {password}-{dummy_if}', tmp) + + def test_openfabric_multiple_domains(self): + domain_2 = 'VyOS_2' + interface = 'dum5678' + new_path = base_path + ['domain', domain_2] + + self.openfabric_base_config() + + # set same interface for 2 OpenFabric domains + self.cli_set(['interfaces', 'dummy', interface]) + self.cli_set(new_path + ['interface', interface, 'address-family', 'ipv4']) + self.cli_set(path + ['interface', interface, 'address-family', 'ipv4']) + + # verify() - same interface can be used only for one OpenFabric instance + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(path + ['interface', interface]) + + # Commit all changes + self.cli_commit() + + # Verify FRR openfabric configuration + tmp = self.getFRRconfig(f'router openfabric {domain}', daemon='fabricd') + self.assertIn(f'router openfabric {domain}', tmp) + self.assertIn(f' net {net}', tmp) + + tmp = self.getFRRconfig(f'router openfabric {domain_2}', daemon='fabricd') + self.assertIn(f'router openfabric {domain_2}', tmp) + self.assertIn(f' net {net}', tmp) + + # Verify interface configuration + tmp = self.getFRRconfig(f'interface {dummy_if}', daemon='fabricd') + self.assertIn(f' ip router openfabric {domain}', tmp) + self.assertIn(f' ipv6 router openfabric {domain}', tmp) + + tmp = self.getFRRconfig(f'interface {interface}', daemon='fabricd') + self.assertIn(f' ip router openfabric {domain_2}', tmp) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/src/conf_mode/protocols_openfabric.py b/src/conf_mode/protocols_openfabric.py new file mode 100644 index 000000000..8e8c50c06 --- /dev/null +++ b/src/conf_mode/protocols_openfabric.py @@ -0,0 +1,145 @@ +#!/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 <http://www.gnu.org/licenses/>. + +from sys import exit + +from vyos.base import Warning +from vyos.config import Config +from vyos.configdict import node_changed +from vyos.configverify import verify_interface_exists +from vyos.template import render_to_string +from vyos import ConfigError +from vyos import frr +from vyos import airbag + +airbag.enable() + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base_path = ['protocols', 'openfabric'] + + openfabric = conf.get_config_dict(base_path, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + # Remove per domain MPLS configuration - get a list of all changed Openfabric domains + # (removed and added) so that they will be properly rendered for the FRR config. + openfabric['domains_all'] = list(conf.list_nodes(' '.join(base_path) + f' domain') + + node_changed(conf, base_path + ['domain'])) + + # Get a list of all interfaces + openfabric['interfaces_all'] = [] + for domain in openfabric['domains_all']: + interfaces_modified = list(node_changed(conf, base_path + ['domain', domain, 'interface']) + + conf.list_nodes(' '.join(base_path) + f' domain {domain} interface')) + openfabric['interfaces_all'].extend(interfaces_modified) + + if not conf.exists(base_path): + openfabric.update({'deleted': ''}) + + return openfabric + +def verify(openfabric): + # bail out early - looks like removal from running config + if not openfabric or 'deleted' in openfabric: + return None + + if 'net' not in openfabric: + raise ConfigError('Network entity is mandatory!') + + # last byte in OpenFabric area address must be 0 + tmp = openfabric['net'].split('.') + if int(tmp[-1]) != 0: + raise ConfigError('Last byte of OpenFabric network entity title must always be 0!') + + if 'domain' not in openfabric: + raise ConfigError('OpenFabric domain name is mandatory!') + + interfaces_used = [] + + for domain, domain_config in openfabric['domain'].items(): + # If interface not set + if 'interface' not in domain_config: + raise ConfigError(f'Interface used for routing updates in OpenFabric "{domain}" is mandatory!') + + for iface, iface_config in domain_config['interface'].items(): + verify_interface_exists(openfabric, iface) + + # interface can be activated only on one OpenFabric instance + if iface in interfaces_used: + raise ConfigError(f'Interface {iface} is already used in different OpenFabric instance!') + + if 'address_family' not in iface_config or len(iface_config['address_family']) < 1: + raise ConfigError(f'Need to specify address family for the interface "{iface}"!') + + # If md5 and plaintext-password set at the same time + if 'password' in iface_config: + if {'md5', 'plaintext_password'} <= set(iface_config['password']): + raise ConfigError(f'Can use either md5 or plaintext-password for password for the interface!') + + if iface == 'lo' and 'passive' not in iface_config: + Warning('For loopback interface passive mode is implied!') + + interfaces_used.append(iface) + + # If md5 and plaintext-password set at the same time + password = 'domain_password' + if password in domain_config: + if {'md5', 'plaintext_password'} <= set(domain_config[password]): + raise ConfigError(f'Can use either md5 or plaintext-password for domain-password!') + + return None + +def generate(openfabric): + if not openfabric or 'deleted' in openfabric: + return None + + openfabric['frr_fabricd_config'] = render_to_string('frr/fabricd.frr.j2', 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) + for domain in openfabric['domains_all']: + frr_cfg.modify_section(f'^router openfabric {domain}', stop_pattern='^exit', remove_stop_mark=True) + + for interface in openfabric['interfaces_all']: + frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True) + + if 'frr_fabricd_config' in openfabric: + frr_cfg.add_before(frr.default_add_before, openfabric['frr_fabricd_config']) + + frr_cfg.commit_configuration(openfabric_daemon) + + 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/op_mode/restart_frr.py b/src/op_mode/restart_frr.py index 8841b0eca..83146f5ec 100755 --- a/src/op_mode/restart_frr.py +++ b/src/op_mode/restart_frr.py @@ -139,7 +139,7 @@ def _reload_config(daemon): # define program arguments cmd_args_parser = argparse.ArgumentParser(description='restart frr daemons') cmd_args_parser.add_argument('--action', choices=['restart'], required=True, help='action to frr daemons') -cmd_args_parser.add_argument('--daemon', choices=['zebra', 'staticd', 'bgpd', 'eigrpd', 'ospfd', 'ospf6d', 'ripd', 'ripngd', 'isisd', 'pimd', 'pim6d', 'ldpd', 'babeld', 'bfdd'], required=False, nargs='*', help='select single or multiple daemons') +cmd_args_parser.add_argument('--daemon', choices=['zebra', 'staticd', 'bgpd', 'eigrpd', 'ospfd', 'ospf6d', 'ripd', 'ripngd', 'isisd', 'pimd', 'pim6d', 'ldpd', 'babeld', 'bfdd', 'fabricd'], required=False, nargs='*', help='select single or multiple daemons') # parse arguments cmd_args = cmd_args_parser.parse_args() |