diff --git a/tests/topotests/ospf_multi_instance/r1/frr.conf b/tests/topotests/ospf_multi_instance/r1/frr.conf new file mode 100644 index 000000000000..c341a7176a09 --- /dev/null +++ b/tests/topotests/ospf_multi_instance/r1/frr.conf @@ -0,0 +1,19 @@ +! +hostname r1 +password zebra +log file /tmp/r1-frr.log +ip forwarding +! +interface lo + ip address 1.1.1.1/32 + ip ospf area 0 +! +interface r1-eth0 + ip address 10.1.1.1/24 + ip ospf area 0 +! +! +router ospf + ospf router-id 1.1.1.1 + distance 20 +! diff --git a/tests/topotests/ospf_multi_instance/r2/frr.conf b/tests/topotests/ospf_multi_instance/r2/frr.conf new file mode 100644 index 000000000000..8501e0edc07d --- /dev/null +++ b/tests/topotests/ospf_multi_instance/r2/frr.conf @@ -0,0 +1,37 @@ +! +hostname r2 +password zebra +! debug ospf event +! debug ospf lsa +! debug ospf default-information +! debug ospf zebra redistribute + +ip forwarding +! +interface lo1 + ip address 2.2.2.1/32 + ip ospf 1 area 0 + no shut +! +interface lo2 + ip address 2.2.2.2/32 + ip ospf 2 area 0 + no shut +! +interface r2-eth0 + ip address 10.1.1.2/24 + ip ospf 1 area 0 +! +interface r2-eth1 + ip address 10.1.2.2/24 + ip ospf 2 area 0 +! +router ospf 1 + ospf router-id 2.2.2.1 + distance 20 +! +router ospf 2 + ospf router-id 2.2.2.2 + distance 20 +! + diff --git a/tests/topotests/ospf_multi_instance/r3/frr.conf b/tests/topotests/ospf_multi_instance/r3/frr.conf new file mode 100644 index 000000000000..97a3e19c9b10 --- /dev/null +++ b/tests/topotests/ospf_multi_instance/r3/frr.conf @@ -0,0 +1,19 @@ +! +hostname r3 +password zebra +log file /tmp/r3-frr.log +ip forwarding +! +interface lo + ip address 3.3.3.1/32 + ip ospf area 0 +! +interface r3-eth0 + ip address 10.1.2.3/24 + ip ospf area 0 +! +! +router ospf + ospf router-id 3.3.3.1 + distance 20 +! diff --git a/tests/topotests/ospf_multi_instance/test_ospf_multi_instance.py b/tests/topotests/ospf_multi_instance/test_ospf_multi_instance.py new file mode 100644 index 000000000000..de44140c09dc --- /dev/null +++ b/tests/topotests/ospf_multi_instance/test_ospf_multi_instance.py @@ -0,0 +1,403 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_ospf_multi_instance.py +# +# Copyright (c) 2024 LabN Consulting +# Acee Lindem +# + +import os +import sys +from functools import partial +import pytest + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +from lib.common_config import ( + step, + create_interface_in_kernel, +) + + +""" +test_ospf_metric_propagation.py: Test OSPF/BGP metric propagation +""" + +TOPOLOGY = """ + + +---------+ +--------------------+ +---------+ + | r1 | | r2 | r2 | | r3 | + | | | ospf 1 | ospf 2 | | | + | 1.1.1.1 | eth0 eth0| 2.2.2.1 | 2.2.2.2 |eth1 eth0| 3.3.3.1 | + | +-------------+ | +-------------+ | + | | 10.1.1.0/24 | | | 10.1.2.0/24 | | + +---------+ +--------------------+ +---------+ + + +""" + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# Required to instantiate the topology builder class. + +pytestmark = [pytest.mark.ospfd, pytest.mark.bgpd] + + +def build_topo(tgen): + "Build function" + + # Create 3 routers + tgen.add_router("r1") + tgen.add_router("r2") + tgen.add_router("r3") + + # Interconect router 1, 2 (0) + switch = tgen.add_switch("s1-1-2") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + # Interconect router 2, 3 (1) + switch = tgen.add_switch("s2-2-3") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + + # Add more loopbacks to r2 + create_interface_in_kernel( + tgen, "r2", "lo1", "2.2.2.1", netmask="255.255.255.255", create=True + ) + create_interface_in_kernel( + tgen, "r2", "lo2", "2.2.2.2", netmask="255.255.255.255", create=True + ) + + +def setup_module(mod): + logger.info("OSPF Multi-Instance:\n {}".format(TOPOLOGY)) + + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + # Starting Routers + router_list = tgen.routers() + + for rname, router in router_list.items(): + logger.info("Loading router %s" % rname) + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) + + # Initialize all routers. + tgen.start_router() + + +def teardown_module(): + "Teardown the pytest environment" + tgen = get_topogen() + tgen.stop_topology() + + +def test_multi_instance_default_origination(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip("Skipped because of router(s) failure") + + step("Configure a local default route") + r1 = tgen.gears["r1"] + r1.vtysh_cmd("conf t\nip route 0.0.0.0/0 Null0") + + step("Verify the R1 configuration and install of 'ip route 0.0.0.0/0 Null0'") + prefix_suppression_cfg = ( + tgen.net["r1"] + .cmd('vtysh -c "show running" | grep "^ip route 0.0.0.0/0 Null0"') + .rstrip() + ) + assertmsg = "'ip route 0.0.0.0/0 Null0' applied, but not present in configuration" + assert prefix_suppression_cfg == "ip route 0.0.0.0/0 Null0", assertmsg + + input_dict = { + "0.0.0.0/0": [ + { + "prefix": "0.0.0.0/0", + "prefixLen": 0, + "protocol": "static", + "nexthops": [ + { + "blackhole": True, + } + ], + } + ] + } + test_func = partial( + topotest.router_json_cmp, r1, "show ip route 0.0.0.0/0 json", input_dict + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "0.0.0.0/0 not installed on router r1" + assert result is None, assertmsg + + step( + "Verify the R1 configuration and advertisement of 'default-information originate'" + ) + r1.vtysh_cmd("conf t\nrouter ospf\n default-information originate") + + input_dict = { + "asExternalLinkStates": [ + { + "lsaType": "AS-external-LSA", + "linkStateId": "0.0.0.0", + "advertisingRouter": "1.1.1.1", + "networkMask": 0, + "metricType": "E2 (Larger than any link state path)", + "metric": 10, + "forwardAddress": "0.0.0.0", + "externalRouteTag": 0, + } + ] + } + test_func = partial( + topotest.router_json_cmp, r1, "show ip ospf database json", input_dict + ) + + r2 = tgen.gears["r2"] + step("Verify the OSPF instance 1 installation of default route on router 2") + input_dict = { + "0.0.0.0/0": [ + { + "prefix": "0.0.0.0/0", + "prefixLen": 0, + "protocol": "ospf", + "instance": 1, + "nexthops": [ + { + "ip": "10.1.1.1", + "interfaceName": "r2-eth0", + } + ], + } + ] + } + test_func = partial( + topotest.router_json_cmp, r2, "show ip route 0.0.0.0/0 json", input_dict + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "0.0.0.0/0 not installed on router r2" + assert result is None, assertmsg + + step("Configure OSPF 'default-intformation originate' on router r2 instance 2") + r2.vtysh_cmd("conf t\nrouter ospf 2\n default-information originate") + + step("Verify r2 instance 2 AS-External default origination") + input_dict = { + "ospfInstance": 2, + "routerId": "2.2.2.2", + "asExternalLinkStates": [ + { + "lsaType": "AS-external-LSA", + "linkStateId": "0.0.0.0", + "advertisingRouter": "2.2.2.2", + "networkMask": 0, + "metricType": "E2 (Larger than any link state path)", + "tos": 0, + "metric": 10, + "forwardAddress": "0.0.0.0", + "externalRouteTag": 0, + } + ], + } + test_func = partial( + topotest.router_json_cmp, + r2, + "show ip ospf 2 database external json", + input_dict, + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "AS-External default not originated by router r2 OSPF instance 2" + assert result is None, assertmsg + + step("Update the OSPF instance 2 distance so it will be preferred over instance 1") + r2.vtysh_cmd("conf t\nrouter ospf 2\n distance 15") + + step("Generate a default route from OSPF on r3") + r3 = tgen.gears["r3"] + r3.vtysh_cmd("conf t\nrouter ospf\n default-information originate") + r3.vtysh_cmd("conf t\nip route 0.0.0.0/0 Null0") + + step("Verify r3 AS-External default origination on r2") + input_dict = { + "ospfInstance": 2, + "routerId": "2.2.2.2", + "asExternalLinkStates": [ + { + "lsaType": "AS-external-LSA", + "linkStateId": "0.0.0.0", + "advertisingRouter": "3.3.3.1", + "length": 36, + "networkMask": 0, + "metricType": "E2 (Larger than any link state path)", + "tos": 0, + "metric": 10, + "forwardAddress": "0.0.0.0", + "externalRouteTag": 0, + } + ], + } + test_func = partial( + topotest.router_json_cmp, + r2, + "show ip ospf 2 database external json", + input_dict, + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "AS-External default not originated by router r3 OSPF" + assert result is None, assertmsg + + step("Verify r3's default installed by OSPF instance 2 is preferred on r2") + input_dict = { + "0.0.0.0/0": [ + { + "prefix": "0.0.0.0/0", + "prefixLen": 0, + "protocol": "ospf", + "instance": 2, + "distance": 15, + "nexthops": [ + { + "ip": "10.1.2.3", + "interfaceName": "r2-eth1", + } + ], + } + ] + } + test_func = partial( + topotest.router_json_cmp, r2, "show ip route 0.0.0.0/0 json", input_dict + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "0.0.0.0/0 from r3 not installed on router r2" + assert result is None, assertmsg + + step( + "Verify that r2's OSPF instance 2 AS-External LSA default is flushed due to default from r3" + ) + input_dict = { + "ospfInstance": 2, + "routerId": "2.2.2.2", + "asExternalLinkStates": [ + { + "lsaAge": 3600, + "lsaType": "AS-external-LSA", + "linkStateId": "0.0.0.0", + "advertisingRouter": "2.2.2.2", + "networkMask": 0, + "metricType": "E2 (Larger than any link state path)", + "tos": 0, + "metric": 10, + "forwardAddress": "0.0.0.0", + "externalRouteTag": 0, + } + ], + } + test_func = partial( + topotest.router_json_cmp, + r2, + "show ip ospf 2 database external json", + input_dict, + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "AS-External default not flushed by router r2 OSPF instance 2" + assert result is None, assertmsg + + step("Remove r3's default route and verify that its advertisement is flushed") + r3.vtysh_cmd("conf t\nno ip route 0.0.0.0/0 Null0") + input_dict = { + "routerId": "3.3.3.1", + "asExternalLinkStates": [ + { + "lsaAge": 3600, + "lsaType": "AS-external-LSA", + "linkStateId": "0.0.0.0", + "advertisingRouter": "3.3.3.1", + "networkMask": 0, + "metricType": "E2 (Larger than any link state path)", + "tos": 0, + "metric": 10, + "forwardAddress": "0.0.0.0", + "externalRouteTag": 0, + } + ], + } + test_func = partial( + topotest.router_json_cmp, r3, "show ip ospf database external json", input_dict + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "AS-External default not flushed by router r3 OSPF" + assert result is None, assertmsg + + step( + "Verify that r2's OSPF instance 2 AS-External default is advertised and installed by r3" + ) + input_dict = { + "routerId": "3.3.3.1", + "asExternalLinkStates": [ + { + "lsaType": "AS-external-LSA", + "linkStateId": "0.0.0.0", + "advertisingRouter": "2.2.2.2", + "networkMask": 0, + "metricType": "E2 (Larger than any link state path)", + "tos": 0, + "metric": 10, + "forwardAddress": "0.0.0.0", + "externalRouteTag": 0, + } + ], + } + test_func = partial( + topotest.router_json_cmp, r3, "show ip ospf database external json", input_dict + ) + assertmsg = "AS-External default not originated by r2 OSPF instance 2" + assert result is None, assertmsg + + step("Verify r2's OSPF instance 2 is AS-External default is installed on r3") + input_dict = { + "0.0.0.0/0": [ + { + "prefix": "0.0.0.0/0", + "prefixLen": 0, + "protocol": "ospf", + "distance": 20, + "nexthops": [ + { + "ip": "10.1.2.2", + "interfaceName": "r3-eth0", + } + ], + } + ] + } + test_func = partial( + topotest.router_json_cmp, r3, "show ip route 0.0.0.0/0 json", input_dict + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "0.0.0.0/0 from router r2 not installed on r3" + assert result is None, assertmsg + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args))