#!/usr/bin/env python
+# SPDX-License-Identifier: ISC
#
# test_isis_sr_topo1.py
# Copyright (c) 2019 by
# Network Device Education Foundation, Inc. ("NetDEF")
#
-# Permission to use, copy, modify, and/or distribute this software
-# for any purpose with or without fee is hereby granted, provided
-# that the above copyright notice and this permission notice appear
-# in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
-# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
-# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
-# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
-# OF THIS SOFTWARE.
-#
"""
test_isis_sr_te_topo1.py:
import sys
import pytest
import json
-import re
-from time import sleep
from functools import partial
# Save the Current Working Directory to find configuration files.
from lib.topolog import logger
# Required to instantiate the topology builder class.
-from mininet.topo import Topo
pytestmark = [pytest.mark.bgpd, pytest.mark.isisd, pytest.mark.pathd]
-class TemplateTopo(Topo):
- "Test topology builder"
+def build_topo(tgen):
+ "Build function"
- def build(self, *_args, **_opts):
- "Build function"
- tgen = get_topogen(self)
+ #
+ # Define FRR Routers
+ #
+ for router in ["rt1", "rt2", "rt3", "rt4", "rt5", "rt6", "dst"]:
+ tgen.add_router(router)
- #
- # Define FRR Routers
- #
- for router in ["rt1", "rt2", "rt3", "rt4", "rt5", "rt6", "dst"]:
- tgen.add_router(router)
+ #
+ # Define connections
+ #
+ switch = tgen.add_switch("s1")
+ switch.add_link(tgen.gears["rt1"], nodeif="eth-sw1")
+ switch.add_link(tgen.gears["rt2"], nodeif="eth-sw1")
+ switch.add_link(tgen.gears["rt3"], nodeif="eth-sw1")
- #
- # Define connections
- #
- switch = tgen.add_switch("s1")
- switch.add_link(tgen.gears["rt1"], nodeif="eth-sw1")
- switch.add_link(tgen.gears["rt2"], nodeif="eth-sw1")
- switch.add_link(tgen.gears["rt3"], nodeif="eth-sw1")
+ switch = tgen.add_switch("s2")
+ switch.add_link(tgen.gears["rt2"], nodeif="eth-rt4-1")
+ switch.add_link(tgen.gears["rt4"], nodeif="eth-rt2-1")
- switch = tgen.add_switch("s2")
- switch.add_link(tgen.gears["rt2"], nodeif="eth-rt4-1")
- switch.add_link(tgen.gears["rt4"], nodeif="eth-rt2-1")
+ switch = tgen.add_switch("s3")
+ switch.add_link(tgen.gears["rt2"], nodeif="eth-rt4-2")
+ switch.add_link(tgen.gears["rt4"], nodeif="eth-rt2-2")
- switch = tgen.add_switch("s3")
- switch.add_link(tgen.gears["rt2"], nodeif="eth-rt4-2")
- switch.add_link(tgen.gears["rt4"], nodeif="eth-rt2-2")
+ switch = tgen.add_switch("s4")
+ switch.add_link(tgen.gears["rt3"], nodeif="eth-rt5-1")
+ switch.add_link(tgen.gears["rt5"], nodeif="eth-rt3-1")
- switch = tgen.add_switch("s4")
- switch.add_link(tgen.gears["rt3"], nodeif="eth-rt5-1")
- switch.add_link(tgen.gears["rt5"], nodeif="eth-rt3-1")
+ switch = tgen.add_switch("s5")
+ switch.add_link(tgen.gears["rt3"], nodeif="eth-rt5-2")
+ switch.add_link(tgen.gears["rt5"], nodeif="eth-rt3-2")
- switch = tgen.add_switch("s5")
- switch.add_link(tgen.gears["rt3"], nodeif="eth-rt5-2")
- switch.add_link(tgen.gears["rt5"], nodeif="eth-rt3-2")
+ switch = tgen.add_switch("s6")
+ switch.add_link(tgen.gears["rt4"], nodeif="eth-rt5")
+ switch.add_link(tgen.gears["rt5"], nodeif="eth-rt4")
- switch = tgen.add_switch("s6")
- switch.add_link(tgen.gears["rt4"], nodeif="eth-rt5")
- switch.add_link(tgen.gears["rt5"], nodeif="eth-rt4")
+ switch = tgen.add_switch("s7")
+ switch.add_link(tgen.gears["rt4"], nodeif="eth-rt6")
+ switch.add_link(tgen.gears["rt6"], nodeif="eth-rt4")
- switch = tgen.add_switch("s7")
- switch.add_link(tgen.gears["rt4"], nodeif="eth-rt6")
- switch.add_link(tgen.gears["rt6"], nodeif="eth-rt4")
+ switch = tgen.add_switch("s8")
+ switch.add_link(tgen.gears["rt5"], nodeif="eth-rt6")
+ switch.add_link(tgen.gears["rt6"], nodeif="eth-rt5")
- switch = tgen.add_switch("s8")
- switch.add_link(tgen.gears["rt5"], nodeif="eth-rt6")
- switch.add_link(tgen.gears["rt6"], nodeif="eth-rt5")
-
- switch = tgen.add_switch("s9")
- switch.add_link(tgen.gears["rt6"], nodeif="eth-dst")
- switch.add_link(tgen.gears["dst"], nodeif="eth-rt6")
+ switch = tgen.add_switch("s9")
+ switch.add_link(tgen.gears["rt6"], nodeif="eth-dst")
+ switch.add_link(tgen.gears["dst"], nodeif="eth-rt6")
def setup_module(mod):
"Sets up the pytest environment"
- tgen = Topogen(TemplateTopo, mod.__name__)
+ tgen = Topogen(build_topo, mod.__name__)
frrdir = tgen.config.get(tgen.CONFIG_SECTION, "frrdir")
if not os.path.isfile(os.path.join(frrdir, "pathd")):
router_list = tgen.routers()
# For all registered routers, load the zebra configuration file
- for rname, router in router_list.iteritems():
+ for rname, router in router_list.items():
router.load_config(
TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname))
)
return cmp_json_output(rname, command, reference, True)
-def add_candidate_path(rname, endpoint, pref, name, segment_list="default"):
+def compare_json_test_inverted(router, command, reference, exact):
+ "logically inverts result of compare_json_test"
+
+ # None vs something else
+ result = compare_json_test(router, command, reference, exact)
+ if result is None:
+ return "Some"
+ return None
+
+
+def cmp_json_output_doesnt(rname, command, reference):
+ "Compare router JSON output, shouldn't include reference"
+
+ logger.info('Comparing (anti) router "%s" "%s" output', rname, command)
+
+ tgen = get_topogen()
+ filename = "{}/{}/{}".format(CWD, rname, reference)
+ expected = json.loads(open(filename).read())
+
+ # Run test function until we get an result. Wait at most 60 seconds.
+ test_func = partial(
+ compare_json_test_inverted, tgen.gears[rname], command, expected, exact=False
+ )
+ _, diff = topotest.run_and_expect(test_func, None, count=120, wait=0.5)
+ assertmsg = '"{}" JSON output mismatches the expected result'.format(rname)
+ assert diff is None, assertmsg
+
+
+def dump_json(v):
+ if isinstance(v, (dict, list)):
+ return "\t" + "\t".join(
+ json.dumps(v, indent=4, separators=(",", ": ")).splitlines(True)
+ )
+ else:
+ return "'{}'".format(v)
+
+
+def add_candidate_path(rname, endpoint, pref, name, segment_list="default", color=1):
get_topogen().net[rname].cmd(
""" \
vtysh -c "conf t" \
-c "segment-routing" \
-c "traffic-eng" \
- -c "policy color 1 endpoint """
+ -c "policy color """
+ + str(color)
+ + " endpoint "
+ endpoint
+ """" \
-c "candidate-path preference """
)
-def delete_candidate_path(rname, endpoint, pref):
+def delete_candidate_path(rname, endpoint, pref, color=1):
get_topogen().net[rname].cmd(
""" \
vtysh -c "conf t" \
-c "segment-routing" \
-c "traffic-eng" \
- -c "policy color 1 endpoint """
+ -c "policy color """
+ + str(color)
+ + " endpoint "
+ endpoint
+ """" \
-c "no candidate-path preference """
)
+def set_route_map_color(rname, color):
+ get_topogen().net[rname].cmd(
+ ''' \
+ vtysh -c "conf t" \
+ -c "route-map SET_SR_POLICY permit 10" \
+ -c "set sr-te color "'''
+ + str(color)
+ )
+
+
+def router_bgp_shutdown_neighbor(rname, neighbor):
+ get_topogen().net[rname].cmd(
+ """ \
+ vtysh -c "conf t" \
+ -c "router bgp 1" \
+ -c " neighbor """
+ + neighbor
+ + ' shutdown"'
+ )
+
+
+def router_bgp_no_shutdown_neighbor(rname, neighbor):
+ get_topogen().net[rname].cmd(
+ """ \
+ vtysh -c "conf t" \
+ -c "router bgp 1" \
+ -c " no neighbor """
+ + neighbor
+ + ' shutdown"'
+ )
+
+
+def show_running_cfg(rname):
+ output = (
+ get_topogen()
+ .net[rname]
+ .cmd(
+ """ \
+ vtysh -c "show run" """
+ )
+ )
+ logger.info(output)
+
+
#
# Step 1
#
delete_candidate_path("rt1", "6.6.6.6", 100)
+def save_rt(routername, filename):
+ save_filename = routername + "/" + filename
+ tgen = get_topogen()
+ router = tgen.gears[routername]
+
+ config_output = router.vtysh_cmd("sh run")
+
+ route_output_json = json.loads(router.vtysh_cmd("show ip route bgp json"))
+ route_output = dump_json(route_output_json)
+
+ f = open(save_filename, "w")
+ f.write(config_output)
+ f.write(route_output)
+ f.close()
+
+
#
# Step 5
#
delete_candidate_path("rt1", "6.6.6.6", 100)
+def test_srte_route_map_sr_policy_vs_route_order_step5():
+ setup_testcase(
+ "Test (step 5): Config policy first, add route after and check route validity"
+ )
+
+ #
+ # BGP route and route-map are already configured.
+ # route-map sets color 1 on BGP routes
+
+ # Developer: to force pause here
+ # tgen = get_topogen()
+ # tgen.mininet_cli()
+
+ #
+ # Configure policy/path
+ #
+ add_candidate_path("rt1", "6.6.6.6", 100, "default", "default", 1)
+
+ #
+ # Route should be valid
+ #
+ logger.info(
+ "BGP route and route-map are already configured, SR candidate path added after. Route should be valid"
+ )
+ cmp_json_output(
+ "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_active_srte.ref"
+ )
+
+ #
+ # shutdown/no-shutdown on BGP neighbor to delete/re-add BGP route
+ #
+ router_bgp_shutdown_neighbor("rt1", "6.6.6.6")
+ router_bgp_no_shutdown_neighbor("rt1", "6.6.6.6")
+
+ #
+ # Route should be valid (but isn't)
+ #
+ logger.info(
+ "After shutdown + no-shutdown neighbor. Route should be valid, but isn't"
+ )
+ cmp_json_output(
+ "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_active_srte.ref"
+ )
+
+ #
+ # delete and re-add policy/path
+ #
+ delete_candidate_path("rt1", "6.6.6.6", 100, 1)
+ add_candidate_path("rt1", "6.6.6.6", 100, "default", "default", 1)
+
+ #
+ # Route should be valid
+ #
+ logger.info("After re-add candidate path. Route should be valid")
+ cmp_json_output(
+ "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_active_srte.ref"
+ )
+
+ # Developer: to force pause here
+ # tgen = get_topogen()
+ # tgen.mininet_cli()
+
+ # clean up
+ delete_candidate_path("rt1", "6.6.6.6", 100, 2)
+
+
+def test_srte_route_map_sr_policy_vs_routemap_order_step5():
+ setup_testcase(
+ "Test (step 5): Config policy first, set route-map after and check route validity"
+ )
+
+ #
+ # BGP route and route-map are already configured.
+ # route-map sets color 1 on BGP routes
+ #
+
+ #
+ # Configure policy/path
+ #
+ add_candidate_path("rt1", "6.6.6.6", 100, "default", "default", 1)
+
+ #
+ # Route should be valid
+ #
+ logger.info("After add candidate path. Route should be valid")
+ cmp_json_output(
+ "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_active_srte.ref"
+ )
+
+ # Developer: to force pause here
+ # tgen = get_topogen()
+ # tgen.mininet_cli()
+
+ #
+ # change route-map color to someting else and back again
+ #
+ set_route_map_color("rt1", 2)
+ logger.info("route-map color was set to 2")
+ # show_running_cfg("rt1")
+ # 220625 nexthop no longer becomes empty. Colored routes without
+ # matching SR policies now fall back to their non-colored equivalent
+ # nexthops. So the route to 9.9.9.9/32 will now be valid, but with
+ # different nexthop values.
+ logger.info("now route table will lose policy-mapped route")
+ cmp_json_output_doesnt(
+ "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_active_srte.ref"
+ )
+ set_route_map_color("rt1", 1)
+ logger.info("route-map color was set to 1")
+ # show_running_cfg("rt1")
+
+ #
+ # Route should be valid (but isn't)
+ #
+ logger.info("After change route-map color. Route should be valid, but isn't")
+ cmp_json_output(
+ "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_active_srte.ref"
+ )
+
+ #
+ # delete and re-add policy/path
+ #
+ delete_candidate_path("rt1", "6.6.6.6", 100, 1)
+ add_candidate_path("rt1", "6.6.6.6", 100, "default", "default", 1)
+
+ #
+ # Route should be valid
+ #
+ logger.info("After delete/re-add candidate path. Route should be valid")
+ cmp_json_output(
+ "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_active_srte.ref"
+ )
+
+ # Developer: force pause
+ # tgen = get_topogen()
+ # tgen.mininet_cli()
+
+ # clean up
+ delete_candidate_path("rt1", "6.6.6.6", 100, 2)
+
+
def test_srte_route_map_with_sr_policy_reinstall_prefix_sid_check_nextop_step5():
setup_testcase(
"Test (step 5): remove and re-install prefix SID on fist path element and check SR Policy activity"