Page MenuHomeVyOS Platform

No OneTemporary

Size
36 KB
Referenced Files
None
Subscribers
None
diff --git a/data/templates/frr/ospf.frr.tmpl b/data/templates/frr/ospf.frr.tmpl
index 140b6b406..a47c64c89 100644
--- a/data/templates/frr/ospf.frr.tmpl
+++ b/data/templates/frr/ospf.frr.tmpl
@@ -1,183 +1,183 @@
!
{% if interface is defined and interface is not none %}
{% for iface, iface_config in interface.items() %}
-interface {{ iface }}
+interface {{ iface }} {{ 'vrf ' + vrf if vrf is defined and vrf is not none }}
{% if iface_config.authentication is defined and iface_config.authentication is not none %}
{% if iface_config.authentication.plaintext_password is defined and iface_config.authentication.plaintext_password is not none %}
ip ospf authentication-key {{ iface_config.authentication.plaintext_password }}
{% elif iface_config.authentication.md5 is defined %}
ip ospf authentication message-digest
{% if iface_config.authentication.md5.key_id is defined and iface_config.authentication.md5.key_id is not none %}
{% for key, key_config in iface_config.authentication.md5.key_id.items() %}
ip ospf message-digest-key {{ key }} md5 {{ key_config.md5_key }}
{% endfor %}
{% endif %}
{% endif %}
{% endif %}
{% if iface_config.cost is defined and iface_config.cost is not none %}
ip ospf cost {{ iface_config.cost }}
{% endif %}
{% if iface_config.priority is defined and iface_config.priority is not none %}
ip ospf priority {{ iface_config.priority }}
{% endif %}
{% if iface_config.hello_interval is defined and iface_config.hello_interval is not none %}
ip ospf hello-interval {{ iface_config.hello_interval }}
{% endif %}
{% if iface_config.retransmit_interval is defined and iface_config.retransmit_interval is not none %}
ip ospf retransmit-interval {{ iface_config.retransmit_interval }}
{% endif %}
{% if iface_config.transmit_delay is defined and iface_config.transmit_delay is not none %}
ip ospf transmit-delay {{ iface_config.transmit_delay }}
{% endif %}
{% if iface_config.dead_interval is defined and iface_config.dead_interval is not none %}
ip ospf dead-interval {{ iface_config.dead_interval }}
{% elif iface_config.hello_multiplier is defined and iface_config.hello_multiplier is not none %}
ip ospf dead-interval minimal hello-multiplier {{ iface_config.hello_multiplier }}
{% endif %}
{% if iface_config.bfd is defined %}
ip ospf bfd
{% endif %}
{% if iface_config.mtu_ignore is defined %}
ip ospf mtu-ignore
{% endif %}
{% if iface_config.network is defined and iface_config.network is not none %}
ip ospf network {{ iface_config.network }}
{% endif %}
{% if iface_config.bandwidth is defined and iface_config.bandwidth is not none %}
bandwidth {{ iface_config.bandwidth }}
{% endif %}
!
{% endfor %}
{% endif %}
!
-router ospf
+router ospf {{ 'vrf ' + vrf if vrf is defined and vrf is not none }}
{% 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 %}
{# 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.transmit_delay }} dead-interval {{ link_config.dead_interval }}
{% 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 %}
{% if interface.startswith('vlink') %}
{% set interface = interface.upper() %}
{% endif %}
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/protocols-vrf.xml.in b/interface-definitions/protocols-vrf.xml.in
index 77297938b..e9ef5dc2a 100644
--- a/interface-definitions/protocols-vrf.xml.in
+++ b/interface-definitions/protocols-vrf.xml.in
@@ -1,35 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<interfaceDefinition>
<node name="protocols">
<children>
<tagNode name="vrf" owner="${vyos_conf_scripts_dir}/protocols_vrf.py">
<properties>
<help>Name of VRF to add route for</help>
<completionHelp>
<path>vrf name</path>
</completionHelp>
<valueHelp>
<format>txt</format>
<description>VRF instance name</description>
</valueHelp>
<constraint>
<validator name="vrf-name"/>
</constraint>
<constraintErrorMessage>VRF instance name must be 15 characters or less and can not\nbe named as regular network interfaces.\n</constraintErrorMessage>
</properties>
<children>
<node name="static">
<properties>
<help>Static route parameters</help>
</properties>
<children>
#include <include/static-route.xml.i>
#include <include/static-route6.xml.i>
</children>
</node>
+ <node name="ospf" owner="${vyos_conf_scripts_dir}/protocols_ospf.py $VAR(../@)">
+ <properties>
+ <help>Open Shortest Path First (OSPF)</help>
+ </properties>
+ <children>
+ #include <include/ospf/ospf-common-config.xml.i>
+ </children>
+ </node>
#include <include/vni.xml.i>
</children>
</tagNode>
</children>
</node>
</interfaceDefinition>
diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py
index d9a6c17e4..683ca12b8 100755
--- a/smoketest/scripts/cli/test_protocols_ospf.py
+++ b/smoketest/scripts/cli/test_protocols_ospf.py
@@ -1,312 +1,343 @@
#!/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/>.
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 = 'foo-bar-baz10'
-def getFRROSPFconfig():
+def getFRRconfig(vrf=None):
+ if vrf:
+ return cmd(f'vtysh -c "show run" | sed -n "/^router ospf vrf {vrf}/,/^!/p"')
return cmd('vtysh -c "show run" | sed -n "/^router ospf/,/^!/p"')
def getFRRInterfaceConfig(interface):
return cmd(f'vtysh -c "show run" | sed -n "/^interface {interface}$/,/^!/p"')
class TestProtocolsOSPF(unittest.TestCase):
def setUp(self):
self.session = ConfigSession(os.getpid())
self.session.set(['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit'])
self.session.set(['policy', 'route-map', route_map, 'rule', '20', 'action', 'permit'])
def tearDown(self):
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
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()
+ frrconfig = getFRRconfig()
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
-
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()
+ frrconfig = getFRRconfig()
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)
def test_ospf_03_access_list(self):
acl = '100'
seq = '10'
protocols = ['bgp', 'connected', 'isis', '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()
+ frrconfig = getFRRconfig()
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
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()
+ frrconfig = getFRRconfig()
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()
+ frrconfig = getFRRconfig()
self.assertIn(f' default-information originate always metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig)
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()
+ frrconfig = getFRRconfig()
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()
+ frrconfig = getFRRconfig()
self.assertIn(f' distance ospf intra-area {intra_area} inter-area {inter_area} external {external}', frrconfig)
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()
+ frrconfig = getFRRconfig()
self.assertIn(f'router ospf', frrconfig)
for neighbor in neighbors:
self.assertIn(f' neighbor {neighbor} priority {priority} poll-interval {poll_interval}', frrconfig) # default
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()
+ frrconfig = getFRRconfig()
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
def test_ospf_08_redistribute(self):
metric = '15'
metric_type = '1'
redistribute = ['bgp', 'connected', 'isis', '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()
+ frrconfig = getFRRconfig()
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)
def test_ospf_09_virtual_link(self):
networks = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16']
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])
for network in networks:
self.session.set(base_path + ['area', area, 'network', network])
# commit changes
self.session.commit()
# Verify FRR ospfd configuration
- frrconfig = getFRROSPFconfig()
+ frrconfig = getFRRconfig()
import pprint
# From time to time the CI fails with an error like:
# ======================================================================
# FAIL: test_ospf_09_virtual_link (__main__.TestProtocolsOSPF)
# ----------------------------------------------------------------------
# Traceback (most recent call last):
# File "/usr/libexec/vyos/tests/smoke/cli/test_protocols_ospf.py", line 261, in test_ospf_09_virtual_link
# self.assertIn(f'router ospf', frrconfig)
# AssertionError: 'router ospf' not found in ''
#
# Add some debug code so we can find the root cause
pprint.pprint(frrconfig)
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)
for network in networks:
self.assertIn(f' network {network} area {area}', frrconfig)
def test_ospf_10_interface_configureation(self):
interfaces = Section.interfaces('ethernet')
password = 'vyos1234'
bandwidth = '10000'
cost = '150'
network = 'point-to-point'
priority = '200'
for interface in interfaces:
self.session.set(base_path + ['interface', interface, 'authentication', 'plaintext-password', password])
self.session.set(base_path + ['interface', interface, 'bandwidth', bandwidth])
self.session.set(base_path + ['interface', interface, 'bfd'])
self.session.set(base_path + ['interface', interface, 'cost', cost])
self.session.set(base_path + ['interface', interface, 'mtu-ignore'])
self.session.set(base_path + ['interface', interface, 'network', network])
self.session.set(base_path + ['interface', interface, 'priority', priority])
# commit changes
self.session.commit()
for interface in interfaces:
config = getFRRInterfaceConfig(interface)
self.assertIn(f'interface {interface}', config)
self.assertIn(f' ip ospf authentication-key {password}', config)
self.assertIn(f' ip ospf bfd', config)
self.assertIn(f' ip ospf cost {cost}', config)
self.assertIn(f' ip ospf mtu-ignore', config)
self.assertIn(f' ip ospf network {network}', config)
self.assertIn(f' ip ospf priority {priority}', config)
self.assertIn(f' bandwidth {bandwidth}', config)
+ def test_ospf_01_11_vrfs(self):
+ vrfs = ['red', 'green', 'blue']
+ # It is safe to assume that when the basic VRF test works, all
+ # other OSPF related features work, as we entirely inherit the CLI
+ # templates and Jinja2 FRR template.
+ table = '1000'
+ for vrf in vrfs:
+ self.session.set(['vrf', 'name', vrf, 'table', table])
+ self.session.set(['protocols', 'vrf', vrf, 'ospf'])
+ table = str(int(table) + 1000)
+
+ self.session.commit()
+
+ # Verify FRR ospfd configuration
+ frrconfig = getFRRconfig()
+ 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
+
+ for vrf in vrfs:
+ frrconfig = getFRRconfig(vrf)
+ self.assertIn(f'router ospf vrf {vrf}', frrconfig)
+ self.assertIn(f' auto-cost reference-bandwidth 100', frrconfig)
+ self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults
+
+ self.session.delete(['protocols', 'vrf', vrf])
+ self.session.delete(['vrf', 'name', vrf])
+
+
if __name__ == '__main__':
- unittest.main(verbosity=2)
+ unittest.main(verbosity=2, failfast=True)
diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py
index aefe7c23e..8c2372bd9 100755
--- a/src/conf_mode/protocols_ospf.py
+++ b/src/conf_mode/protocols_ospf.py
@@ -1,161 +1,212 @@
#!/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/>.
import os
from sys import exit
+from sys import argv
from vyos.config import Config
from vyos.configdict import dict_merge
+from vyos.configdict import node_changed
from vyos.configverify import verify_route_maps
from vyos.configverify import verify_interface_exists
+from vyos.configverify import verify_vrf
from vyos.template import render_to_string
from vyos.util import call
from vyos.util import dict_search
+from vyos.util import get_json_iface_options
from vyos.xml import defaults
from vyos import ConfigError
from vyos import frr
from vyos import airbag
airbag.enable()
frr_daemon = 'ospfd'
def get_config(config=None):
if config:
conf = config
else:
conf = Config()
- base = ['protocols', 'ospf']
+
+ vrf = None
+ if len(argv) > 1:
+ vrf = argv[1]
+
+ base_path = ['protocols', 'ospf']
+
+ # eqivalent of the C foo ? 'a' : 'b' statement
+ base = vrf and ['protocols', 'vrf', vrf, '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
+
# Bail out early if configuration tree does not exist
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 = defaults(base)
+ # XXX: Note that we can not call defaults(base), as defaults does not work
+ # on an instance of a tag node. As we use the exact same CLI definition for
+ # both the non-vrf and vrf version this is absolutely safe!
+ default_values = defaults(base_path)
# 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']
if 'mpls_te' not in ospf:
del default_values['mpls_te']
for protocol in ['bgp', 'connected', 'isis', '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']
del default_values['interface']
# 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])
if 'interface' in ospf:
for interface in ospf['interface']:
# We need to reload the defaults on every pass b/c of
# hello-multiplier dependency on dead-interval
default_values = defaults(base + ['interface'])
# If hello-multiplier is set, we need to remove the default from
# dead-interval.
if 'hello_multiplier' in ospf['interface'][interface]:
del default_values['dead_interval']
ospf['interface'][interface] = dict_merge(default_values,
ospf['interface'][interface])
+ # As we no re-use this Python handler for both VRF and non VRF instances for
+ # OSPF we need to find out if any interfaces changed so properly adjust
+ # the FRR configuration and not by acctident change interfaces from a
+ # different VRF.
+ interfaces_removed = node_changed(conf, base + ['interface'])
+ if interfaces_removed:
+ ospf['interface_removed'] = list(interfaces_removed)
+
# We also need some additional information from the config, prefix-lists
# and route-maps for instance. They will be used in verify()
base = ['policy']
tmp = conf.get_config_dict(base, key_mangling=('-', '_'))
# Merge policy dict into OSPF dict
ospf = dict_merge(tmp, ospf)
return ospf
def verify(ospf):
if not ospf:
return None
+ verify_vrf(ospf)
verify_route_maps(ospf)
if 'interface' in ospf:
for interface in ospf['interface']:
verify_interface_exists(interface)
# One can not use dead-interval and hello-multiplier at the same
# time. FRR will only activate the last option set via CLI.
if {'hello_multiplier', 'dead_interval'} <= set(ospf['interface'][interface]):
raise ConfigError(f'Can not use hello-multiplier and dead-interval ' \
- f'concurrently for "{interface}"!')
+ f'concurrently for {interface}!')
+
+ if 'vrf' in ospf:
+ # If interface specific options are set, we must ensure that the
+ # interface is bound to our requesting VRF. Due to the VyOS/Vyatta
+ # priorities the interface is bound to the VRF after creation of
+ # the VRF itself, and before any routing protocol is configured.
+ vrf = ospf['vrf']
+ tmp = get_json_iface_options(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(ospf):
- if not ospf:
+ if not ospf or 'deleted' in ospf:
ospf['new_frr_config'] = ''
return None
ospf['new_frr_config'] = render_to_string('frr/ospf.frr.tmpl', ospf)
return None
def apply(ospf):
# Save original configuration prior to starting any commit actions
frr_cfg = frr.FRRConfig()
frr_cfg.load_configuration(frr_daemon)
- frr_cfg.modify_section(r'^interface \S+', '')
- frr_cfg.modify_section('^router ospf$', '')
+
+ if 'vrf' in ospf:
+ vrf = ospf['vrf']
+ frr_cfg.modify_section(f'^router ospf vrf {vrf}$', '')
+ else:
+ frr_cfg.modify_section('^router ospf$', '')
+
+ for key in ['interface', 'interface_removed']:
+ if key not in ospf:
+ continue
+ for interface in ospf[key]:
+ frr_cfg.modify_section(f'^interface {interface}$', '')
+
frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', ospf['new_frr_config'])
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(frr_daemon)
return None
if __name__ == '__main__':
try:
c = get_config()
verify(c)
generate(c)
apply(c)
except ConfigError as e:
print(e)
exit(1)

File Metadata

Mime Type
text/x-diff
Expires
Sat, Oct 17, 12:21 AM (12 h, 31 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4416535
Default Alt Text
(36 KB)

Event Timeline