From 1b0d49545c8516c3a377f840ac242b0f1d882cb7 Mon Sep 17 00:00:00 2001 From: Acee Lindem Date: Wed, 22 Jan 2025 20:32:50 +0000 Subject: [PATCH] tests: OSPF topotest for next-hop pruning OSPF topotest to test OSPF next-hop pruning on installation into zebra routing table. Also fix multicast_pim_dr_nondr_test topotest which had a duplicate OSPF route in the results. Signed-off-by: Acee Lindem X (cherry picked from commit 1d96c58375baf7eeab65a614a89a5fc4cb7f7031) --- ..._pim_dr_nondr_with_transit_router_topo3.py | 6 - .../topotests/ospf_prune_next_hop/r1/frr.conf | 23 ++ .../topotests/ospf_prune_next_hop/r2/frr.conf | 23 ++ .../topotests/ospf_prune_next_hop/r3/frr.conf | 35 ++ .../topotests/ospf_prune_next_hop/r4/frr.conf | 34 ++ .../topotests/ospf_prune_next_hop/r5/frr.conf | 34 ++ .../topotests/ospf_prune_next_hop/r6/frr.conf | 34 ++ .../topotests/ospf_prune_next_hop/r7/frr.conf | 14 + .../topotests/ospf_prune_next_hop/r8/frr.conf | 14 + .../test_ospf_prune_next_hop.py | 343 ++++++++++++++++++ 10 files changed, 554 insertions(+), 6 deletions(-) create mode 100644 tests/topotests/ospf_prune_next_hop/r1/frr.conf create mode 100644 tests/topotests/ospf_prune_next_hop/r2/frr.conf create mode 100644 tests/topotests/ospf_prune_next_hop/r3/frr.conf create mode 100644 tests/topotests/ospf_prune_next_hop/r4/frr.conf create mode 100644 tests/topotests/ospf_prune_next_hop/r5/frr.conf create mode 100644 tests/topotests/ospf_prune_next_hop/r6/frr.conf create mode 100644 tests/topotests/ospf_prune_next_hop/r7/frr.conf create mode 100644 tests/topotests/ospf_prune_next_hop/r8/frr.conf create mode 100644 tests/topotests/ospf_prune_next_hop/test_ospf_prune_next_hop.py diff --git a/tests/topotests/multicast_pim_dr_nondr_test/test_pim_dr_nondr_with_transit_router_topo3.py b/tests/topotests/multicast_pim_dr_nondr_test/test_pim_dr_nondr_with_transit_router_topo3.py index 8d91826022..a32b82c7f4 100755 --- a/tests/topotests/multicast_pim_dr_nondr_test/test_pim_dr_nondr_with_transit_router_topo3.py +++ b/tests/topotests/multicast_pim_dr_nondr_test/test_pim_dr_nondr_with_transit_router_topo3.py @@ -638,12 +638,6 @@ def pre_config_for_source_dr_tests( "interfaceName": "r5-r4-eth1", "weight": 1, }, - { - "ip": "10.0.3.1", - "afi": "ipv4", - "interfaceName": "r5-r4-eth1", - "weight": 1, - }, ], } ] diff --git a/tests/topotests/ospf_prune_next_hop/r1/frr.conf b/tests/topotests/ospf_prune_next_hop/r1/frr.conf new file mode 100644 index 0000000000..130872e8d0 --- /dev/null +++ b/tests/topotests/ospf_prune_next_hop/r1/frr.conf @@ -0,0 +1,23 @@ +! +hostname r1 +ip forwarding +! +interface r1-eth0 + ip address 10.1.1.1/24 + ip ospf network broadcast + ip ospf hello-interval 1 + ip ospf dead-interval 10 +! +interface r1-eth1 + ip address 10.1.2.1/24 + ip ospf network broadcast + ip ospf hello-interval 1 + ip ospf dead-interval 10 +! +! +! +router ospf + ospf router-id 1.1.1.1 + distance 20 + network 10.1.1.0/24 area 0 + network 10.1.2.0/24 area 0 diff --git a/tests/topotests/ospf_prune_next_hop/r2/frr.conf b/tests/topotests/ospf_prune_next_hop/r2/frr.conf new file mode 100644 index 0000000000..4268aea857 --- /dev/null +++ b/tests/topotests/ospf_prune_next_hop/r2/frr.conf @@ -0,0 +1,23 @@ +! +hostname r2 +ip forwarding +! +interface r2-eth0 + ip address 10.1.1.2/24 + ip ospf network broadcast + ip ospf hello-interval 1 + ip ospf dead-interval 10 +! +interface r2-eth1 + ip address 10.1.2.1/24 + ip ospf network broadcast + ip ospf hello-interval 1 + ip ospf dead-interval 10 +! +! +! +router ospf + ospf router-id 2.2.2.2 + distance 20 + network 10.1.1.0/24 area 0 + network 10.1.2.0/24 area 0 diff --git a/tests/topotests/ospf_prune_next_hop/r3/frr.conf b/tests/topotests/ospf_prune_next_hop/r3/frr.conf new file mode 100644 index 0000000000..21d6506d7c --- /dev/null +++ b/tests/topotests/ospf_prune_next_hop/r3/frr.conf @@ -0,0 +1,35 @@ +! +hostname r3 +ip forwarding +! +interface r3-eth0 + ip address 20.1.3.3/24 + ip ospf network broadcast + ip ospf hello-interval 1 + ip ospf dead-interval 10 +! +interface r3-eth1 + ip address 10.1.3.3/24 + ip ospf network broadcast + ip ospf hello-interval 1 + ip ospf dead-interval 10 +! +interface r3-eth2 + ip address 10.1.2.3/24 + ip ospf network broadcast + ip ospf hello-interval 1 + ip ospf dead-interval 10 +! +! +! +router ospf + ospf router-id 3.3.3.3 + distance 20 + network 10.1.2.0/24 area 0 + network 10.1.3.0/24 area 0 + network 20.1.3.0/24 area 1 + area 1 range 20.1.0.0/16 + redistribute static +! +! +ip route 100.100.100.100/32 Null0 diff --git a/tests/topotests/ospf_prune_next_hop/r4/frr.conf b/tests/topotests/ospf_prune_next_hop/r4/frr.conf new file mode 100644 index 0000000000..e66e93e20c --- /dev/null +++ b/tests/topotests/ospf_prune_next_hop/r4/frr.conf @@ -0,0 +1,34 @@ +! +hostname r4 +ip forwarding +! +interface r4-eth0 + ip address 20.1.4.4/24 + ip ospf network broadcast + ip ospf hello-interval 1 + ip ospf dead-interval 10 +! +interface r4-eth1 + ip address 10.1.3.4/24 + ip ospf network broadcast + ip ospf hello-interval 1 + ip ospf dead-interval 10 +! +interface r4-eth2 + ip address 10.1.2.4/24 + ip ospf network broadcast + ip ospf hello-interval 1 + ip ospf dead-interval 10 +! +! +router ospf + ospf router-id 4.4.4.4 + distance 20 + network 10.1.2.0/24 area 0 + network 10.1.3.0/24 area 0 + network 20.1.4.0/24 area 1 + area 1 range 20.1.0.0/16 + redistribute static +! +! +ip route 100.100.100.100/32 Null0 diff --git a/tests/topotests/ospf_prune_next_hop/r5/frr.conf b/tests/topotests/ospf_prune_next_hop/r5/frr.conf new file mode 100644 index 0000000000..2d1dad9925 --- /dev/null +++ b/tests/topotests/ospf_prune_next_hop/r5/frr.conf @@ -0,0 +1,34 @@ +! +hostname r5 +ip forwarding +! +interface r5-eth0 + ip address 20.1.5.5/24 + ip ospf network broadcast + ip ospf hello-interval 1 + ip ospf dead-interval 10 +! +interface r5-eth1 + ip address 10.1.3.5/24 + ip ospf network broadcast + ip ospf hello-interval 1 + ip ospf dead-interval 10 +! +interface r5-eth2 + ip address 10.1.2.5/24 + ip ospf network broadcast + ip ospf hello-interval 1 + ip ospf dead-interval 10 +! +! +router ospf + ospf router-id 5.5.5.5 + distance 20 + network 10.1.2.0/24 area 0 + network 10.1.3.0/24 area 0 + network 20.1.5.0/24 area 1 + area 1 range 20.1.0.0/16 + redistribute static +! +! +ip route 100.100.100.100/32 Null0 diff --git a/tests/topotests/ospf_prune_next_hop/r6/frr.conf b/tests/topotests/ospf_prune_next_hop/r6/frr.conf new file mode 100644 index 0000000000..f343ee7c35 --- /dev/null +++ b/tests/topotests/ospf_prune_next_hop/r6/frr.conf @@ -0,0 +1,34 @@ +! +hostname r6 +ip forwarding +! +interface r6-eth0 + ip address 20.1.6.6/24 + ip ospf network broadcast + ip ospf hello-interval 1 + ip ospf dead-interval 10 +! +interface r6-eth1 + ip address 10.1.3.6/24 + ip ospf network broadcast + ip ospf hello-interval 1 + ip ospf dead-interval 10 +! +interface r6-eth2 + ip address 10.1.2.6/24 + ip ospf network broadcast + ip ospf hello-interval 1 + ip ospf dead-interval 10 +! +! +router ospf + ospf router-id 6.6.6.6 + distance 20 + network 10.1.2.0/24 area 0 + network 10.1.3.0/24 area 0 + network 20.1.6.0/24 area 1 + area 1 range 20.1.0.0/16 + redistribute static +! +! +ip route 100.100.100.100/32 Null0 diff --git a/tests/topotests/ospf_prune_next_hop/r7/frr.conf b/tests/topotests/ospf_prune_next_hop/r7/frr.conf new file mode 100644 index 0000000000..1eeb88c9d0 --- /dev/null +++ b/tests/topotests/ospf_prune_next_hop/r7/frr.conf @@ -0,0 +1,14 @@ +! +hostname r7 +ip forwarding +! +interface r7-eth0 + ip address 10.1.3.7/24 + ip ospf network broadcast + ip ospf hello-interval 1 + ip ospf dead-interval 10 +! +router ospf + ospf router-id 7.7.7.7 + distance 20 + network 10.1.3.0/24 area 0 diff --git a/tests/topotests/ospf_prune_next_hop/r8/frr.conf b/tests/topotests/ospf_prune_next_hop/r8/frr.conf new file mode 100644 index 0000000000..d8facbc01f --- /dev/null +++ b/tests/topotests/ospf_prune_next_hop/r8/frr.conf @@ -0,0 +1,14 @@ +! +hostname r8 +ip forwarding +! +interface r8-eth0 + ip address 10.1.3.8/24 + ip ospf network broadcast + ip ospf hello-interval 1 + ip ospf dead-interval 10 +! +router ospf + ospf router-id 8.8.8.8 + distance 20 + network 10.1.3.0/24 area 0 diff --git a/tests/topotests/ospf_prune_next_hop/test_ospf_prune_next_hop.py b/tests/topotests/ospf_prune_next_hop/test_ospf_prune_next_hop.py new file mode 100644 index 0000000000..88aa6b2e36 --- /dev/null +++ b/tests/topotests/ospf_prune_next_hop/test_ospf_prune_next_hop.py @@ -0,0 +1,343 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_ospf_prune_next_hop +# +# Copyright (c) 2025 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, get_topogen +from lib.topolog import logger + +from lib.common_config import ( + step, +) + + +""" +test_ospf_metric_propagation.py: Test OSPF/BGP metric propagation +""" + +TOPOLOGY = """ + 20.1.3.0 20.1.4.0 20.1.5.0 20.1.6.0 + eth0 | .3 eth0 | .4 eth0 | .5 eth0 | .6 + +--+-+ +--+-+ +--+-+ +--+-+ +10.1 3.0 | R3 | | R4 | | R5 | | R6 | + +-----+ | | |eth1 | |eth1 | | 10.1.3.0/24 + | | | | +---- | |--- + -+---+ + | +--+-+ +--+-+ +--+-+ +--+-+ | + | eth2 | .3 eth2 | .4 eth2 | .5 eth2 | | +eth0| | | | | | eth0 + +--+--+ ++-------+ Switch Network +---------++ +--+---+ + | R7 | | 10.1.2.0/24 | | R8 | + +-----+ +------------------------------------+ +------+ + eth1 | .2 + +--+--+ + | R2 | + +--+--+ + eth0 | .2 + 10.1.1.0/24 | + eth0 | .1 + +--+--+ + | R1 | + +-----+ + +""" + +# 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 8 routers + tgen.add_router("r1") + tgen.add_router("r2") + tgen.add_router("r3") + tgen.add_router("r4") + tgen.add_router("r5") + tgen.add_router("r6") + tgen.add_router("r7") + tgen.add_router("r8") + + # Interconect router 1, 2 (0) + switch = tgen.add_switch("s1-1-2") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + # Add standalone networks to router 3 + switch = tgen.add_switch("s2-3") + switch.add_link(tgen.gears["r3"]) + + # Add standalone network to router 4 + switch = tgen.add_switch("s3-4") + switch.add_link(tgen.gears["r4"]) + + # Add standalone network to router 5 + switch = tgen.add_switch("s4-5") + switch.add_link(tgen.gears["r5"]) + + # Add standalone network to router 6 + switch = tgen.add_switch("s5-6") + switch.add_link(tgen.gears["r6"]) + + # Interconect routers 3, 4, 5, and 6 + switch = tgen.add_switch("s6-3") + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r7"]) + switch = tgen.add_switch("s7-4") + switch.add_link(tgen.gears["r4"]) + switch = tgen.add_switch("s8-5") + switch.add_link(tgen.gears["r5"]) + switch = tgen.add_switch("s9-6") + switch.add_link(tgen.gears["r6"]) + switch.add_link(tgen.gears["r8"]) + + # Interconect routers 2, 3, 4, 5, and 6 + switch = tgen.add_switch("s10-lan") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r4"]) + switch.add_link(tgen.gears["r5"]) + switch.add_link(tgen.gears["r6"]) + + +def setup_module(mod): + logger.info("OSPF Prune Next Hops:\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_intra_area_route_prune(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip("Skipped because of router(s) failure") + + step("Test OSPF intra-area route 10.1.3.0/24 duplicate nexthops already pruned") + # Verify OSPF route 10.1.3.0/24 nexthops pruned already. + r1 = tgen.gears["r1"] + input_dict = { + "10.1.3.0/24": { + "routeType": "N", + "transit": True, + "cost": 30, + "area": "0.0.0.0", + "nexthops": [ + {"ip": "10.1.1.2", "via": "r1-eth0", "advertisedRouter": "8.8.8.8"} + ], + } + } + test_func = partial( + topotest.router_json_cmp, r1, "show ip ospf route detail json", input_dict + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "OSPF Intra-Area route 10.1.3.0/24 mismatch on router r1" + assert result is None, assertmsg + + step("Test IP route 10.1.3.0/24 installed") + input_dict = { + "10.1.3.0/24": [ + { + "prefix": "10.1.3.0/24", + "prefixLen": 24, + "protocol": "ospf", + "vrfName": "default", + "distance": 20, + "metric": 30, + "installed": True, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "ip": "10.1.1.2", + "afi": "ipv4", + "interfaceName": "r1-eth0", + "active": True, + "weight": 1, + } + ], + } + ] + } + test_func = partial( + topotest.router_json_cmp, r1, "show ip route 10.1.3.0/24 json", input_dict + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "IP route 10.1.3.0/24 mismatch on router r1" + assert result is None, assertmsg + + +def test_inter_area_route_prune(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip("Skipped because of router(s) failure") + + step("Test OSPF inter-area route 20.1.0.0/16 duplicate nexthops installed") + # Verify OSPF route 20.1.0.0/16 duplication nexthops + r1 = tgen.gears["r1"] + input_dict = { + "20.1.0.0/16": { + "routeType": "N IA", + "cost": 30, + "area": "0.0.0.0", + "nexthops": [ + {"ip": "10.1.1.2", "via": "r1-eth0", "advertisedRouter": "3.3.3.3"}, + {"ip": "10.1.1.2", "via": "r1-eth0", "advertisedRouter": "4.4.4.4"}, + {"ip": "10.1.1.2", "via": "r1-eth0", "advertisedRouter": "5.5.5.5"}, + {"ip": "10.1.1.2", "via": "r1-eth0", "advertisedRouter": "6.6.6.6"}, + ], + } + } + test_func = partial( + topotest.router_json_cmp, r1, "show ip ospf route detail json", input_dict + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "OSPF Inter-Area route 20.1.0.0/16 mismatch on router r1" + assert result is None, assertmsg + + step("Test IP route 10.1.3.0/24 installed with pruned next-hops") + input_dict = { + "20.1.0.0/16": [ + { + "prefix": "20.1.0.0/16", + "prefixLen": 16, + "protocol": "ospf", + "vrfName": "default", + "distance": 20, + "metric": 30, + "installed": True, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "ip": "10.1.1.2", + "afi": "ipv4", + "interfaceName": "r1-eth0", + "active": True, + "weight": 1, + } + ], + } + ] + } + test_func = partial( + topotest.router_json_cmp, r1, "show ip route 20.1.0.0/16 json", input_dict + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "IP route 20.1.1.0/24 mismatch on router r1" + assert result is None, assertmsg + + +def test_as_external_route_prune(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip("Skipped because of router(s) failure") + + step("Test OSPF AS external route 100.100.100.100 duplicate nexthops installed") + # Verify OSPF route 20.1.0.0/16 duplication nexthops + r1 = tgen.gears["r1"] + input_dict = { + "100.100.100.100/32": { + "routeType": "N E2", + "cost": 20, + "type2cost": 20, + "tag": 0, + "nexthops": [ + {"ip": "10.1.1.2", "via": "r1-eth0", "advertisedRouter": "3.3.3.3"}, + {"ip": "10.1.1.2", "via": "r1-eth0", "advertisedRouter": "4.4.4.4"}, + {"ip": "10.1.1.2", "via": "r1-eth0", "advertisedRouter": "5.5.5.5"}, + {"ip": "10.1.1.2", "via": "r1-eth0", "advertisedRouter": "6.6.6.6"}, + ], + } + } + test_func = partial( + topotest.router_json_cmp, r1, "show ip ospf route detail json", input_dict + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "OSPF AS external route 100.100.100.100/32 mismatch on router r1" + assert result is None, assertmsg + + step("Test IP route 100.100.100.100/32 installed with pruned next-hops") + input_dict = { + "100.100.100.100/32": [ + { + "prefix": "100.100.100.100/32", + "prefixLen": 32, + "protocol": "ospf", + "vrfName": "default", + "distance": 20, + "metric": 20, + "installed": True, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "ip": "10.1.1.2", + "afi": "ipv4", + "interfaceName": "r1-eth0", + "active": True, + "weight": 1, + } + ], + } + ] + } + test_func = partial( + topotest.router_json_cmp, + r1, + "show ip route 100.100.100.100/32 json", + input_dict, + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = "IP route 100.100.100.100/32 mismatch on router r1" + 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)) -- 2.39.5