4 # test_isis_sr_topo1.py
5 # Part of NetDEF Topology Tests
7 # Copyright (c) 2019 by
8 # Network Device Education Foundation, Inc. ("NetDEF")
10 # Permission to use, copy, modify, and/or distribute this software
11 # for any purpose with or without fee is hereby granted, provided
12 # that the above copyright notice and this permission notice appear
15 # THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES
16 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
17 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR
18 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
19 # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
20 # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
21 # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
26 test_isis_sr_te_topo1.py:
38 +---------+ | +---------+
40 | RT2 |eth-sw1 | eth-sw1| RT3 |
41 | 2.2.2.2 +----------+----------+ 3.3.3.3 |
43 +---------+ +---------+
44 eth-rt4-1| |eth-rt4-2 eth-rt5-1| |eth-rt5-2
46 10.0.2.0/24| |10.0.3.0/24 10.0.4.0/24| |10.0.5.0/24
48 eth-rt2-1| |eth-rt2-2 eth-rt3-1| |eth-rt3-2
49 +---------+ +---------+
51 | RT4 | 10.0.6.0/24 | RT5 |
52 | 4.4.4.4 +---------------------+ 5.5.5.5 |
54 +---------+ +---------+
57 10.0.7.0/24| |10.0.8.0/24
61 +----------+ 6.6.6.6 +-----------+
82 from functools
import partial
84 # Save the Current Working Directory to find configuration files.
85 CWD
= os
.path
.dirname(os
.path
.realpath(__file__
))
86 sys
.path
.append(os
.path
.join(CWD
, "../"))
88 # pylint: disable=C0413
89 # Import topogen and topotest helpers
90 from lib
import topotest
91 from lib
.topogen
import Topogen
, TopoRouter
, get_topogen
92 from lib
.topolog
import logger
94 # Required to instantiate the topology builder class.
96 pytestmark
= [pytest
.mark
.bgpd
, pytest
.mark
.isisd
, pytest
.mark
.pathd
]
105 for router
in ["rt1", "rt2", "rt3", "rt4", "rt5", "rt6", "dst"]:
106 tgen
.add_router(router
)
111 switch
= tgen
.add_switch("s1")
112 switch
.add_link(tgen
.gears
["rt1"], nodeif
="eth-sw1")
113 switch
.add_link(tgen
.gears
["rt2"], nodeif
="eth-sw1")
114 switch
.add_link(tgen
.gears
["rt3"], nodeif
="eth-sw1")
116 switch
= tgen
.add_switch("s2")
117 switch
.add_link(tgen
.gears
["rt2"], nodeif
="eth-rt4-1")
118 switch
.add_link(tgen
.gears
["rt4"], nodeif
="eth-rt2-1")
120 switch
= tgen
.add_switch("s3")
121 switch
.add_link(tgen
.gears
["rt2"], nodeif
="eth-rt4-2")
122 switch
.add_link(tgen
.gears
["rt4"], nodeif
="eth-rt2-2")
124 switch
= tgen
.add_switch("s4")
125 switch
.add_link(tgen
.gears
["rt3"], nodeif
="eth-rt5-1")
126 switch
.add_link(tgen
.gears
["rt5"], nodeif
="eth-rt3-1")
128 switch
= tgen
.add_switch("s5")
129 switch
.add_link(tgen
.gears
["rt3"], nodeif
="eth-rt5-2")
130 switch
.add_link(tgen
.gears
["rt5"], nodeif
="eth-rt3-2")
132 switch
= tgen
.add_switch("s6")
133 switch
.add_link(tgen
.gears
["rt4"], nodeif
="eth-rt5")
134 switch
.add_link(tgen
.gears
["rt5"], nodeif
="eth-rt4")
136 switch
= tgen
.add_switch("s7")
137 switch
.add_link(tgen
.gears
["rt4"], nodeif
="eth-rt6")
138 switch
.add_link(tgen
.gears
["rt6"], nodeif
="eth-rt4")
140 switch
= tgen
.add_switch("s8")
141 switch
.add_link(tgen
.gears
["rt5"], nodeif
="eth-rt6")
142 switch
.add_link(tgen
.gears
["rt6"], nodeif
="eth-rt5")
144 switch
= tgen
.add_switch("s9")
145 switch
.add_link(tgen
.gears
["rt6"], nodeif
="eth-dst")
146 switch
.add_link(tgen
.gears
["dst"], nodeif
="eth-rt6")
149 def setup_module(mod
):
150 "Sets up the pytest environment"
152 tgen
= Topogen(build_topo
, mod
.__name
__)
154 frrdir
= tgen
.config
.get(tgen
.CONFIG_SECTION
, "frrdir")
155 if not os
.path
.isfile(os
.path
.join(frrdir
, "pathd")):
156 pytest
.skip("pathd daemon wasn't built")
158 tgen
.start_topology()
160 router_list
= tgen
.routers()
162 # For all registered routers, load the zebra configuration file
163 for rname
, router
in router_list
.items():
165 TopoRouter
.RD_ZEBRA
, os
.path
.join(CWD
, "{}/zebra.conf".format(rname
))
168 TopoRouter
.RD_ISIS
, os
.path
.join(CWD
, "{}/isisd.conf".format(rname
))
171 TopoRouter
.RD_PATH
, os
.path
.join(CWD
, "{}/pathd.conf".format(rname
))
174 TopoRouter
.RD_BGP
, os
.path
.join(CWD
, "{}/bgpd.conf".format(rname
))
180 def teardown_module(mod
):
181 "Teardown the pytest environment"
184 # This function tears down the whole topology.
188 def setup_testcase(msg
):
192 # Skip if previous fatal error condition is raised
193 if tgen
.routers_have_failure():
194 pytest
.skip(tgen
.errors
)
199 def print_cmd_result(rname
, command
):
200 print(get_topogen().gears
[rname
].vtysh_cmd(command
, isjson
=False))
203 def compare_json_test(router
, command
, reference
, exact
):
204 output
= router
.vtysh_cmd(command
, isjson
=True)
205 result
= topotest
.json_cmp(output
, reference
)
207 # Note: topotest.json_cmp() just checks on inclusion of keys.
208 # For exact matching also compare the other way around.
209 if not result
and exact
:
210 return topotest
.json_cmp(reference
, output
)
215 def cmp_json_output(rname
, command
, reference
, exact
=False):
216 "Compare router JSON output"
218 logger
.info('Comparing router "%s" "%s" output', rname
, command
)
221 filename
= "{}/{}/{}".format(CWD
, rname
, reference
)
222 expected
= json
.loads(open(filename
).read())
224 # Run test function until we get an result. Wait at most 60 seconds.
225 test_func
= partial(compare_json_test
, tgen
.gears
[rname
], command
, expected
, exact
)
226 _
, diff
= topotest
.run_and_expect(test_func
, None, count
=120, wait
=0.5)
227 assertmsg
= '"{}" JSON output mismatches the expected result'.format(rname
)
228 assert diff
is None, assertmsg
231 def cmp_json_output_exact(rname
, command
, reference
):
232 return cmp_json_output(rname
, command
, reference
, True)
235 def compare_json_test_inverted(router
, command
, reference
, exact
):
236 "logically inverts result of compare_json_test"
238 # None vs something else
239 result
= compare_json_test(router
, command
, reference
, exact
)
245 def cmp_json_output_doesnt(rname
, command
, reference
):
246 "Compare router JSON output, shouldn't include reference"
248 logger
.info('Comparing (anti) router "%s" "%s" output', rname
, command
)
251 filename
= "{}/{}/{}".format(CWD
, rname
, reference
)
252 expected
= json
.loads(open(filename
).read())
254 # Run test function until we get an result. Wait at most 60 seconds.
256 compare_json_test_inverted
, tgen
.gears
[rname
], command
, expected
, exact
=False
258 _
, diff
= topotest
.run_and_expect(test_func
, None, count
=120, wait
=0.5)
259 assertmsg
= '"{}" JSON output mismatches the expected result'.format(rname
)
260 assert diff
is None, assertmsg
264 if isinstance(v
, (dict, list)):
265 return "\t" + "\t".join(
266 json
.dumps(v
, indent
=4, separators
=(",", ": ")).splitlines(True)
269 return "'{}'".format(v
)
272 def add_candidate_path(rname
, endpoint
, pref
, name
, segment_list
="default", color
=1):
273 get_topogen().net
[rname
].cmd(
276 -c "segment-routing" \
283 -c "candidate-path preference """
287 + """ explicit segment-list """
293 def delete_candidate_path(rname
, endpoint
, pref
, color
=1):
294 get_topogen().net
[rname
].cmd(
297 -c "segment-routing" \
304 -c "no candidate-path preference """
310 def add_segment(rname
, name
, index
, label
):
311 get_topogen().net
[rname
].cmd(
314 -c "segment-routing" \
327 def delete_segment(rname
, name
, index
):
328 get_topogen().net
[rname
].cmd(
331 -c "segment-routing" \
342 def create_sr_policy(rname
, endpoint
, bsid
):
343 get_topogen().net
[rname
].cmd(
346 -c "segment-routing" \
348 -c "policy color 1 endpoint """
358 def delete_sr_policy(rname
, endpoint
):
359 get_topogen().net
[rname
].cmd(
362 -c "segment-routing" \
364 -c "no policy color 1 endpoint """
370 def create_prefix_sid(rname
, prefix
, sid
):
371 get_topogen().net
[rname
].cmd(
375 -c "segment-routing prefix """
383 def delete_prefix_sid(rname
, prefix
):
384 get_topogen().net
[rname
].cmd(
388 -c "no segment-routing prefix "'''
393 def set_route_map_color(rname
, color
):
394 get_topogen().net
[rname
].cmd(
397 -c "route-map SET_SR_POLICY permit 10" \
398 -c "set sr-te color "'''
403 def router_bgp_shutdown_neighbor(rname
, neighbor
):
404 get_topogen().net
[rname
].cmd(
414 def router_bgp_no_shutdown_neighbor(rname
, neighbor
):
415 get_topogen().net
[rname
].cmd(
425 def show_running_cfg(rname
):
431 vtysh -c "show run" """
440 # Checking the MPLS table using a single SR Policy and a single Candidate Path
442 def test_srte_init_step1():
443 setup_testcase("Test (step 1): wait for IS-IS convergence / label distribution")
445 for rname
in ["rt1", "rt6"]:
447 rname
, "show mpls table json", "step1/show_mpls_table_without_candidate.ref"
451 def test_srte_add_candidate_check_mpls_table_step1():
452 setup_testcase("Test (step 1): check MPLS table regarding the added Candidate Path")
454 for rname
, endpoint
in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
455 add_candidate_path(rname
, endpoint
, 100, "default")
457 rname
, "show mpls table json", "step1/show_mpls_table_with_candidate.ref"
459 delete_candidate_path(rname
, endpoint
, 100)
462 def test_srte_reinstall_sr_policy_check_mpls_table_step1():
464 "Test (step 1): check MPLS table after the SR Policy was removed and reinstalled"
467 for rname
, endpoint
, bsid
in [("rt1", "6.6.6.6", 1111), ("rt6", "1.1.1.1", 6666)]:
468 add_candidate_path(rname
, endpoint
, 100, "default")
469 delete_sr_policy(rname
, endpoint
)
471 rname
, "show mpls table json", "step1/show_mpls_table_without_candidate.ref"
473 create_sr_policy(rname
, endpoint
, bsid
)
474 add_candidate_path(rname
, endpoint
, 100, "default")
476 rname
, "show mpls table json", "step1/show_mpls_table_with_candidate.ref"
478 delete_candidate_path(rname
, endpoint
, 100)
484 # Checking pathd operational data using a single SR Policy and a single Candidate Path
486 def test_srte_bare_policy_step2():
487 setup_testcase("Test (step 2): bare SR Policy should not be operational")
489 for rname
in ["rt1", "rt6"]:
490 cmp_json_output_exact(
492 "show yang operational-data /frr-pathd:pathd pathd",
493 "step2/show_operational_data.ref",
497 def test_srte_add_candidate_check_operational_data_step2():
499 "Test (step 2): add single Candidate Path, SR Policy should be operational"
502 for rname
, endpoint
in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
503 add_candidate_path(rname
, endpoint
, 100, "default")
506 "show yang operational-data /frr-pathd:pathd pathd",
507 "step2/show_operational_data_with_candidate.ref",
511 def test_srte_config_remove_candidate_check_operational_data_step2():
513 "Test (step 2): remove single Candidate Path, SR Policy should not be operational anymore"
516 for rname
, endpoint
in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
517 delete_candidate_path(rname
, endpoint
, 100)
518 cmp_json_output_exact(
520 "show yang operational-data /frr-pathd:pathd pathd",
521 "step2/show_operational_data.ref",
528 # Testing the Candidate Path selection
530 def test_srte_add_two_candidates_step3():
531 setup_testcase("Test (step 3): second Candidate Path has higher Priority")
533 for rname
, endpoint
in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
534 for pref
, cand_name
in [("100", "first"), ("200", "second")]:
535 add_candidate_path(rname
, endpoint
, pref
, cand_name
)
538 "show yang operational-data /frr-pathd:pathd pathd",
539 "step3/show_operational_data_with_two_candidates.ref",
543 for rname
, endpoint
in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
544 for pref
in ["100", "200"]:
545 delete_candidate_path(rname
, endpoint
, pref
)
548 def test_srte_add_two_candidates_with_reverse_priority_step3():
549 setup_testcase("Test (step 3): second Candidate Path has lower Priority")
551 # Use reversed priorities here
552 for rname
, endpoint
in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
553 for pref
, cand_name
in [("200", "first"), ("100", "second")]:
554 add_candidate_path(rname
, endpoint
, pref
, cand_name
)
557 "show yang operational-data /frr-pathd:pathd pathd",
558 "step3/show_operational_data_with_two_candidates.ref",
562 for rname
, endpoint
in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
563 for pref
in ["100", "200"]:
564 delete_candidate_path(rname
, endpoint
, pref
)
567 def test_srte_remove_best_candidate_step3():
568 setup_testcase("Test (step 3): delete the Candidate Path with higher priority")
570 for rname
, endpoint
in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
571 for pref
, cand_name
in [("100", "first"), ("200", "second")]:
572 add_candidate_path(rname
, endpoint
, pref
, cand_name
)
574 # Delete candidate with higher priority
575 for rname
, endpoint
in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
576 delete_candidate_path(rname
, endpoint
, 200)
578 # Candidate with lower priority should get active now
579 for rname
, endpoint
in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
582 "show yang operational-data /frr-pathd:pathd pathd",
583 "step3/show_operational_data_with_single_candidate.ref",
586 delete_candidate_path(rname
, endpoint
, 100)
592 # Checking MPLS table with a single SR Policy and a Candidate Path with different Segment Lists and other modifications
594 def test_srte_change_segment_list_check_mpls_table_step4():
595 setup_testcase("Test (step 4): check MPLS table for changed Segment List")
597 for rname
, endpoint
in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
598 add_candidate_path(rname
, endpoint
, 100, "default")
599 # now change the segment list name
600 add_candidate_path(rname
, endpoint
, 100, "default", "test")
601 cmp_json_output(rname
, "show mpls table json", "step4/show_mpls_table.ref")
602 delete_candidate_path(rname
, endpoint
, 100)
605 def test_srte_segment_list_add_segment_check_mpls_table_step4():
607 "Test (step 4): check MPLS table for added (then changed and finally deleted) segment"
610 add_candidate_path("rt1", "6.6.6.6", 100, "default", "test")
612 # first add a new segment
613 add_segment("rt1", "test", 25, 16050)
615 "rt1", "show mpls table json", "step4/show_mpls_table_add_segment.ref"
618 # ... then change it ...
619 add_segment("rt1", "test", 25, 16030)
621 "rt1", "show mpls table json", "step4/show_mpls_table_change_segment.ref"
624 # ... and finally delete it
625 delete_segment("rt1", "test", 25)
626 cmp_json_output("rt1", "show mpls table json", "step4/show_mpls_table.ref")
627 delete_candidate_path("rt1", "6.6.6.6", 100)
630 def save_rt(routername
, filename
):
631 save_filename
= routername
+ "/" + filename
633 router
= tgen
.gears
[routername
]
635 config_output
= router
.vtysh_cmd("sh run")
637 route_output_json
= json
.loads(router
.vtysh_cmd("show ip route bgp json"))
638 route_output
= dump_json(route_output_json
)
640 f
= open(save_filename
, "w")
641 f
.write(config_output
)
642 f
.write(route_output
)
649 # Checking the nexthop using a single SR Policy and a Candidate Path with configured route-map
651 def test_srte_route_map_with_sr_policy_check_nextop_step5():
653 "Test (step 5): recursive nexthop learned through BGP neighbour should be aligned with SR Policy from route-map"
656 # (re-)build the SR Policy two times to ensure that reinstalling still works
659 "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_inactive_srte.ref"
662 delete_sr_policy("rt1", "6.6.6.6")
664 "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_inactive_srte.ref"
667 create_sr_policy("rt1", "6.6.6.6", 1111)
669 "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_inactive_srte.ref"
672 add_candidate_path("rt1", "6.6.6.6", 100, "default")
674 "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_active_srte.ref"
677 delete_candidate_path("rt1", "6.6.6.6", 100)
680 def test_srte_route_map_sr_policy_vs_route_order_step5():
682 "Test (step 5): Config policy first, add route after and check route validity"
686 # BGP route and route-map are already configured.
687 # route-map sets color 1 on BGP routes
689 # Developer: to force pause here
690 # tgen = get_topogen()
694 # Configure policy/path
696 add_candidate_path("rt1", "6.6.6.6", 100, "default", "default", 1)
699 # Route should be valid
702 "BGP route and route-map are already configured, SR candidate path added after. Route should be valid"
705 "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_active_srte.ref"
709 # shutdown/no-shutdown on BGP neighbor to delete/re-add BGP route
711 router_bgp_shutdown_neighbor("rt1", "6.6.6.6")
712 router_bgp_no_shutdown_neighbor("rt1", "6.6.6.6")
715 # Route should be valid (but isn't)
718 "After shutdown + no-shutdown neighbor. Route should be valid, but isn't"
721 "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_active_srte.ref"
725 # delete and re-add policy/path
727 delete_candidate_path("rt1", "6.6.6.6", 100, 1)
728 add_candidate_path("rt1", "6.6.6.6", 100, "default", "default", 1)
731 # Route should be valid
733 logger
.info("After re-add candidate path. Route should be valid")
735 "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_active_srte.ref"
738 # Developer: to force pause here
739 # tgen = get_topogen()
743 delete_candidate_path("rt1", "6.6.6.6", 100, 2)
746 def test_srte_route_map_sr_policy_vs_routemap_order_step5():
748 "Test (step 5): Config policy first, set route-map after and check route validity"
752 # BGP route and route-map are already configured.
753 # route-map sets color 1 on BGP routes
757 # Configure policy/path
759 add_candidate_path("rt1", "6.6.6.6", 100, "default", "default", 1)
762 # Route should be valid
764 logger
.info("After add candidate path. Route should be valid")
766 "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_active_srte.ref"
769 # Developer: to force pause here
770 # tgen = get_topogen()
774 # change route-map color to someting else and back again
776 set_route_map_color("rt1", 2)
777 logger
.info("route-map color was set to 2")
778 # show_running_cfg("rt1")
779 # 220625 nexthop no longer becomes empty. Colored routes without
780 # matching SR policies now fall back to their non-colored equivalent
781 # nexthops. So the route to 9.9.9.9/32 will now be valid, but with
782 # different nexthop values.
783 logger
.info("now route table will lose policy-mapped route")
784 cmp_json_output_doesnt(
785 "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_active_srte.ref"
787 set_route_map_color("rt1", 1)
788 logger
.info("route-map color was set to 1")
789 # show_running_cfg("rt1")
792 # Route should be valid (but isn't)
794 logger
.info("After change route-map color. Route should be valid, but isn't")
796 "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_active_srte.ref"
800 # delete and re-add policy/path
802 delete_candidate_path("rt1", "6.6.6.6", 100, 1)
803 add_candidate_path("rt1", "6.6.6.6", 100, "default", "default", 1)
806 # Route should be valid
808 logger
.info("After delete/re-add candidate path. Route should be valid")
810 "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_active_srte.ref"
813 # Developer: force pause
814 # tgen = get_topogen()
818 delete_candidate_path("rt1", "6.6.6.6", 100, 2)
821 def test_srte_route_map_with_sr_policy_reinstall_prefix_sid_check_nextop_step5():
823 "Test (step 5): remove and re-install prefix SID on fist path element and check SR Policy activity"
826 # first add a candidate path so the SR Policy is active
827 add_candidate_path("rt1", "6.6.6.6", 100, "default")
830 "show yang operational-data /frr-pathd:pathd pathd",
831 "step5/show_operational_data_active.ref",
834 # delete prefix SID from first element of the configured path and check
835 # if the SR Policy is inactive since the label can't be resolved anymore
836 delete_prefix_sid("rt5", "5.5.5.5/32")
839 "show yang operational-data /frr-pathd:pathd pathd",
840 "step5/show_operational_data_inactive.ref",
843 "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_inactive_srte.ref"
846 # re-create the prefix SID and check if the SR Policy is active
847 create_prefix_sid("rt5", "5.5.5.5/32", 50)
850 "show yang operational-data /frr-pathd:pathd pathd",
851 "step5/show_operational_data_active.ref",
854 "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_active_srte.ref"
858 # Memory leak test template
859 def test_memory_leak():
860 "Run the memory leak test and report results."
862 if not tgen
.is_memleak_enabled():
863 pytest
.skip("Memory leak test/report is disabled")
865 tgen
.report_memory_leaks()
868 if __name__
== "__main__":
869 args
= ["-s"] + sys
.argv
[1:]
870 sys
.exit(pytest
.main(args
))