summaryrefslogtreecommitdiffstats
path: root/tests/topotests/ospf6_topo2/test_ospf6_topo2.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/topotests/ospf6_topo2/test_ospf6_topo2.py')
-rw-r--r--tests/topotests/ospf6_topo2/test_ospf6_topo2.py653
1 files changed, 653 insertions, 0 deletions
diff --git a/tests/topotests/ospf6_topo2/test_ospf6_topo2.py b/tests/topotests/ospf6_topo2/test_ospf6_topo2.py
new file mode 100644
index 0000000..f95f7bb
--- /dev/null
+++ b/tests/topotests/ospf6_topo2/test_ospf6_topo2.py
@@ -0,0 +1,653 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: ISC
+
+#
+# test_ospf6_topo2.py
+# Part of NetDEF Topology Tests
+#
+# Copyright (c) 2021 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+
+"""
+test_ospf6_topo2.py: Test the FRR OSPFv3 daemon.
+"""
+
+import os
+import sys
+from functools import partial
+import pytest
+
+# Save the Current Working Directory to find configuration files.
+CWD = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.join(CWD, "../"))
+
+# 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
+
+# Required to instantiate the topology builder class.
+
+pytestmark = [pytest.mark.ospf6d]
+
+
+def expect_lsas(router, area, lsas, wait=5, extra_params=""):
+ """
+ Run the OSPFv3 show LSA database command and expect the supplied LSAs.
+
+ Optional parameters:
+ * `wait`: amount of seconds to wait.
+ * `extra_params`: extra LSA database parameters.
+ * `inverse`: assert the inverse of the expected.
+ """
+ tgen = get_topogen()
+
+ command = "show ipv6 ospf6 database {} json".format(extra_params)
+
+ logger.info("waiting OSPFv3 router '{}' LSA".format(router))
+ test_func = partial(
+ topotest.router_json_cmp,
+ tgen.gears[router],
+ command,
+ {"areaScopedLinkStateDb": [{"areaId": area, "lsa": lsas}]},
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=wait, wait=1)
+ assertmsg = '"{}" convergence failure'.format(router)
+
+ assert result is None, assertmsg
+
+
+def expect_ospfv3_routes(router, routes, wait=5, type=None, detail=False):
+ "Run command `ipv6 ospf6 route` and expect route with type."
+ tgen = get_topogen()
+
+ if detail == False:
+ if type == None:
+ cmd = "show ipv6 ospf6 route json"
+ else:
+ cmd = "show ipv6 ospf6 route {} json".format(type)
+ else:
+ if type == None:
+ cmd = "show ipv6 ospf6 route detail json"
+ else:
+ cmd = "show ipv6 ospf6 route {} detail json".format(type)
+
+ logger.info("waiting OSPFv3 router '{}' route".format(router))
+ test_func = partial(
+ topotest.router_json_cmp, tgen.gears[router], cmd, {"routes": routes}
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=wait, wait=1)
+ assertmsg = '"{}" convergence failure'.format(router)
+
+ assert result is None, assertmsg
+
+
+def dont_expect_route(router, unexpected_route, type=None):
+ "Specialized test function to expect route go missing"
+ tgen = get_topogen()
+
+ if type == None:
+ cmd = "show ipv6 ospf6 route json"
+ else:
+ cmd = "show ipv6 ospf6 route {} json".format(type)
+
+ output = tgen.gears[router].vtysh_cmd(cmd, isjson=True)
+ if unexpected_route in output["routes"]:
+ return output["routes"][unexpected_route]
+ return None
+
+
+def build_topo(tgen):
+ "Build function"
+
+ # Create 4 routers
+ for routern in range(1, 5):
+ tgen.add_router("r{}".format(routern))
+
+ switch = tgen.add_switch("s1")
+ switch.add_link(tgen.gears["r1"])
+ switch.add_link(tgen.gears["r2"])
+
+ switch = tgen.add_switch("s2")
+ switch.add_link(tgen.gears["r2"])
+ switch.add_link(tgen.gears["r3"])
+
+ switch = tgen.add_switch("s3")
+ switch.add_link(tgen.gears["r2"])
+ switch.add_link(tgen.gears["r4"])
+
+ switch = tgen.add_switch("s4")
+ switch.add_link(tgen.gears["r4"], nodeif="r4-stubnet")
+
+
+def setup_module(mod):
+ "Sets up the pytest environment"
+ tgen = Topogen(build_topo, mod.__name__)
+ tgen.start_topology()
+
+ router_list = tgen.routers()
+ for rname, router in router_list.items():
+
+ daemon_file = "{}/{}/zebra.conf".format(CWD, rname)
+ if os.path.isfile(daemon_file):
+ router.load_config(TopoRouter.RD_ZEBRA, daemon_file)
+
+ daemon_file = "{}/{}/ospf6d.conf".format(CWD, rname)
+ if os.path.isfile(daemon_file):
+ router.load_config(TopoRouter.RD_OSPF6, daemon_file)
+
+ # Initialize all routers.
+ tgen.start_router()
+
+
+def test_wait_protocol_convergence():
+ "Wait for OSPFv3 to converge"
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ logger.info("waiting for protocols to converge")
+
+ def expect_neighbor_full(router, neighbor):
+ "Wait until OSPFv3 convergence."
+ logger.info("waiting OSPFv3 router '{}'".format(router))
+ test_func = partial(
+ topotest.router_json_cmp,
+ tgen.gears[router],
+ "show ipv6 ospf6 neighbor json",
+ {"neighbors": [{"neighborId": neighbor, "state": "Full"}]},
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=130, wait=1)
+ assertmsg = '"{}" convergence failure'.format(router)
+ assert result is None, assertmsg
+
+ expect_neighbor_full("r1", "10.254.254.2")
+ expect_neighbor_full("r2", "10.254.254.1")
+ expect_neighbor_full("r2", "10.254.254.3")
+ expect_neighbor_full("r2", "10.254.254.4")
+ expect_neighbor_full("r3", "10.254.254.2")
+ expect_neighbor_full("r4", "10.254.254.2")
+
+
+def test_ospfv3_expected_route_types():
+ "Test routers route type to determine if NSSA/Stub is working as expected."
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ logger.info("waiting for protocols to converge")
+
+ def expect_ospf6_route_types(router, expected_summary):
+ "Expect the correct route types."
+ logger.info("waiting OSPFv3 router '{}'".format(router))
+ test_func = partial(
+ topotest.router_json_cmp,
+ tgen.gears[router],
+ "show ipv6 ospf6 route summary json",
+ expected_summary,
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=10, wait=1)
+ assertmsg = '"{}" convergence failure'.format(router)
+ assert result is None, assertmsg
+
+ # Stub router: no external routes.
+ expect_ospf6_route_types(
+ "r1",
+ {
+ "numberOfIntraAreaRoutes": 1,
+ "numberOfInterAreaRoutes": 3,
+ "numberOfExternal1Routes": 0,
+ "numberOfExternal2Routes": 0,
+ },
+ )
+ # NSSA router: no external routes.
+ expect_ospf6_route_types(
+ "r4",
+ {
+ "numberOfIntraAreaRoutes": 1,
+ "numberOfInterAreaRoutes": 2,
+ "numberOfExternal1Routes": 0,
+ "numberOfExternal2Routes": 3,
+ },
+ )
+
+
+def test_ospf6_default_route():
+ "Wait for OSPFv3 default route in stub area."
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ logger.info("waiting for default route")
+
+ def expect_route(router, route, metric):
+ "Test OSPF6 route existence."
+ logger.info("waiting OSPFv3 router '{}' routes".format(router))
+ test_func = partial(
+ topotest.router_json_cmp,
+ tgen.gears[router],
+ "show ipv6 route json",
+ {route: [{"metric": metric}]},
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=5, wait=1)
+ assertmsg = '"{}" convergence failure'.format(router)
+ assert result is None, assertmsg
+
+ metric = 123
+ expect_lsas(
+ "r1",
+ "0.0.0.1",
+ [{"prefix": "::/0", "metric": metric}],
+ extra_params="inter-prefix detail",
+ )
+ expect_route("r1", "::/0", metric + 10)
+
+
+def test_redistribute_metrics():
+ """
+ Test that the configured metrics are honored when a static route is
+ redistributed.
+ """
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ # Add new static route on r3.
+ config = """
+ configure terminal
+ ipv6 route 2001:db8:500::/64 Null0
+ """
+ tgen.gears["r3"].vtysh_cmd(config)
+
+ route = {
+ "2001:db8:500::/64": {
+ "metricType": 2,
+ "metricCost": 10,
+ }
+ }
+ logger.info(
+ "Expecting AS-external route 2001:db8:500::/64 to show up with default metrics"
+ )
+ expect_ospfv3_routes("r2", route, wait=30, detail=True)
+
+ # Change the metric of redistributed routes of the static type on r3.
+ config = """
+ configure terminal
+ router ospf6
+ redistribute static metric 50 metric-type 1
+ """
+ tgen.gears["r3"].vtysh_cmd(config)
+
+ # Check if r3 reinstalled 2001:db8:500::/64 using the new metric type and value.
+ route = {
+ "2001:db8:500::/64": {
+ "metricType": 1,
+ "metricCost": 60,
+ }
+ }
+ logger.info(
+ "Expecting AS-external route 2001:db8:500::/64 to show up with updated metric type and value"
+ )
+ expect_ospfv3_routes("r2", route, wait=30, detail=True)
+
+
+def test_nssa_lsa_type7():
+ """
+ Test that static route gets announced as external route when redistributed
+ and gets removed when redistribution stops.
+ """
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ #
+ # Add new static route and check if it gets announced as LSA Type-7.
+ #
+ config = """
+ configure terminal
+ ipv6 route 2001:db8:100::/64 Null0
+ """
+ tgen.gears["r2"].vtysh_cmd(config)
+
+ lsas = [
+ {
+ "type": "NSSA",
+ "advertisingRouter": "10.254.254.2",
+ "prefix": "2001:db8:100::/64",
+ "forwardingAddress": "2001:db8:3::1",
+ }
+ ]
+ route = {
+ "2001:db8:100::/64": {
+ "pathType": "E2",
+ "nextHops": [{"nextHop": "::", "interfaceName": "r4-eth0"}],
+ }
+ }
+
+ logger.info("Expecting LSA type-7 and OSPFv3 route 2001:db8:100::/64 to show up")
+ expect_lsas("r4", "0.0.0.2", lsas, wait=30, extra_params="type-7 detail")
+ expect_ospfv3_routes("r4", route, wait=30)
+
+ #
+ # Remove static route and check for LSA Type-7 removal.
+ #
+ config = """
+ configure terminal
+ no ipv6 route 2001:db8:100::/64 Null0
+ """
+ tgen.gears["r2"].vtysh_cmd(config)
+
+ def dont_expect_lsa(unexpected_lsa):
+ "Specialized test function to expect LSA go missing"
+ output = tgen.gears["r4"].vtysh_cmd(
+ "show ipv6 ospf6 database type-7 detail json", isjson=True
+ )
+ for lsa in output["areaScopedLinkStateDb"][0]["lsa"]:
+ if lsa["prefix"] == unexpected_lsa["prefix"]:
+ if lsa["forwardingAddress"] == unexpected_lsa["forwardingAddress"]:
+ return lsa
+ return None
+
+ logger.info("Expecting LSA type-7 and OSPFv3 route 2001:db8:100::/64 to go away")
+
+ # Test that LSA doesn't exist.
+ test_func = partial(dont_expect_lsa, lsas[0])
+ _, result = topotest.run_and_expect(test_func, None, count=130, wait=1)
+ assertmsg = '"{}" LSA still exists'.format("r4")
+ assert result is None, assertmsg
+
+ # Test that route doesn't exist.
+ test_func = partial(dont_expect_route, "r4", "2001:db8:100::/64")
+ _, result = topotest.run_and_expect(test_func, None, count=130, wait=1)
+ assertmsg = '"{}" route still exists'.format("r4")
+ assert result is None, assertmsg
+
+
+def test_nssa_no_summary():
+ """
+ Test the following:
+ * Type-3 inter-area routes should be removed when the NSSA no-summary option
+ is configured;
+ * A type-3 inter-area default route should be originated into the NSSA area
+ when the no-summary option is configured;
+ * Once the no-summary option is unconfigured, all previously existing
+ Type-3 inter-area routes should be re-added, and the inter-area default
+ route removed.
+ """
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ #
+ # Configure area 1 as a NSSA totally stub area.
+ #
+ config = """
+ configure terminal
+ router ospf6
+ area 2 nssa no-summary
+ """
+ tgen.gears["r2"].vtysh_cmd(config)
+
+ logger.info("Expecting inter-area routes to be removed")
+ for route in ["2001:db8:1::/64", "2001:db8:2::/64"]:
+ test_func = partial(dont_expect_route, "r4", route, type="inter-area")
+ _, result = topotest.run_and_expect(test_func, None, count=130, wait=1)
+ assertmsg = "{}'s {} inter-area route still exists".format("r4", route)
+ assert result is None, assertmsg
+
+ logger.info("Expecting inter-area default-route to be added")
+ routes = {"::/0": {}}
+ expect_ospfv3_routes("r4", routes, wait=30, type="inter-area")
+
+ #
+ # Configure area 1 as a regular NSSA area.
+ #
+ config = """
+ configure terminal
+ router ospf6
+ area 2 nssa
+ """
+ tgen.gears["r2"].vtysh_cmd(config)
+
+ logger.info("Expecting inter-area routes to be re-added")
+ routes = {"2001:db8:1::/64": {}, "2001:db8:2::/64": {}}
+ expect_ospfv3_routes("r4", routes, wait=30, type="inter-area")
+
+ logger.info("Expecting inter-area default route to be removed")
+ test_func = partial(dont_expect_route, "r4", "::/0", type="inter-area")
+ _, result = topotest.run_and_expect(test_func, None, count=130, wait=1)
+ assertmsg = "{}'s inter-area default route still exists".format("r4")
+ assert result is None, assertmsg
+
+
+def test_nssa_default_originate():
+ """
+ Test the following:
+ * A type-7 default route should be originated into the NSSA area
+ when the default-information-originate option is configured;
+ * Once the default-information-originate option is unconfigured, the
+ previously originated Type-7 default route should be removed.
+ """
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ #
+ # Configure r2 to announce a Type-7 default route.
+ #
+ config = """
+ configure terminal
+ router ospf6
+ no default-information originate
+ area 2 nssa default-information-originate
+ """
+ tgen.gears["r2"].vtysh_cmd(config)
+
+ logger.info("Expecting Type-7 default-route to be added")
+ routes = {"::/0": {}}
+ expect_ospfv3_routes("r4", routes, wait=30, type="external-2")
+
+ #
+ # Configure r2 to stop announcing a Type-7 default route.
+ #
+ config = """
+ configure terminal
+ router ospf6
+ area 2 nssa
+ """
+ tgen.gears["r2"].vtysh_cmd(config)
+
+ logger.info("Expecting Type-7 default route to be removed")
+ test_func = partial(dont_expect_route, "r4", "::/0", type="external-2")
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assertmsg = "r4's Type-7 default route still exists"
+ assert result is None, assertmsg
+
+
+def test_area_filters():
+ """
+ Test ABR import/export filters.
+ """
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ #
+ # Configure import/export filters on r2 (ABR for area 2).
+ #
+ config = """
+ configure terminal
+ ipv6 access-list ACL_IMPORT seq 5 permit 2001:db8:2::/64
+ ipv6 access-list ACL_IMPORT seq 10 deny any
+ ipv6 access-list ACL_EXPORT seq 10 deny any
+ router ospf6
+ area 1 import-list ACL_IMPORT
+ area 1 export-list ACL_EXPORT
+ """
+ tgen.gears["r2"].vtysh_cmd(config)
+
+ logger.info("Expecting inter-area routes to be removed on r1")
+ for route in ["::/0", "2001:db8:3::/64"]:
+ test_func = partial(dont_expect_route, "r1", route, type="inter-area")
+ _, result = topotest.run_and_expect(test_func, None, count=130, wait=1)
+ assertmsg = "{}'s {} inter-area route still exists".format("r1", route)
+ assert result is None, assertmsg
+
+ logger.info("Expecting inter-area routes to be removed on r3")
+ for route in ["2001:db8:1::/64"]:
+ test_func = partial(dont_expect_route, "r3", route, type="inter-area")
+ _, result = topotest.run_and_expect(test_func, None, count=130, wait=1)
+ assertmsg = "{}'s {} inter-area route still exists".format("r3", route)
+ assert result is None, assertmsg
+
+ #
+ # Update the ACLs used by the import/export filters.
+ #
+ config = """
+ configure terminal
+ ipv6 access-list ACL_IMPORT seq 6 permit 2001:db8:3::/64
+ ipv6 access-list ACL_EXPORT seq 5 permit 2001:db8:1::/64
+ """
+ tgen.gears["r2"].vtysh_cmd(config)
+
+ logger.info("Expecting 2001:db8:3::/64 to be re-added on r1")
+ routes = {"2001:db8:3::/64": {}}
+ expect_ospfv3_routes("r1", routes, wait=30, type="inter-area")
+ logger.info("Expecting 2001:db8:1::/64 to be re-added on r3")
+ routes = {"2001:db8:1::/64": {}}
+ expect_ospfv3_routes("r3", routes, wait=30, type="inter-area")
+
+ #
+ # Unconfigure r2's ABR import/export filters.
+ #
+ config = """
+ configure terminal
+ router ospf6
+ no area 1 import-list ACL_IMPORT
+ no area 1 export-list ACL_EXPORT
+ """
+ tgen.gears["r2"].vtysh_cmd(config)
+
+ logger.info("Expecting ::/0 to be re-added on r1")
+ routes = {"::/0": {}}
+ expect_ospfv3_routes("r1", routes, wait=30, type="inter-area")
+
+
+def test_nssa_range():
+ """
+ Test NSSA ABR ranges.
+ """
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ # Configure new addresses on r4 and enable redistribution of connected
+ # routes.
+ config = """
+ configure terminal
+ interface r4-stubnet
+ ipv6 address 2001:db8:1000::1/128
+ ipv6 address 2001:db8:1000::2/128
+ router ospf6
+ redistribute connected
+ """
+ tgen.gears["r4"].vtysh_cmd(config)
+ logger.info("Expecting NSSA-translated external routes to be added on r3")
+ routes = {"2001:db8:1000::1/128": {}, "2001:db8:1000::2/128": {}}
+ expect_ospfv3_routes("r3", routes, wait=30, type="external-2")
+
+ # Configure an NSSA range on r2 (ABR for area 2).
+ config = """
+ configure terminal
+ router ospf6
+ area 2 nssa range 2001:db8:1000::/64
+ """
+ tgen.gears["r2"].vtysh_cmd(config)
+ logger.info("Expecting summarized routes to be removed from r3")
+ for route in ["2001:db8:1000::1/128", "2001:db8:1000::2/128"]:
+ test_func = partial(dont_expect_route, "r3", route, type="external-2")
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assertmsg = "{}'s {} summarized route still exists".format("r3", route)
+ assert result is None, assertmsg
+ logger.info("Expecting NSSA range to be added on r3")
+ routes = {
+ "2001:db8:1000::/64": {
+ "metricType": 2,
+ "metricCost": 20,
+ "metricCostE2": 10,
+ }
+ }
+ expect_ospfv3_routes("r3", routes, wait=30, type="external-2", detail=True)
+
+ # Change the NSSA range cost.
+ config = """
+ configure terminal
+ router ospf6
+ area 2 nssa range 2001:db8:1000::/64 cost 1000
+ """
+ tgen.gears["r2"].vtysh_cmd(config)
+ logger.info("Expecting NSSA range to be updated with new cost")
+ routes = {
+ "2001:db8:1000::/64": {
+ "metricType": 2,
+ "metricCost": 20,
+ "metricCostE2": 1000,
+ }
+ }
+ expect_ospfv3_routes("r3", routes, wait=30, type="external-2", detail=True)
+
+ # Configure the NSSA range to not be advertised.
+ config = """
+ configure terminal
+ router ospf6
+ area 2 nssa range 2001:db8:1000::/64 not-advertise
+ """
+ tgen.gears["r2"].vtysh_cmd(config)
+ logger.info("Expecting NSSA summary route to be removed")
+ route = "2001:db8:1000::/64"
+ test_func = partial(dont_expect_route, "r3", route, type="external-2")
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assertmsg = "{}'s {} NSSA summary route still exists".format("r3", route)
+ assert result is None, assertmsg
+
+ # Remove the NSSA range.
+ config = """
+ configure terminal
+ router ospf6
+ no area 2 nssa range 2001:db8:1000::/64
+ """
+ tgen.gears["r2"].vtysh_cmd(config)
+ logger.info("Expecting previously summarized routes to be re-added")
+ routes = {
+ "2001:db8:1000::1/128": {
+ "metricType": 2,
+ "metricCostE2": 20,
+ },
+ "2001:db8:1000::2/128": {
+ "metricType": 2,
+ "metricCostE2": 20,
+ },
+ }
+ expect_ospfv3_routes("r3", routes, wait=30, type="external-2", detail=True)
+
+
+def teardown_module(_mod):
+ "Teardown the pytest environment"
+ tgen = get_topogen()
+ tgen.stop_topology()
+
+
+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))