From 0ddd684ff12b297313331a1c87f36d8308eade8d Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 22 Jan 2021 16:31:20 +0100 Subject: ospf: T3236: add default values --- interface-definitions/include/ospf-metric.xml.i | 6 +- interface-definitions/protocols-ospf.xml.in | 95 ++++++++++--------------- src/conf_mode/protocols_ospf.py | 25 +++++++ 3 files changed, 68 insertions(+), 58 deletions(-) mode change 100644 => 100755 src/conf_mode/protocols_ospf.py diff --git a/interface-definitions/include/ospf-metric.xml.i b/interface-definitions/include/ospf-metric.xml.i index 771fda02d..b2812ba36 100644 --- a/interface-definitions/include/ospf-metric.xml.i +++ b/interface-definitions/include/ospf-metric.xml.i @@ -13,15 +13,16 @@ - OSPF metric type for default routes + OSPF metric type for default routes (default: 2) u32:1-2 - Metric type for default routes (default 2) + Metric type for default routes + 2 @@ -31,3 +32,4 @@ + diff --git a/interface-definitions/protocols-ospf.xml.in b/interface-definitions/protocols-ospf.xml.in index 4d5b84be0..04ad39732 100644 --- a/interface-definitions/protocols-ospf.xml.in +++ b/interface-definitions/protocols-ospf.xml.in @@ -3,7 +3,7 @@ - + Open Shortest Path First protocol (OSPF) parameters 620 @@ -109,7 +109,7 @@ - Nssa-abr + Configure NSSA-ABR (default: candidate) always candidate never @@ -129,6 +129,7 @@ ^(always|candidate|never)$ + candidate @@ -353,7 +354,7 @@ - Calculate OSPF interface cost according to bandwidth + Calculate OSPF interface cost according to bandwidth (default: 100) @@ -361,12 +362,13 @@ Reference bandwidth method to assign OSPF cost u32:1-4294967 - Reference bandwidth cost in Mbits/sec (default 100) + Reference bandwidth cost in Mbits/sec + 100 @@ -386,38 +388,7 @@ - - - OSPF default metric - - u32:0-16777214 - Default metric - - - - - - - - - OSPF metric type for default routes - - u32:1-2 - Metric type for default routes (default 2) - - - - - - - - - Route map reference - - policy route-map - - - + #include @@ -589,27 +560,29 @@ - Dead neighbor polling interval + Dead neighbor polling interval (default: 60) u32:1-65535 - Seconds between dead neighbor polling interval (default 60) + Seconds between dead neighbor polling interval + 60 - Neighbor priority in seconds + Neighbor priority in seconds (default: 0) u32:0-255 - Neighbor priority (default 0) + Neighbor priority + 0 @@ -620,7 +593,7 @@ - OSPF ABR type + OSPF ABR type (default: cisco) cisco ibm shortcut standard @@ -644,6 +617,7 @@ ^(cisco|ibm|shortcut|standard)$ + cisco @@ -674,31 +648,37 @@ Suppress routing updates on an interface + + default + + - <interface> + txt Interface to be passive (i.e. suppress routing updates) default Default to suppress routing updates on all interfaces - - default - - + + ^(br|bond|dum|en|eth|gnv|peth|tun|vti|vxlan|wg|wlan)[0-9]+|lo|default$ + Interface to exclude when using 'passive-interface default' - - <interface> - Interface to be passive (i.e. suppress routing updates) - + + txt + Interface to be passive (i.e. suppress routing updates) + + + ^(br|bond|dum|en|eth|gnv|peth|tun|vti|vxlan|wg|wlan)[0-9]+|lo$ + @@ -793,39 +773,42 @@ - Delay (msec) from first change received till SPF calculation + Delay from first change received till SPF calculation (default: 200) u32:0-600000 - Delay in msec (default 200) + Delay in milliseconds + 200 - Initial hold time(msec) between consecutive SPF calculations + Initial hold time between consecutive SPF calculations (default: 1000) u32:0-600000 - Initial hold time in msec (default 1000) + Initial hold time in milliseconds + 1000 - Maximum hold time (msec) + Maximum hold time (default: 10000) u32:0-600000 - Max hold time in msec (default 10000) + Max hold time in milliseconds + 10000 diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py old mode 100644 new mode 100755 index 73c244571..fc66a884c --- a/src/conf_mode/protocols_ospf.py +++ b/src/conf_mode/protocols_ospf.py @@ -24,6 +24,7 @@ from vyos.template import render from vyos.template import render_to_string from vyos.util import call from vyos.util import dict_search +from vyos.xml import defaults from vyos import ConfigError from vyos import frr from vyos import airbag @@ -43,6 +44,30 @@ def get_config(): conf = Config() base = ['protocols', 'ospf'] ospf = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + + # Bail out early if configuration tree does not exist + if not conf.exists(base): + 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 = defaults(base) + + # 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 dict_search('area.area_type.nssa', ospf) is None: + del default_values['area']['area_type']['nssa'] + for protocol in ['bgp', 'connected', 'kernel', 'rip', 'static']: + if dict_search(f'redistribute.{protocol}', ospf) is None: + del default_values['redistribute'][protocol] + + ospf = dict_merge(default_values, ospf) + return ospf def verify(ospf): -- cgit v1.2.3 From 93c0416efc685424a0750fe9cb9728d5e6450073 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 22 Jan 2021 16:33:12 +0100 Subject: ospf: T3236: support processing by vyos-configd --- data/configd-include.json | 1 + src/conf_mode/protocols_ospf.py | 17 +++++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/data/configd-include.json b/data/configd-include.json index 8369f0e13..318ab0e14 100644 --- a/data/configd-include.json +++ b/data/configd-include.json @@ -34,6 +34,7 @@ "protocols_igmp.py", "protocols_isis.py", "protocols_mpls.py", +"protocols_ospf.py", "protocols_pim.py", "protocols_rip.py", "protocols_static_multicast.py", diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py index fc66a884c..f47b21498 100755 --- a/src/conf_mode/protocols_ospf.py +++ b/src/conf_mode/protocols_ospf.py @@ -31,6 +31,7 @@ from vyos import airbag airbag.enable() config_file = r'/tmp/ospf.frr' +frr_daemon = 'ospfd' DEBUG = os.path.exists('/tmp/ospf.debug') if DEBUG: @@ -40,8 +41,11 @@ if DEBUG: ch = logging.StreamHandler() lg.addHandler(ch) -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base = ['protocols', 'ospf'] ospf = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) @@ -90,8 +94,9 @@ def generate(ospf): def apply(ospf): # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(daemon='ospfd') - frr_cfg.modify_section(f'router ospf', '') + frr_cfg.load_configuration(frr_daemon) + frr_cfg.modify_section('router ospf', '') + frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', ospf['new_frr_config']) # Debugging if DEBUG: @@ -107,13 +112,13 @@ def apply(ospf): print(f'Modified config:\n') print(f'{frr_cfg}') - frr_cfg.commit_configuration(daemon='ospfd') + frr_cfg.commit_configuration(frr_daemon) # If FRR config is blank, rerun the blank commit x times due to frr-reload # behavior/bug not properly clearing out on one commit. if ospf['new_frr_config'] == '': for a in range(5): - frr_cfg.commit_configuration(daemon='ospfd') + frr_cfg.commit_configuration(frr_daemon) return None -- cgit v1.2.3 From 4ed4d822cfd1d1aad19982783066a5e2431889b4 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 22 Jan 2021 17:38:22 +0100 Subject: ospf: T3236: provide full protocol support in XML and Python This commit provides the implementation of the OSPF CLI with a Jinja2 template that is loaded by FRR reload. It also contains some initial smoketests. There is yet no verify() implementation! --- Makefile | 1 - data/templates/frr/ospf.frr.tmpl | 129 +++++++++ .../include/ospf-metric-type.xml.i | 15 ++ interface-definitions/include/ospf-metric.xml.i | 21 -- interface-definitions/include/ospf-route-map.xml.i | 10 + interface-definitions/protocols-ospf.xml.in | 63 +++-- smoketest/configs/ospf-config | 115 ++++++++ smoketest/scripts/cli/test_protocols_ospf.py | 296 +++++++++++++++++++++ src/conf_mode/protocols_ospf.py | 21 ++ 9 files changed, 633 insertions(+), 38 deletions(-) create mode 100644 interface-definitions/include/ospf-metric-type.xml.i create mode 100644 interface-definitions/include/ospf-route-map.xml.i create mode 100644 smoketest/configs/ospf-config create mode 100755 smoketest/scripts/cli/test_protocols_ospf.py diff --git a/Makefile b/Makefile index a063d1df6..66b1e8bb7 100644 --- a/Makefile +++ b/Makefile @@ -47,7 +47,6 @@ interface_definitions: $(config_xml_obj) rm -f $(TMPL_DIR)/vpn/node.def rm -f $(TMPL_DIR)/vpn/ipsec/node.def rm -rf $(TMPL_DIR)/vpn/nipsec - rm -rf $(TMPL_DIR)/protocols/nospf # XXX: required until OSPF and RIP is migrated from vyatta-cfg-quagga to vyos-1x mkdir $(TMPL_DIR)/interfaces/loopback/node.tag/ipv6 diff --git a/data/templates/frr/ospf.frr.tmpl b/data/templates/frr/ospf.frr.tmpl index 465034f15..07699290c 100644 --- a/data/templates/frr/ospf.frr.tmpl +++ b/data/templates/frr/ospf.frr.tmpl @@ -1,2 +1,131 @@ ! +router ospf +{% if access_list is defined and access_list is not none %} +{% for acl, acl_config in access_list.items() %} +{% for protocol in acl_config.export if acl_config.export is defined %} + distribute-list {{ acl }} out {{ protocol }} +{% endfor %} +{% endfor %} +{% endif %} +{% if area is defined and area is not none %} +{% for area_id, area_config in area.items() %} +{% if area_config.area_type is defined and area_config.area_type is not none %} +{% for type, type_config in area_config.area_type.items() if type != 'normal' %} + area {{ area_id }} {{ type }} {{ 'no-summary' if type_config.no_summary is defined }} +{% if type_config.default_cost is defined and type_config.default_cost is not none %} + area {{ area_id }} default-cost {{ type_config.default_cost }} +{% endif %} +{% endfor %} +{% endif %} +{% if area_config.authentication is defined and area_config.authentication is not none %} + area {{ area_id }} authentication {{ 'message-digest' if area_config.authentication == 'md5' }} +{% endif %} +{% for network in area_config.network if area_config.network is defined %} + network {{ network }} area {{ area_id }} +{% endfor %} +{% if area_config.range is defined and area_config.range is not none %} +{% for range, range_config in area_config.range.items() %} +{% if range_config.cost is defined and range_config.cost is not none %} + area {{ area_id }} range {{ range }} cost {{ range_config.cost }} +{% endif %} +{% if range_config.not_advertise is defined %} + area {{ area_id }} range {{ range }} not-advertise +{% endif %} +{% if range_config.substitute is defined and range_config.substitute is not none %} + area {{ area_id }} range {{ range }} substitute {{ range_config.substitute }} +{% endif %} +{% endfor %} +{% endif %} +{% if area_config.shortcut is defined and area_config.shortcut is not none %} + area {{ area_id }} shortcut {{ area_config.shortcut }} +{% endif %} +{% if area_config.virtual_link is defined and area_config.virtual_link is not none %} +{% for link, link_config in area_config.virtual_link.items() %} +{% if link_config.authentication is defined and link_config.authentication is not none %} +{% if link_config.authentication.plaintext_password is defined and link_config.authentication.plaintext_password is not none %} + area {{ area_id }} virtual-link {{ link }} authentication-key {{ link_config.authentication.plaintext_password }} +{% elif link_config.authentication.md5 is defined and link_config.authentication.md5.key_id is defined and link_config.authentication.md5.key_id is not none %} +{% for key, key_config in link_config.authentication.md5.key_id.items() %} + area {{ area_id }} virtual-link {{ link }} message-digest-key {{ key }} md5 {{ key_config.md5_key }} +{% endfor %} +{% endif %} +{% endif %} +{% if link_config.dead_interval is defined and link_config.dead_interval is not none %} +{# The following values are default values #} + area {{ area_id }} virtual-link {{ link }} hello-interval {{ link_config.hello_interval }} retransmit-interval {{ link_config.retransmit_interval }} transmit-delay {{ link_config.retransmit_interval }} dead-interval {{ link_config.dead_interval }} +{% endif %} +{% endfor %} +{% endif %} +{% endfor %} +{% endif %} +{% if auto_cost is defined and auto_cost.reference_bandwidth is defined and auto_cost.reference_bandwidth is not none %} + auto-cost reference-bandwidth {{ auto_cost.reference_bandwidth }} +{% endif %} +{% if default_information is defined and default_information.originate is defined and default_information.originate is not none %} + default-information originate {{ 'always' if default_information.originate.always is defined }} {{ 'metric ' + default_information.originate.metric if default_information.originate.metric is defined }} {{ 'metric-type ' + default_information.originate.metric_type if default_information.originate.metric_type is defined }} {{ 'route-map ' + default_information.originate.route_map if default_information.originate.route_map is defined }} +{% endif %} +{% if default_metric is defined and default_metric is not none %} + default-metric {{ default_metric }} +{% endif %} +{% if distance is defined and distance is not none %} +{% if distance.global is defined and distance.global is not none %} + distance {{ distance.global }} +{% endif %} +{% if distance.ospf is defined and distance.ospf is not none %} + distance ospf {{ 'intra-area ' + distance.ospf.intra_area if distance.ospf.intra_area is defined }} {{ 'inter-area ' + distance.ospf.inter_area if distance.ospf.inter_area is defined }} {{ 'external ' + distance.ospf.external if distance.ospf.external is defined }} +{% endif %} +{% endif %} +{% if log_adjacency_changes is defined %} + log-adjacency-changes {{ "detail" if log_adjacency_changes.detail is defined }} +{% endif %} +{% if max_metric is defined and max_metric.router_lsa is defined and max_metric.router_lsa is not none %} +{% if max_metric.router_lsa.administrative is defined %} + max-metric router-lsa administrative +{% endif %} +{% if max_metric.router_lsa.on_shutdown is defined and max_metric.router_lsa.on_shutdown is not none %} + max-metric router-lsa on-shutdown {{ max_metric.router_lsa.on_shutdown }} +{% endif %} +{% if max_metric.router_lsa.on_startup is defined and max_metric.router_lsa.on_startup is not none %} + max-metric router-lsa on-startup {{ max_metric.router_lsa.on_startup }} +{% endif %} +{% endif %} +{% if mpls_te is defined and mpls_te.enable is defined %} + mpls-te on + mpls-te router-address {{ mpls_te.router_address }} +{% endif %} +{% if neighbor is defined and neighbor is not none%} +{% for address, address_config in neighbor.items() %} + neighbor {{ address }} {{ 'priority ' + address_config.priority if address_config.priority is defined }} {{ 'poll-interval ' + address_config.poll_interval if address_config.poll_interval is defined }} +{% endfor %} +{% endif %} +{% if parameters is defined and parameters is not none %} +{% if parameters.abr_type is defined and parameters.abr_type is not none %} + ospf abr-type {{ parameters.abr_type }} +{% endif %} +{% if parameters.router_id is defined and parameters.router_id is not none %} + ospf router-id {{ parameters.router_id }} +{% endif %} +{% endif %} +{% for interface in passive_interface if passive_interface is defined %} + passive-interface {{ interface }} +{% endfor %} +{% for interface in passive_interface_exclude if passive_interface_exclude is defined %} + no passive-interface {{ interface }} +{% endfor %} +{% if redistribute is defined and redistribute is not none %} +{% for protocol, options in redistribute.items() %} + redistribute {{ protocol }} {{ 'metric ' + options.metric if options.metric is defined }} {{ 'metric-type ' + options.metric_type if options.metric_type is defined }} {{ 'route-map ' + options.route_map if options.route_map is defined }} +{% endfor %} +{% endif %} +{% if refresh is defined and refresh.timers is defined and refresh.timers is not none %} + refresh timer {{ refresh.timers }} +{% endif %} +{% if timers is defined and timers.throttle is defined and timers.throttle.spf is defined and timers.throttle.spf is not none %} +{# Timer values have default values #} + timers throttle spf {{ timers.throttle.spf.delay }} {{ timers.throttle.spf.initial_holdtime }} {{ timers.throttle.spf.max_holdtime }} +{% endif %} +! +{% if route_map is defined and route_map is not none %} +ip protocol ospf route-map {{ route_map }} +{% endif %} ! diff --git a/interface-definitions/include/ospf-metric-type.xml.i b/interface-definitions/include/ospf-metric-type.xml.i new file mode 100644 index 000000000..50f11960c --- /dev/null +++ b/interface-definitions/include/ospf-metric-type.xml.i @@ -0,0 +1,15 @@ + + + + OSPF metric type for default routes (default: 2) + + u32:1-2 + Metric type for default routes + + + + + + 2 + + diff --git a/interface-definitions/include/ospf-metric.xml.i b/interface-definitions/include/ospf-metric.xml.i index b2812ba36..3ce12e877 100644 --- a/interface-definitions/include/ospf-metric.xml.i +++ b/interface-definitions/include/ospf-metric.xml.i @@ -11,25 +11,4 @@ - - - OSPF metric type for default routes (default: 2) - - u32:1-2 - Metric type for default routes - - - - - - 2 - - - - Route map reference - - policy route-map - - - diff --git a/interface-definitions/include/ospf-route-map.xml.i b/interface-definitions/include/ospf-route-map.xml.i new file mode 100644 index 000000000..8dc5b37da --- /dev/null +++ b/interface-definitions/include/ospf-route-map.xml.i @@ -0,0 +1,10 @@ + + + + Route map reference + + policy route-map + + + + diff --git a/interface-definitions/protocols-ospf.xml.in b/interface-definitions/protocols-ospf.xml.in index 04ad39732..074d0db63 100644 --- a/interface-definitions/protocols-ospf.xml.in +++ b/interface-definitions/protocols-ospf.xml.in @@ -5,13 +5,16 @@ - Open Shortest Path First protocol (OSPF) parameters + Open Shortest Path First (OSPF) 620 Access list to filter networks in routing updates + + policy access-list + u32 Access-list number @@ -195,6 +198,13 @@ Summarize routes matching prefix (border routers only) + + ipv4net + Area range prefix + + + + @@ -255,6 +265,14 @@ Virtual link + + ipv4 + OSPF area in dotted decimal notation + + + + + @@ -302,7 +320,7 @@ - Interval after which a neighbor is declared dead + Interval after which a neighbor is declared dead (default: 40) u32:1-65535 Neighbor dead interval (seconds) @@ -311,10 +329,11 @@ + 40 - Interval between hello packets + Interval between hello packets (default: 10) u32:1-65535 Hello interval (seconds) @@ -323,10 +342,11 @@ + 10 - Interval between retransmitting lost link state advertisements + Interval between retransmitting lost link state advertisements (default: 5) u32:1-65535 Retransmit interval (seconds) @@ -335,10 +355,11 @@ + 5 - Link state transmit delay + Link state transmit delay (default: 1) u32:1-65535 Link state transmit delay (seconds) @@ -347,6 +368,7 @@ + 1 @@ -389,6 +411,8 @@ #include + #include + #include @@ -500,11 +524,11 @@ Advertise stub-router prior to full shutdown of OSPF - u32:5-86400 + u32:5-100 Time (seconds) to advertise self as stub-router - + @@ -546,16 +570,20 @@ + 0.0.0.0 - Neighbor IP address + Specify neighbor router ipv4 Neighbor IP address + + + @@ -693,6 +721,8 @@ #include + #include + #include @@ -701,6 +731,8 @@ #include + #include + #include @@ -709,6 +741,8 @@ #include + #include + #include @@ -717,6 +751,8 @@ #include + #include + #include @@ -725,6 +761,8 @@ #include + #include + #include @@ -748,14 +786,7 @@ - - - Filter routes installed in local route map - - policy route-map - - - + #include Adjust routing timers diff --git a/smoketest/configs/ospf-config b/smoketest/configs/ospf-config new file mode 100644 index 000000000..9e18e6155 --- /dev/null +++ b/smoketest/configs/ospf-config @@ -0,0 +1,115 @@ +interfaces { + dummy dum0 { + address 172.18.254.201/32 + } + ethernet eth0 { + duplex auto + smp-affinity auto + speed auto + vif 201 { + address 172.18.201.10/24 + ip { + ospf { + authentication { + md5 { + key-id 10 { + md5-key OSPFVyOSNET + } + } + } + dead-interval 40 + hello-interval 10 + priority 1 + retransmit-interval 5 + transmit-delay 1 + } + } + } + } + ethernet eth1 { + duplex auto + smp-affinity auto + speed auto + } +} +protocols { + ospf { + area 0 { + network 172.18.201.0/24 + network 172.18.254.201/32 + } + log-adjacency-changes { + } + parameters { + abr-type cisco + router-id 172.18.254.201 + } + passive-interface default + passive-interface-exclude eth0.201 + } + static { + route 0.0.0.0/0 { + next-hop 172.18.201.254 { + distance 10 + } + } + } +} +service { + lldp { + interface all { + } + } + snmp { + community public { + authorization ro + network 172.16.100.0/24 + } + contact "VyOS maintainers and contributors " + location "Jenkins" + } + ssh { + disable-host-validation + port 22 + } +} +system { + config-management { + commit-revisions 200 + } + domain-name vyos.net + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/ + plaintext-password "" + } + level admin + } + } + name-server 172.16.254.30 + ntp { + server 0.pool.ntp.org { + } + server 1.pool.ntp.org { + } + server 2.pool.ntp.org { + } + } + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } + time-zone Europe/Berlin +} + +/* Warning: Do not remove the following line. */ +/* === vyatta-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@2:dhcp-server@5:dns-forwarding@1:firewall@5:ipsec@5:l2tp@1:mdns@1:nat@4:ntp@1:pptp@1:qos@1:quagga@6:snmp@1:ssh@1:system@9:vrrp@2:wanloadbalance@3:webgui@1:webproxy@1:webproxy@2:zone-policy@1" === */ +/* Release version: 1.2.6 */ diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py new file mode 100755 index 000000000..f5fc75084 --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_ospf.py @@ -0,0 +1,296 @@ +#!/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 . + +import os +import unittest + +from vyos.configsession import ConfigSession +from vyos.ifconfig import Section +from vyos.util import cmd +from vyos.util import process_named_running + +PROCESS_NAME = 'ospfd' +base_path = ['protocols', 'ospf'] + +route_map = 'foobarbaz' +route_map_seq = '10' + +def getFRROSPFconfig(): + return cmd('vtysh -c "show run" | sed -n "/router ospf/,/^!/p"') + +class TestProtocolsOSPF(unittest.TestCase): + def setUp(self): + self.session = ConfigSession(os.getpid()) + self.session.set(['policy', 'route-map', route_map, 'rule', route_map_seq, 'action', 'permit']) + + def tearDown(self): + self.session.delete(['policy', 'route-map', route_map]) + self.session.delete(base_path) + self.session.commit() + del self.session + + def test_ospf_01_defaults(self): + # commit changes + self.session.set(base_path) + self.session.commit() + + # Verify FRR ospfd configuration + frrconfig = getFRROSPFconfig() + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' auto-cost reference-bandwidth 100', frrconfig) + self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_ospf_02_simple(self): + router_id = '127.0.0.1' + abr_type = 'ibm' + bandwidth = '1000' + metric = '123' + + self.session.set(base_path + ['auto-cost', 'reference-bandwidth', bandwidth]) + self.session.set(base_path + ['parameters', 'router-id', router_id]) + self.session.set(base_path + ['parameters', 'abr-type', abr_type]) + self.session.set(base_path + ['log-adjacency-changes', 'detail']) + self.session.set(base_path + ['default-metric', metric]) + + # commit changes + self.session.commit() + + # Verify FRR ospfd configuration + frrconfig = getFRROSPFconfig() + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' auto-cost reference-bandwidth {bandwidth}', frrconfig) + self.assertIn(f' ospf router-id {router_id}', frrconfig) + self.assertIn(f' ospf abr-type {abr_type}', frrconfig) + self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults + self.assertIn(f' default-metric {metric}', frrconfig) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_ospf_03_access_list(self): + acl = '100' + seq = '10' + protocols = ['bgp', 'connected', 'kernel', 'rip', 'static'] + + self.session.set(['policy', 'access-list', acl, 'rule', seq, 'action', 'permit']) + self.session.set(['policy', 'access-list', acl, 'rule', seq, 'source', 'any']) + self.session.set(['policy', 'access-list', acl, 'rule', seq, 'destination', 'any']) + for ptotocol in protocols: + self.session.set(base_path + ['access-list', acl, 'export', ptotocol]) + + # commit changes + self.session.commit() + + # Verify FRR ospfd configuration + frrconfig = getFRROSPFconfig() + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults + for ptotocol in protocols: + self.assertIn(f' distribute-list {acl} out {ptotocol}', frrconfig) # defaults + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + self.session.delete(['policy', 'access-list', acl]) + + def test_ospf_04_default_originate(self): + seq = '100' + metric = '50' + metric_type = '1' + + self.session.set(base_path + ['default-information', 'originate', 'metric', metric]) + self.session.set(base_path + ['default-information', 'originate', 'metric-type', metric_type]) + self.session.set(base_path + ['default-information', 'originate', 'route-map', route_map]) + + # commit changes + self.session.commit() + + # Verify FRR ospfd configuration + frrconfig = getFRROSPFconfig() + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults + self.assertIn(f' default-information originate metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) + + # Now set 'always' + self.session.set(base_path + ['default-information', 'originate', 'always']) + self.session.commit() + + # Verify FRR ospfd configuration + frrconfig = getFRROSPFconfig() + self.assertIn(f' default-information originate always metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_ospf_05_options(self): + global_distance = '128' + intra_area = '100' + inter_area = '110' + external = '120' + on_startup = '30' + on_shutdown = '60' + refresh = '50' + + self.session.set(base_path + ['distance', 'global', global_distance]) + self.session.set(base_path + ['distance', 'ospf', 'external', external]) + self.session.set(base_path + ['distance', 'ospf', 'intra-area', intra_area]) + + self.session.set(base_path + ['max-metric', 'router-lsa', 'on-startup', on_startup]) + self.session.set(base_path + ['max-metric', 'router-lsa', 'on-shutdown', on_shutdown]) + + self.session.set(base_path + ['mpls-te', 'enable']) + self.session.set(base_path + ['refresh', 'timers', refresh]) + + # commit changes + self.session.commit() + + # Verify FRR ospfd configuration + frrconfig = getFRROSPFconfig() + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' mpls-te on', frrconfig) + self.assertIn(f' mpls-te router-address 0.0.0.0', frrconfig) # default + self.assertIn(f' distance {global_distance}', frrconfig) + self.assertIn(f' distance ospf intra-area {intra_area} external {external}', frrconfig) + self.assertIn(f' max-metric router-lsa on-startup {on_startup}', frrconfig) + self.assertIn(f' max-metric router-lsa on-shutdown {on_shutdown}', frrconfig) + self.assertIn(f' refresh timer {refresh}', frrconfig) + + + # enable inter-area + self.session.set(base_path + ['distance', 'ospf', 'inter-area', inter_area]) + self.session.commit() + + frrconfig = getFRROSPFconfig() + self.assertIn(f' distance ospf intra-area {intra_area} inter-area {inter_area} external {external}', frrconfig) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_ospf_06_neighbor(self): + priority = '10' + poll_interval = '20' + neighbors = ['1.1.1.1', '2.2.2.2', '3.3.3.3'] + for neighbor in neighbors: + self.session.set(base_path + ['neighbor', neighbor, 'priority', priority]) + self.session.set(base_path + ['neighbor', neighbor, 'poll-interval', poll_interval]) + + # commit changes + self.session.commit() + + # Verify FRR ospfd configuration + frrconfig = getFRROSPFconfig() + self.assertIn(f'router ospf', frrconfig) + for neighbor in neighbors: + self.assertIn(f' neighbor {neighbor} priority {priority} poll-interval {poll_interval}', frrconfig) # default + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_ospf_07_passive_interface(self): + self.session.set(base_path + ['passive-interface', 'default']) + interfaces = Section.interfaces('ethernet') + for interface in interfaces: + self.session.set(base_path + ['passive-interface-exclude', interface]) + + # commit changes + self.session.commit() + + # Verify FRR ospfd configuration + frrconfig = getFRROSPFconfig() + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' passive-interface default', frrconfig) # default + for interface in interfaces: + self.assertIn(f' no passive-interface {interface}', frrconfig) # default + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_ospf_08_redistribute(self): + metric = '15' + metric_type = '1' + redistribute = ['bgp', 'connected', 'kernel', 'rip', 'static'] + + for protocol in redistribute: + self.session.set(base_path + ['redistribute', protocol, 'metric', metric]) + self.session.set(base_path + ['redistribute', protocol, 'route-map', route_map]) + if protocol not in ['kernel', 'static']: + self.session.set(base_path + ['redistribute', protocol, 'metric-type', metric_type]) + + # commit changes + self.session.commit() + + # Verify FRR ospfd configuration + frrconfig = getFRROSPFconfig() + self.assertIn(f'router ospf', frrconfig) + for protocol in redistribute: + if protocol in ['kernel', 'static']: + self.assertIn(f' redistribute {protocol} metric {metric} route-map {route_map}', frrconfig) + else: + self.assertIn(f' redistribute {protocol} metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_ospf_09_area(self): + area = '0' + networks = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'] + for network in networks: + self.session.set(base_path + ['area', area, 'network', network]) + + # commit changes + self.session.commit() + + # Verify FRR ospfd configuration + frrconfig = getFRROSPFconfig() + self.assertIn(f'router ospf', frrconfig) + for network in networks: + self.assertIn(f' network {network} area {area}', frrconfig) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_ospf_10_virtual_link(self): + area = '10' + shortcut = 'enable' + virtual_link = '192.0.2.1' + hello = '6' + retransmit = '5' + transmit = '5' + dead = '40' + + self.session.set(base_path + ['area', area, 'shortcut', shortcut]) + self.session.set(base_path + ['area', area, 'virtual-link', virtual_link, 'hello-interval', hello]) + self.session.set(base_path + ['area', area, 'virtual-link', virtual_link, 'retransmit-interval', retransmit]) + self.session.set(base_path + ['area', area, 'virtual-link', virtual_link, 'transmit-delay', transmit]) + self.session.set(base_path + ['area', area, 'virtual-link', virtual_link, 'dead-interval', dead]) + + # commit changes + self.session.commit() + + # Verify FRR ospfd configuration + frrconfig = getFRROSPFconfig() + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' area {area} shortcut {shortcut}', frrconfig) + self.assertIn(f' area {area} virtual-link {virtual_link} hello-interval {hello} retransmit-interval {retransmit} transmit-delay {transmit} dead-interval {dead}', frrconfig) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py index f47b21498..f43929b46 100755 --- a/src/conf_mode/protocols_ospf.py +++ b/src/conf_mode/protocols_ospf.py @@ -66,12 +66,33 @@ def get_config(config=None): del default_values['default_information'] if dict_search('area.area_type.nssa', ospf) is None: del default_values['area']['area_type']['nssa'] + if 'mpls_te' not in ospf: + del default_values['mpls_te'] for protocol in ['bgp', 'connected', 'kernel', 'rip', 'static']: if dict_search(f'redistribute.{protocol}', ospf) is None: del default_values['redistribute'][protocol] + # XXX: T2665: we currently have no nice way for defaults under tag nodes, + # clean them out and add them manually :( + del default_values['neighbor'] + del default_values['area']['virtual_link'] + # merge in remaining default values ospf = dict_merge(default_values, ospf) + if 'neighbor' in ospf: + default_values = defaults(base + ['neighbor']) + for neighbor in ospf['neighbor']: + ospf['neighbor'][neighbor] = dict_merge(default_values, ospf['neighbor'][neighbor]) + + if 'area' in ospf: + default_values = defaults(base + ['area', 'virtual-link']) + for area, area_config in ospf['area'].items(): + if 'virtual_link' in area_config: + print(default_values) + for virtual_link in area_config['virtual_link']: + ospf['area'][area]['virtual_link'][virtual_link] = dict_merge( + default_values, ospf['area'][area]['virtual_link'][virtual_link]) + return ospf def verify(ospf): -- cgit v1.2.3