Page Menu
Home
VyOS Platform
Search
Configure Global Search
Log In
Files
F119063106
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
39 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py
index 82fb96754..1b9cc50fe 100755
--- a/smoketest/scripts/cli/test_protocols_ospf.py
+++ b/smoketest/scripts/cli/test_protocols_ospf.py
@@ -1,544 +1,564 @@
#!/usr/bin/env python3
#
# Copyright (C) 2021-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.ifconfig import Section
from vyos.utils.process import process_named_running
PROCESS_NAME = 'ospfd'
base_path = ['protocols', 'ospf']
route_map = 'foo-bar-baz10'
class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
@classmethod
def setUpClass(cls):
super(TestProtocolsOSPF, 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)
cls.cli_set(cls, ['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit'])
cls.cli_set(cls, ['policy', 'route-map', route_map, 'rule', '20', 'action', 'permit'])
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
cls.cli_delete(cls, base_path)
@classmethod
def tearDownClass(cls):
cls.cli_delete(cls, ['policy', 'route-map', route_map])
super(TestProtocolsOSPF, cls).tearDownClass()
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 test_ospf_01_defaults(self):
# commit changes
self.cli_set(base_path)
self.cli_commit()
# Verify FRR ospfd configuration
frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
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.cli_set(base_path + ['auto-cost', 'reference-bandwidth', bandwidth])
self.cli_set(base_path + ['parameters', 'router-id', router_id])
self.cli_set(base_path + ['parameters', 'abr-type', abr_type])
self.cli_set(base_path + ['parameters', 'opaque-lsa'])
self.cli_set(base_path + ['parameters', 'rfc1583-compatibility'])
self.cli_set(base_path + ['log-adjacency-changes', 'detail'])
self.cli_set(base_path + ['default-metric', metric])
self.cli_set(base_path + ['passive-interface', 'default'])
self.cli_set(base_path + ['area', '10', 'area-type', 'stub'])
self.cli_set(base_path + ['area', '10', 'network', '10.0.0.0/16'])
self.cli_set(base_path + ['area', '10', 'range', '10.0.1.0/24'])
self.cli_set(base_path + ['area', '10', 'range', '10.0.2.0/24', 'not-advertise'])
# commit changes
self.cli_commit()
# Verify FRR ospfd configuration
frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
self.assertIn(f'router ospf', frrconfig)
self.assertIn(f' compatible rfc1583', 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' capability opaque', frrconfig)
self.assertIn(f' default-metric {metric}', frrconfig)
self.assertIn(f' passive-interface default', frrconfig)
self.assertIn(f' area 10 stub', frrconfig)
self.assertIn(f' network 10.0.0.0/16 area 10', frrconfig)
self.assertIn(f' area 10 range 10.0.1.0/24', frrconfig)
self.assertNotIn(f' area 10 range 10.0.1.0/24 not-advertise', frrconfig)
self.assertIn(f' area 10 range 10.0.2.0/24 not-advertise', frrconfig)
def test_ospf_03_access_list(self):
acl = '100'
seq = '10'
protocols = ['bgp', 'connected', 'isis', 'kernel', 'rip', 'static']
self.cli_set(['policy', 'access-list', acl, 'rule', seq, 'action', 'permit'])
self.cli_set(['policy', 'access-list', acl, 'rule', seq, 'source', 'any'])
self.cli_set(['policy', 'access-list', acl, 'rule', seq, 'destination', 'any'])
for ptotocol in protocols:
self.cli_set(base_path + ['access-list', acl, 'export', ptotocol])
# commit changes
self.cli_commit()
# Verify FRR ospfd configuration
frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
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.cli_delete(['policy', 'access-list', acl])
def test_ospf_04_default_originate(self):
seq = '100'
metric = '50'
metric_type = '1'
self.cli_set(base_path + ['default-information', 'originate', 'metric', metric])
self.cli_set(base_path + ['default-information', 'originate', 'metric-type', metric_type])
self.cli_set(base_path + ['default-information', 'originate', 'route-map', route_map])
# commit changes
self.cli_commit()
# Verify FRR ospfd configuration
frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
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.cli_set(base_path + ['default-information', 'originate', 'always'])
self.cli_commit()
# Verify FRR ospfd configuration
frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
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'
aggregation_timer = '100'
summary_nets = {
'10.0.1.0/24' : {},
'10.0.2.0/24' : {'tag' : '50'},
'10.0.3.0/24' : {'no_advertise' : {}},
}
self.cli_set(base_path + ['distance', 'global', global_distance])
self.cli_set(base_path + ['distance', 'ospf', 'external', external])
self.cli_set(base_path + ['distance', 'ospf', 'intra-area', intra_area])
self.cli_set(base_path + ['max-metric', 'router-lsa', 'on-startup', on_startup])
self.cli_set(base_path + ['max-metric', 'router-lsa', 'on-shutdown', on_shutdown])
self.cli_set(base_path + ['mpls-te', 'enable'])
self.cli_set(base_path + ['refresh', 'timers', refresh])
self.cli_set(base_path + ['aggregation', 'timer', aggregation_timer])
for summary, summary_options in summary_nets.items():
self.cli_set(base_path + ['summary-address', summary])
if 'tag' in summary_options:
self.cli_set(base_path + ['summary-address', summary, 'tag', summary_options['tag']])
if 'no_advertise' in summary_options:
self.cli_set(base_path + ['summary-address', summary, 'no-advertise'])
# commit changes
self.cli_commit()
# Verify FRR ospfd configuration
frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
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)
self.assertIn(f' aggregation timer {aggregation_timer}', frrconfig)
for summary, summary_options in summary_nets.items():
self.assertIn(f' summary-address {summary}', frrconfig)
if 'tag' in summary_options:
tag = summary_options['tag']
self.assertIn(f' summary-address {summary} tag {tag}', frrconfig)
if 'no_advertise' in summary_options:
self.assertIn(f' summary-address {summary} no-advertise', frrconfig)
# enable inter-area
self.cli_set(base_path + ['distance', 'ospf', 'inter-area', inter_area])
self.cli_commit()
frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
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.cli_set(base_path + ['neighbor', neighbor, 'priority', priority])
self.cli_set(base_path + ['neighbor', neighbor, 'poll-interval', poll_interval])
# commit changes
self.cli_commit()
# Verify FRR ospfd configuration
frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
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_redistribute(self):
metric = '15'
metric_type = '1'
redistribute = ['babel', 'bgp', 'connected', 'isis', 'kernel', 'rip', 'static']
for protocol in redistribute:
self.cli_set(base_path + ['redistribute', protocol, 'metric', metric])
self.cli_set(base_path + ['redistribute', protocol, 'route-map', route_map])
self.cli_set(base_path + ['redistribute', protocol, 'metric-type', metric_type])
# commit changes
self.cli_commit()
# Verify FRR ospfd configuration
frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
self.assertIn(f'router ospf', frrconfig)
for protocol in redistribute:
self.assertIn(f' redistribute {protocol} metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig)
def test_ospf_08_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.cli_set(base_path + ['area', area, 'shortcut', shortcut])
self.cli_set(base_path + ['area', area, 'virtual-link', virtual_link, 'hello-interval', hello])
self.cli_set(base_path + ['area', area, 'virtual-link', virtual_link, 'retransmit-interval', retransmit])
self.cli_set(base_path + ['area', area, 'virtual-link', virtual_link, 'transmit-delay', transmit])
self.cli_set(base_path + ['area', area, 'virtual-link', virtual_link, 'dead-interval', dead])
for network in networks:
self.cli_set(base_path + ['area', area, 'network', network])
# commit changes
self.cli_commit()
# Verify FRR ospfd configuration
frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
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_09_interface_configuration(self):
interfaces = Section.interfaces('ethernet')
password = 'vyos1234'
bandwidth = '10000'
cost = '150'
network = 'point-to-point'
priority = '200'
bfd_profile = 'vyos-test'
self.cli_set(base_path + ['passive-interface', 'default'])
for interface in interfaces:
base_interface = base_path + ['interface', interface]
self.cli_set(base_interface + ['authentication', 'plaintext-password', password])
self.cli_set(base_interface + ['bandwidth', bandwidth])
self.cli_set(base_interface + ['bfd', 'profile', bfd_profile])
self.cli_set(base_interface + ['cost', cost])
self.cli_set(base_interface + ['mtu-ignore'])
self.cli_set(base_interface + ['network', network])
self.cli_set(base_interface + ['priority', priority])
self.cli_set(base_interface + ['passive', 'disable'])
# commit changes
self.cli_commit()
frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
self.assertIn(f'router ospf', frrconfig)
self.assertIn(f' passive-interface default', frrconfig)
for interface in interfaces:
# Can not use daemon for getFRRconfig() as bandwidth parameter belongs to zebra process
config = self.getFRRconfig(f'interface {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 bfd profile {bfd_profile}', 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' no ip ospf passive', config)
self.assertIn(f' bandwidth {bandwidth}', config)
# T5467: Remove interface from OSPF process and VRF
self.cli_delete(base_path + ['interface'])
self.cli_commit()
for interface in interfaces:
# T5467: It must also be removed from FRR config
frrconfig = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
self.assertNotIn(f'interface {interface}', frrconfig)
# There should be no OSPF related command at all under the interface
self.assertNotIn(f' ip ospf', frrconfig)
def test_ospf_11_interface_area(self):
area = '0'
interfaces = Section.interfaces('ethernet')
self.cli_set(base_path + ['area', area, 'network', '10.0.0.0/8'])
for interface in interfaces:
self.cli_set(base_path + ['interface', interface, 'area', area])
# we can not have bot area network and interface area set
with self.assertRaises(ConfigSessionError):
self.cli_commit()
self.cli_delete(base_path + ['area', area, 'network'])
self.cli_commit()
# Verify FRR ospfd configuration
frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
self.assertIn(f'router ospf', frrconfig)
for interface in interfaces:
config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
self.assertIn(f'interface {interface}', config)
self.assertIn(f' ip ospf area {area}', config)
def test_ospf_12_vrfs(self):
# 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'
vrf = 'blue'
vrf_base = ['vrf', 'name', vrf]
vrf_iface = 'eth1'
area = '1'
self.cli_set(vrf_base + ['table', table])
self.cli_set(vrf_base + ['protocols', 'ospf', 'interface', vrf_iface, 'area', area])
self.cli_set(['interfaces', 'ethernet', vrf_iface, 'vrf', vrf])
# Also set a default VRF OSPF config
self.cli_set(base_path)
self.cli_commit()
# Verify FRR ospfd configuration
frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
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
frrconfig = self.getFRRconfig(f'router ospf vrf {vrf}', daemon=PROCESS_NAME)
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
frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=PROCESS_NAME)
self.assertIn(f'interface {vrf_iface}', frrconfig)
self.assertIn(f' ip ospf area {area}', frrconfig)
# T5467: Remove interface from OSPF process and VRF
self.cli_delete(vrf_base + ['protocols', 'ospf', 'interface'])
self.cli_delete(['interfaces', 'ethernet', vrf_iface, 'vrf'])
self.cli_commit()
# T5467: It must also be removed from FRR config
frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=PROCESS_NAME)
self.assertNotIn(f'interface {vrf_iface}', frrconfig)
# There should be no OSPF related command at all under the interface
self.assertNotIn(f' ip ospf', frrconfig)
# cleanup
self.cli_delete(['vrf', 'name', vrf])
self.cli_delete(['interfaces', 'ethernet', vrf_iface, 'vrf'])
def test_ospf_13_export_list(self):
# Verify explort-list works on ospf-area
acl = '100'
seq = '10'
area = '0.0.0.10'
network = '10.0.0.0/8'
self.cli_set(['policy', 'access-list', acl, 'rule', seq, 'action', 'permit'])
self.cli_set(['policy', 'access-list', acl, 'rule', seq, 'source', 'any'])
self.cli_set(['policy', 'access-list', acl, 'rule', seq, 'destination', 'any'])
self.cli_set(base_path + ['area', area, 'network', network])
self.cli_set(base_path + ['area', area, 'export-list', acl])
# commit changes
self.cli_commit()
# Verify FRR ospfd configuration
frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
self.assertIn(f'router ospf', frrconfig)
self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # default
self.assertIn(f' network {network} area {area}', frrconfig)
self.assertIn(f' area {area} export-list {acl}', frrconfig)
def test_ospf_14_segment_routing_configuration(self):
global_block_low = "300"
global_block_high = "399"
local_block_low = "400"
local_block_high = "499"
interface = 'lo'
maximum_stack_size = '5'
prefix_one = '192.168.0.1/32'
prefix_two = '192.168.0.2/32'
prefix_one_value = '1'
prefix_two_value = '2'
self.cli_set(base_path + ['interface', interface])
self.cli_set(base_path + ['segment-routing', 'maximum-label-depth', maximum_stack_size])
self.cli_set(base_path + ['segment-routing', 'global-block', 'low-label-value', global_block_low])
self.cli_set(base_path + ['segment-routing', 'global-block', 'high-label-value', global_block_high])
self.cli_set(base_path + ['segment-routing', 'local-block', 'low-label-value', local_block_low])
self.cli_set(base_path + ['segment-routing', 'local-block', 'high-label-value', local_block_high])
self.cli_set(base_path + ['segment-routing', 'prefix', prefix_one, 'index', 'value', prefix_one_value])
self.cli_set(base_path + ['segment-routing', 'prefix', prefix_one, 'index', 'explicit-null'])
self.cli_set(base_path + ['segment-routing', 'prefix', prefix_two, 'index', 'value', prefix_two_value])
self.cli_set(base_path + ['segment-routing', 'prefix', prefix_two, 'index', 'no-php-flag'])
# Commit all changes
self.cli_commit()
# Verify all changes
frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
self.assertIn(f' segment-routing on', frrconfig)
self.assertIn(f' segment-routing global-block {global_block_low} {global_block_high} local-block {local_block_low} {local_block_high}', frrconfig)
self.assertIn(f' segment-routing node-msd {maximum_stack_size}', frrconfig)
self.assertIn(f' segment-routing prefix {prefix_one} index {prefix_one_value} explicit-null', frrconfig)
self.assertIn(f' segment-routing prefix {prefix_two} index {prefix_two_value} no-php-flag', frrconfig)
def test_ospf_15_ldp_sync(self):
holddown = "500"
interface = 'lo'
interfaces = Section.interfaces('ethernet')
self.cli_set(base_path + ['interface', interface])
self.cli_set(base_path + ['ldp-sync', 'holddown', holddown])
# Commit main OSPF changes
self.cli_commit()
# Verify main OSPF changes
frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
self.assertIn(f'router ospf', frrconfig)
self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig)
self.assertIn(f' mpls ldp-sync holddown {holddown}', frrconfig)
for interface in interfaces:
self.cli_set(base_path + ['interface', interface, 'ldp-sync', 'holddown', holddown])
# Commit interface changes for holddown
self.cli_commit()
for interface in interfaces:
# Verify interface changes for holddown
config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
self.assertIn(f'interface {interface}', config)
self.assertIn(f' ip ospf dead-interval 40', config)
self.assertIn(f' ip ospf mpls ldp-sync', config)
self.assertIn(f' ip ospf mpls ldp-sync holddown {holddown}', config)
for interface in interfaces:
self.cli_set(base_path + ['interface', interface, 'ldp-sync', 'disable'])
# Commit interface changes for disable
self.cli_commit()
for interface in interfaces:
# Verify interface changes for disable
config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
self.assertIn(f'interface {interface}', config)
self.assertIn(f' ip ospf dead-interval 40', config)
self.assertIn(f' no ip ospf mpls ldp-sync', config)
def test_ospf_16_graceful_restart(self):
period = '300'
supported_grace_time = '400'
router_ids = ['192.0.2.1', '192.0.2.2']
self.cli_set(base_path + ['capability', 'opaque'])
self.cli_set(base_path + ['graceful-restart', 'grace-period', period])
self.cli_set(base_path + ['graceful-restart', 'helper', 'planned-only'])
self.cli_set(base_path + ['graceful-restart', 'helper', 'no-strict-lsa-checking'])
self.cli_set(base_path + ['graceful-restart', 'helper', 'supported-grace-time', supported_grace_time])
for router_id in router_ids:
self.cli_set(base_path + ['graceful-restart', 'helper', 'enable', 'router-id', router_id])
# commit changes
self.cli_commit()
# Verify FRR ospfd configuration
frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
self.assertIn(f'router ospf', frrconfig)
self.assertIn(f' capability opaque', frrconfig)
self.assertIn(f' graceful-restart grace-period {period}', frrconfig)
self.assertIn(f' graceful-restart helper planned-only', frrconfig)
self.assertIn(f' no graceful-restart helper strict-lsa-checking', frrconfig)
self.assertIn(f' graceful-restart helper supported-grace-time {supported_grace_time}', frrconfig)
for router_id in router_ids:
self.assertIn(f' graceful-restart helper enable {router_id}', frrconfig)
+ def test_ospf_17_duplicate_area_network(self):
+ area0 = '0'
+ area1 = '1'
+ network = '10.0.0.0/8'
+
+ self.cli_set(base_path + ['area', area0, 'network', network])
+
+ # we can not have the same network defined on two areas
+ self.cli_set(base_path + ['area', area1, 'network', network])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_delete(base_path + ['area', area0])
+
+ self.cli_commit()
+
+ # Verify FRR ospfd configuration
+ frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
+ self.assertIn(f'router ospf', frrconfig)
+ self.assertIn(f' network {network} area {area1}', frrconfig)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py
index 695842795..6fffe7e0d 100755
--- a/src/conf_mode/protocols_ospf.py
+++ b/src/conf_mode/protocols_ospf.py
@@ -1,283 +1,290 @@
#!/usr/bin/env python3
#
# Copyright (C) 2021-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 sys import argv
from vyos.config import Config
from vyos.config import config_dict_merge
from vyos.configdict import dict_merge
from vyos.configdict import node_changed
from vyos.configverify import verify_common_route_maps
from vyos.configverify import verify_route_map
from vyos.configverify import verify_interface_exists
from vyos.configverify import verify_access_list
from vyos.template import render_to_string
from vyos.utils.dict import dict_search
from vyos.utils.network import get_interface_config
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()
vrf = None
if len(argv) > 1:
vrf = argv[1]
base_path = ['protocols', 'ospf']
# eqivalent of the C foo ? 'a' : 'b' statement
base = vrf and ['vrf', 'name', vrf, 'protocols', '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
# FRR has VRF support for different routing daemons. As interfaces belong
# to VRFs - or the global VRF, we need to check for changed interfaces so
# that they will be properly rendered for the FRR config. Also this eases
# removal of interfaces from the running configuration.
interfaces_removed = node_changed(conf, base + ['interface'])
if interfaces_removed:
ospf['interface_removed'] = list(interfaces_removed)
# Bail out early if configuration tree does no longer exist. this must
# be done after retrieving the list of interfaces to be removed.
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 = conf.get_config_defaults(**ospf.kwargs, recursive=True)
# 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 'mpls_te' not in ospf:
del default_values['mpls_te']
if 'graceful_restart' not in ospf:
del default_values['graceful_restart']
for area_num in default_values.get('area', []):
if dict_search(f'area.{area_num}.area_type.nssa', ospf) is None:
del default_values['area'][area_num]['area_type']['nssa']
for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'rip', 'static']:
if dict_search(f'redistribute.{protocol}', ospf) is None:
del default_values['redistribute'][protocol]
if not bool(default_values['redistribute']):
del default_values['redistribute']
for interface in ospf.get('interface', []):
# We need to reload the defaults on every pass b/c of
# hello-multiplier dependency on dead-interval
# If hello-multiplier is set, we need to remove the default from
# dead-interval.
if 'hello_multiplier' in ospf['interface'][interface]:
del default_values['interface'][interface]['dead_interval']
ospf = config_dict_merge(default_values, ospf)
# We also need some additional information from the config, prefix-lists
# and route-maps for instance. They will be used in verify().
#
# XXX: one MUST always call this without the key_mangling() option! See
# vyos.configverify.verify_common_route_maps() for more information.
tmp = conf.get_config_dict(['policy'])
# Merge policy dict into "regular" config dict
ospf = dict_merge(tmp, ospf)
return ospf
def verify(ospf):
if not ospf:
return None
verify_common_route_maps(ospf)
# As we can have a default-information route-map, we need to validate it!
route_map_name = dict_search('default_information.originate.route_map', ospf)
if route_map_name: verify_route_map(route_map_name, ospf)
# Validate if configured Access-list exists
if 'area' in ospf:
+ networks = []
for area, area_config in ospf['area'].items():
if 'import_list' in area_config:
acl_import = area_config['import_list']
if acl_import: verify_access_list(acl_import, ospf)
if 'export_list' in area_config:
acl_export = area_config['export_list']
if acl_export: verify_access_list(acl_export, ospf)
+ if 'network' in area_config:
+ for network in area_config['network']:
+ if network in networks:
+ raise ConfigError(f'Network "{network}" already defined in different area!')
+ networks.append(network)
+
if 'interface' in ospf:
for interface, interface_config in ospf['interface'].items():
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(interface_config):
raise ConfigError(f'Can not use hello-multiplier and dead-interval ' \
f'concurrently for {interface}!')
# One can not use the "network <prefix> area <id>" command and an
# per interface area assignment at the same time. FRR will error
# out using: "Please remove all network commands first."
if 'area' in ospf and 'area' in interface_config:
for area, area_config in ospf['area'].items():
if 'network' in area_config:
raise ConfigError('Can not use OSPF interface area and area ' \
'network configuration at the same time!')
# If interface specific options are set, we must ensure that the
# interface is bound to our requesting VRF. Due to the VyOS
# priorities the interface is bound to the VRF after creation of
# the VRF itself, and before any routing protocol is configured.
if 'vrf' in ospf:
vrf = ospf['vrf']
tmp = get_interface_config(interface)
if 'master' not in tmp or tmp['master'] != vrf:
raise ConfigError(f'Interface "{interface}" is not a member of VRF "{vrf}"!')
# Segment routing checks
if dict_search('segment_routing.global_block', ospf):
g_high_label_value = dict_search('segment_routing.global_block.high_label_value', ospf)
g_low_label_value = dict_search('segment_routing.global_block.low_label_value', ospf)
# If segment routing global block high or low value is blank, throw error
if not (g_low_label_value or g_high_label_value):
raise ConfigError('Segment routing global-block requires both low and high value!')
# If segment routing global block low value is higher than the high value, throw error
if int(g_low_label_value) > int(g_high_label_value):
raise ConfigError('Segment routing global-block low value must be lower than high value')
if dict_search('segment_routing.local_block', ospf):
if dict_search('segment_routing.global_block', ospf) == None:
raise ConfigError('Segment routing local-block requires global-block to be configured!')
l_high_label_value = dict_search('segment_routing.local_block.high_label_value', ospf)
l_low_label_value = dict_search('segment_routing.local_block.low_label_value', ospf)
# If segment routing local-block high or low value is blank, throw error
if not (l_low_label_value or l_high_label_value):
raise ConfigError('Segment routing local-block requires both high and low value!')
# If segment routing local-block low value is higher than the high value, throw error
if int(l_low_label_value) > int(l_high_label_value):
raise ConfigError('Segment routing local-block low value must be lower than high value')
# local-block most live outside global block
global_range = range(int(g_low_label_value), int(g_high_label_value) +1)
local_range = range(int(l_low_label_value), int(l_high_label_value) +1)
# Check for overlapping ranges
if list(set(global_range) & set(local_range)):
raise ConfigError(f'Segment-Routing Global Block ({g_low_label_value}/{g_high_label_value}) '\
f'conflicts with Local Block ({l_low_label_value}/{l_high_label_value})!')
# Check for a blank or invalid value per prefix
if dict_search('segment_routing.prefix', ospf):
for prefix, prefix_config in ospf['segment_routing']['prefix'].items():
if 'index' in prefix_config:
if prefix_config['index'].get('value') is None:
raise ConfigError(f'Segment routing prefix {prefix} index value cannot be blank.')
# Check for explicit-null and no-php-flag configured at the same time per prefix
if dict_search('segment_routing.prefix', ospf):
for prefix, prefix_config in ospf['segment_routing']['prefix'].items():
if 'index' in prefix_config:
if ("explicit_null" in prefix_config['index']) and ("no_php_flag" in prefix_config['index']):
raise ConfigError(f'Segment routing prefix {prefix} cannot have both explicit-null '\
f'and no-php-flag configured at the same time.')
# Check for index ranges being larger than the segment routing global block
if dict_search('segment_routing.global_block', ospf):
g_high_label_value = dict_search('segment_routing.global_block.high_label_value', ospf)
g_low_label_value = dict_search('segment_routing.global_block.low_label_value', ospf)
g_label_difference = int(g_high_label_value) - int(g_low_label_value)
if dict_search('segment_routing.prefix', ospf):
for prefix, prefix_config in ospf['segment_routing']['prefix'].items():
if 'index' in prefix_config:
index_size = ospf['segment_routing']['prefix'][prefix]['index']['value']
if int(index_size) > int(g_label_difference):
raise ConfigError(f'Segment routing prefix {prefix} cannot have an '\
f'index base size larger than the SRGB label base.')
# Check route summarisation
if 'summary_address' in ospf:
for prefix, prefix_options in ospf['summary_address'].items():
if {'tag', 'no_advertise'} <= set(prefix_options):
raise ConfigError(f'Can not set both "tag" and "no-advertise" for Type-5 '\
f'and Type-7 route summarisation of "{prefix}"!')
return None
def generate(ospf):
if not ospf or 'deleted' in ospf:
return None
ospf['frr_ospfd_config'] = render_to_string('frr/ospfd.frr.j2', ospf)
return None
def apply(ospf):
ospf_daemon = 'ospfd'
# Save original configuration prior to starting any commit actions
frr_cfg = frr.FRRConfig()
# Generate empty helper string which can be ammended to FRR commands, it
# will be either empty (default VRF) or contain the "vrf <name" statement
vrf = ''
if 'vrf' in ospf:
vrf = ' vrf ' + ospf['vrf']
frr_cfg.load_configuration(ospf_daemon)
frr_cfg.modify_section(f'^router ospf{vrf}', stop_pattern='^exit', remove_stop_mark=True)
for key in ['interface', 'interface_removed']:
if key not in ospf:
continue
for interface in ospf[key]:
frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True)
if 'frr_ospfd_config' in ospf:
frr_cfg.add_before(frr.default_add_before, ospf['frr_ospfd_config'])
frr_cfg.commit_configuration(ospf_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
Details
Attached
Mime Type
text/x-diff
Expires
Fri, Nov 6, 7:57 AM (1 d, 15 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4642822
Default Alt Text
(39 KB)
Attached To
Mode
rVYOSONEX vyos-1x
Attached
Detach File
Event Timeline
Log In to Comment