diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:53:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:53:30 +0000 |
commit | 2c7cac91ed6e7db0f6937923d2b57f97dbdbc337 (patch) | |
tree | c05dc0f8e6aa3accc84e3e5cffc933ed94941383 /tests/topotests/ospf_sr_te_topo1/test_ospf_sr_te_topo1.py | |
parent | Initial commit. (diff) | |
download | frr-upstream.tar.xz frr-upstream.zip |
Adding upstream version 8.4.4.upstream/8.4.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tests/topotests/ospf_sr_te_topo1/test_ospf_sr_te_topo1.py')
-rwxr-xr-x | tests/topotests/ospf_sr_te_topo1/test_ospf_sr_te_topo1.py | 666 |
1 files changed, 666 insertions, 0 deletions
diff --git a/tests/topotests/ospf_sr_te_topo1/test_ospf_sr_te_topo1.py b/tests/topotests/ospf_sr_te_topo1/test_ospf_sr_te_topo1.py new file mode 100755 index 0000000..6e99267 --- /dev/null +++ b/tests/topotests/ospf_sr_te_topo1/test_ospf_sr_te_topo1.py @@ -0,0 +1,666 @@ +#!/usr/bin/env python + +# +# test_ospf_sr_te_topo1.py +# +# Copyright (c) 2021 by +# Volta Networks +# +# 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_ospf_sr_te_topo1.py: + + +---------+ + | | + | RT1 | + | 1.1.1.1 | + | | + +---------+ + |eth-sw1 + | + | + | + +---------+ | +---------+ + | | | | | + | RT2 |eth-sw1 | eth-sw1| RT3 | + | 2.2.2.2 +----------+ + 3.3.3.3 | + | | 10.0.1.0/24 | | + +---------+ +---------+ + eth-rt4-1| eth-rt5-1| |eth-rt5-2 + | | | + 10.0.2.0/24| 10.0.4.0/24| |10.0.5.0/24 + | | | + eth-rt2-1| eth-rt3-1| |eth-rt3-2 + +---------+ +---------+ + | | | | + | RT4 | 10.0.6.0/24 | RT5 | + | 4.4.4.4 +---------------------+ 5.5.5.5 | + | |eth-rt5 eth-rt4| | + +---------+ +---------+ + eth-rt6| |eth-rt6 + | | + 10.0.7.0/24| |10.0.8.0/24 + | +---------+ | + | | | | + | | RT6 | | + +----------+ 6.6.6.6 +-----------+ + eth-rt4| |eth-rt5 + +---------+ + |eth-dst (.1) + | + |10.0.11.0/24 + | + |eth-rt6 (.2) + +---------+ + | | + | DST | + | 9.9.9.2 | + | | + +---------+ + +""" + +import os +import sys +import pytest +import json +from time import sleep +from functools import partial + +# 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.bgpd, pytest.mark.ospfd, pytest.mark.pathd] + + +def build_topo(tgen): + "Build function" + + # + # 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") + + 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("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("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("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") + + +def setup_module(mod): + "Sets up the pytest environment" + + tgen = Topogen(build_topo, mod.__name__) + + frrdir = tgen.config.get(tgen.CONFIG_SECTION, "frrdir") + if not os.path.isfile(os.path.join(frrdir, "pathd")): + pytest.skip("pathd daemon wasn't built in:" + frrdir) + + tgen.start_topology() + + router_list = tgen.routers() + + # For all registered routers, load the zebra configuration file + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_OSPF, os.path.join(CWD, "{}/ospfd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_PATH, os.path.join(CWD, "{}/pathd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + "Teardown the pytest environment" + tgen = get_topogen() + + # This function tears down the whole topology. + tgen.stop_topology() + + +def setup_testcase(msg): + logger.info(msg) + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + return tgen + + +def print_cmd_result(rname, command): + print(get_topogen().gears[rname].vtysh_cmd(command, isjson=False)) + + +def compare_json_test(router, command, reference, exact): + output = router.vtysh_cmd(command, isjson=True) + result = topotest.json_cmp(output, reference) + + # Note: topotest.json_cmp() just checks on inclusion of keys. + # For exact matching also compare the other way around. + if not result and exact: + return topotest.json_cmp(reference, output) + else: + return result + + +def cmp_json_output(rname, command, reference, exact=False): + "Compare router JSON output" + + logger.info('Comparing 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, tgen.gears[rname], command, expected, exact) + _, diff = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assertmsg = '"{}" JSON output mismatches the expected result'.format(rname) + assert diff is None, assertmsg + + +def cmp_json_output_exact(rname, command, reference): + return cmp_json_output(rname, command, reference, True) + + +def add_candidate_path(rname, endpoint, pref, name, segment_list="default"): + get_topogen().net[rname].cmd( + """ \ + vtysh -c "conf t" \ + -c "segment-routing" \ + -c "traffic-eng" \ + -c "policy color 1 endpoint """ + + endpoint + + """" \ + -c "candidate-path preference """ + + str(pref) + + """ name """ + + name + + """ explicit segment-list """ + + segment_list + + '''"''' + ) + + +def delete_candidate_path(rname, endpoint, pref): + get_topogen().net[rname].cmd( + """ \ + vtysh -c "conf t" \ + -c "segment-routing" \ + -c "traffic-eng" \ + -c "policy color 1 endpoint """ + + endpoint + + """" \ + -c "no candidate-path preference """ + + str(pref) + + '''"''' + ) + + +def add_segment(rname, name, index, label): + get_topogen().net[rname].cmd( + """ \ + vtysh -c "conf t" \ + -c "segment-routing" \ + -c "traffic-eng" \ + -c "segment-list """ + + name + + """" \ + -c "index """ + + str(index) + + """ mpls label """ + + str(label) + + '''"''' + ) + + +def delete_segment(rname, name, index): + get_topogen().net[rname].cmd( + """ \ + vtysh -c "conf t" \ + -c "segment-routing" \ + -c "traffic-eng" \ + -c "segment-list """ + + name + + """" \ + -c "no index """ + + str(index) + + '''"''' + ) + + +def add_segment_adj(rname, name, index, src, dst): + get_topogen().net[rname].cmd( + """ \ + vtysh -c "conf t" \ + -c "segment-routing" \ + -c "traffic-eng" \ + -c "segment-list """ + + name + + """" \ + -c "index """ + + str(index) + + """ nai adjacency """ + + str(src) + + """ """ + + str(dst) + + '''"''' + ) + + +def create_sr_policy(rname, endpoint, bsid): + get_topogen().net[rname].cmd( + """ \ + vtysh -c "conf t" \ + -c "segment-routing" \ + -c "traffic-eng" \ + -c "policy color 1 endpoint """ + + endpoint + + """" \ + -c "name default" \ + -c "binding-sid """ + + str(bsid) + + '''"''' + ) + + +def delete_sr_policy(rname, endpoint): + get_topogen().net[rname].cmd( + """ \ + vtysh -c "conf t" \ + -c "segment-routing" \ + -c "traffic-eng" \ + -c "no policy color 1 endpoint """ + + endpoint + + '''"''' + ) + + +def create_prefix_sid(rname, prefix, sid): + get_topogen().net[rname].cmd( + """ \ + vtysh -c "conf t" \ + -c "router ospf " \ + -c "segment-routing prefix """ + + prefix + + " index " + + str(sid) + + '''"''' + ) + + +def delete_prefix_sid(rname, prefix): + get_topogen().net[rname].cmd( + ''' \ + vtysh -c "conf t" \ + -c "router ospf " \ + -c "no segment-routing prefix "''' + + prefix + ) + + +def check_bsid(rt, bsid, fn_name, positive): + """ + Search for a bsid in rt1 and rt6 + Positive means that check is true is bsid is found + Positive="False" means that check is true is bsid is NOT found + """ + + logger.info('Checking "%s" bsid "%s" for router "%s" ', positive, bsid, rt) + + count = 0 + candidate_key = bsid + candidate_output = "" + # First wait for convergence + tgen = get_topogen() + while count < 30: + matched = False + matched_key = False + sleep(1) + count += 1 + router = tgen.gears[rt] + candidate_output = router.vtysh_cmd("show mpls table json") + candidate_output_json = json.loads(candidate_output) + for item in candidate_output_json.items(): + # logger.info('item "%s"', item) + if item[0] == candidate_key: + matched_key = True + if positive: + break + if positive: + if matched_key: + matched = True + assertmsg = "{} don't has entry {} but is was expected".format( + router.name, candidate_key + ) + else: + if not matched_key: + matched = True + assertmsg = "{} has entry {} but is wans't expected".format( + router.name, candidate_key + ) + if matched: + logger.info('Success "%s" in "%s"', router.name, fn_name) + return + assert matched, assertmsg + + +# +# Step 1 +# +# Checking the MPLS table using a single SR Policy and a single Candidate Path +# Segment list are base in adjacency that query TED +# +def test_srte_init_step1(): + setup_testcase("Test (step 1): wait OSPF convergence / label distribution") + + check_bsid("rt1", "1111", test_srte_init_step1.__name__, False) + check_bsid("rt6", "6666", test_srte_init_step1.__name__, False) + + +def test_srte_add_candidate_check_mpls_table_step1(): + setup_testcase("Test (step 1): check MPLS table regarding the added Candidate Path") + + for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]: + add_candidate_path(rname, endpoint, 100, "default") + check_bsid( + rname, + "1111" if rname == "rt1" else "6666", + test_srte_init_step1.__name__, + True, + ) + delete_candidate_path(rname, endpoint, 100) + + +def test_srte_reinstall_sr_policy_check_mpls_table_step1(): + setup_testcase( + "Test (step 1): check MPLS table after the SR Policy was removed and reinstalled" + ) + + for rname, endpoint, bsid in [("rt1", "6.6.6.6", 1111), ("rt6", "1.1.1.1", 6666)]: + add_candidate_path(rname, endpoint, 100, "default") + delete_sr_policy(rname, endpoint) + check_bsid(rname, bsid, test_srte_init_step1.__name__, False) + create_sr_policy(rname, endpoint, bsid) + add_candidate_path(rname, endpoint, 100, "default") + check_bsid( + rname, + "1111" if rname == "rt1" else "6666", + test_srte_init_step1.__name__, + True, + ) + delete_candidate_path(rname, endpoint, 100) + + +# +# Step 2 +# +# Checking pathd operational data using a single SR Policy and a single Candidate Path +# Segment list are base in adjacency that query TED +# +def test_srte_bare_policy_step2(): + setup_testcase("Test (step 2): bare SR Policy should not be operational") + + for rname in ["rt1", "rt6"]: + cmp_json_output_exact( + rname, + "show yang operational-data /frr-pathd:pathd pathd", + "step2/show_operational_data.ref", + ) + + +def test_srte_add_candidate_check_operational_data_step2(): + setup_testcase( + "Test (step 2): add single Candidate Path, SR Policy should be operational" + ) + + for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]: + add_candidate_path(rname, endpoint, 100, "default") + cmp_json_output( + rname, + "show yang operational-data /frr-pathd:pathd pathd", + "step2/show_operational_data_with_candidate.ref", + ) + + +def test_srte_config_remove_candidate_check_operational_data_step2(): + setup_testcase( + "Test (step 2): remove single Candidate Path, SR Policy should not be operational anymore" + ) + + for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]: + delete_candidate_path(rname, endpoint, 100) + cmp_json_output_exact( + rname, + "show yang operational-data /frr-pathd:pathd pathd", + "step2/show_operational_data.ref", + ) + + +# +# Step 3 +# +# Testing the Candidate Path selection +# Segment list are based in adjacencies resolved by query TED +# +def test_srte_add_two_candidates_step3(): + setup_testcase("Test (step 3): second Candidate Path has higher Priority") + + for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]: + for pref, cand_name in [("100", "first"), ("200", "second")]: + add_candidate_path(rname, endpoint, pref, cand_name) + cmp_json_output( + rname, + "show yang operational-data /frr-pathd:pathd pathd", + "step3/show_operational_data_with_two_candidates.ref", + ) + + # cleanup + for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]: + for pref in ["100", "200"]: + delete_candidate_path(rname, endpoint, pref) + + +def test_srte_add_two_candidates_with_reverse_priority_step3(): + setup_testcase("Test (step 3): second Candidate Path has lower Priority") + + # Use reversed priorities here + for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]: + for pref, cand_name in [("200", "first"), ("100", "second")]: + add_candidate_path(rname, endpoint, pref, cand_name) + cmp_json_output( + rname, + "show yang operational-data /frr-pathd:pathd pathd", + "step3/show_operational_data_with_two_candidates.ref", + ) + + # cleanup + for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]: + for pref in ["100", "200"]: + delete_candidate_path(rname, endpoint, pref) + + +def test_srte_remove_best_candidate_step3(): + setup_testcase("Test (step 3): delete the Candidate Path with higher priority") + + for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]: + for pref, cand_name in [("100", "first"), ("200", "second")]: + add_candidate_path(rname, endpoint, pref, cand_name) + + # Delete candidate with higher priority + for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]: + delete_candidate_path(rname, endpoint, 200) + + # Candidate with lower priority should get active now + for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]: + cmp_json_output( + rname, + "show yang operational-data /frr-pathd:pathd pathd", + "step3/show_operational_data_with_single_candidate.ref", + ) + # cleanup + delete_candidate_path(rname, endpoint, 100) + + +# +# Step 4 +# +# Checking MPLS table with a single SR Policy and a Candidate Path with different Segment Lists and other modifications +# Segment list are base in adjacency that query TED +# +def test_srte_change_segment_list_check_mpls_table_step4(): + setup_testcase("Test (step 4): check MPLS table for changed Segment List") + + for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]: + add_candidate_path(rname, endpoint, 100, "default") + # now change the segment list name + add_candidate_path(rname, endpoint, 100, "default", "test") + check_bsid( + rname, + "1111" if rname == "rt1" else "6666", + test_srte_init_step1.__name__, + True, + ) + delete_segment(rname, "test", 10) + delete_segment(rname, "test", 20) + delete_segment(rname, "test", 30) + delete_segment(rname, "test", 40) + if rname == "rt1": + add_segment_adj(rname, "test", 10, "10.0.1.1", "10.0.1.2") + add_segment_adj(rname, "test", 20, "10.0.2.2", "10.0.2.4") + add_segment_adj(rname, "test", 30, "10.0.6.4", "10.0.6.5") + add_segment_adj(rname, "test", 40, "10.0.8.5", "10.0.8.6") + else: + add_segment_adj(rname, "test", 10, "10.0.8.6", "10.0.8.5") + add_segment_adj(rname, "test", 20, "10.0.6.5", "10.0.6.4") + add_segment_adj(rname, "test", 30, "10.0.2.4", "10.0.2.2") + add_segment_adj(rname, "test", 40, "10.0.1.2", "10.0.1.1") + check_bsid( + rname, + "1111" if rname == "rt1" else "6666", + test_srte_init_step1.__name__, + True, + ) + delete_candidate_path(rname, endpoint, 100) + + +def test_srte_change_sl_priority_error_ted_check_mpls_table_step4(): + setup_testcase("Test (step 4): check MPLS table keeps low prio sl") + + for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]: + add_candidate_path(rname, endpoint, 100, "default") + # now change the segment list name + add_candidate_path(rname, endpoint, 200, "test", "test") + check_bsid( + rname, + "1111" if rname == "rt1" else "6666", + test_srte_init_step1.__name__, + True, + ) + delete_segment(rname, "test", 10) + delete_segment(rname, "test", 20) + delete_segment(rname, "test", 30) + delete_segment(rname, "test", 40) + # These won't resolv + if rname == "rt1": + add_segment_adj(rname, "test", 10, "10.0.1.99", "10.0.1.99") + add_segment_adj(rname, "test", 20, "10.0.2.99", "10.0.2.99") + add_segment_adj(rname, "test", 30, "10.0.6.99", "10.0.6.99") + add_segment_adj(rname, "test", 40, "10.0.8.99", "10.0.8.99") + else: + add_segment_adj(rname, "test", 10, "10.0.8.99", "10.0.8.99") + add_segment_adj(rname, "test", 20, "10.0.6.99", "10.0.6.99") + add_segment_adj(rname, "test", 30, "10.0.2.99", "10.0.2.99") + add_segment_adj(rname, "test", 40, "10.0.1.99", "10.0.1.99") + # So policy sticks with default sl even higher prio + check_bsid( + rname, + "1111" if rname == "rt1" else "6666", + test_srte_init_step1.__name__, + True, + ) + delete_candidate_path(rname, endpoint, 100) + + +# Memory leak test template +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)) |