2 # SPDX-License-Identifier: ISC
5 # test_isis_sr_topo1.py
6 # Part of NetDEF Topology Tests
8 # Copyright (c) 2019 by
9 # Network Device Education Foundation, Inc. ("NetDEF")
13 test_isis_sr_te_topo1.py:
25 +---------+ | +---------+
27 | RT2 |eth-sw1 | eth-sw1| RT3 |
28 | 2.2.2.2 +----------+----------+ 3.3.3.3 |
30 +---------+ +---------+
31 eth-rt4-1| |eth-rt4-2 eth-rt5-1| |eth-rt5-2
33 10.0.2.0/24| |10.0.3.0/24 10.0.4.0/24| |10.0.5.0/24
35 eth-rt2-1| |eth-rt2-2 eth-rt3-1| |eth-rt3-2
36 +---------+ +---------+
38 | RT4 | 10.0.6.0/24 | RT5 |
39 | 4.4.4.4 +---------------------+ 5.5.5.5 |
41 +---------+ +---------+
44 10.0.7.0/24| |10.0.8.0/24
48 +----------+ 6.6.6.6 +-----------+
69 from functools
import partial
71 # Save the Current Working Directory to find configuration files.
72 CWD
= os
.path
.dirname(os
.path
.realpath(__file__
))
73 sys
.path
.append(os
.path
.join(CWD
, "../"))
75 # pylint: disable=C0413
76 # Import topogen and topotest helpers
77 from lib
import topotest
78 from lib
.topogen
import Topogen
, TopoRouter
, get_topogen
79 from lib
.topolog
import logger
81 # Required to instantiate the topology builder class.
83 pytestmark
= [pytest
.mark
.bgpd
, pytest
.mark
.isisd
, pytest
.mark
.pathd
]
92 for router
in ["rt1", "rt2", "rt3", "rt4", "rt5", "rt6", "dst"]:
93 tgen
.add_router(router
)
98 switch
= tgen
.add_switch("s1")
99 switch
.add_link(tgen
.gears
["rt1"], nodeif
="eth-sw1")
100 switch
.add_link(tgen
.gears
["rt2"], nodeif
="eth-sw1")
101 switch
.add_link(tgen
.gears
["rt3"], nodeif
="eth-sw1")
103 switch
= tgen
.add_switch("s2")
104 switch
.add_link(tgen
.gears
["rt2"], nodeif
="eth-rt4-1")
105 switch
.add_link(tgen
.gears
["rt4"], nodeif
="eth-rt2-1")
107 switch
= tgen
.add_switch("s3")
108 switch
.add_link(tgen
.gears
["rt2"], nodeif
="eth-rt4-2")
109 switch
.add_link(tgen
.gears
["rt4"], nodeif
="eth-rt2-2")
111 switch
= tgen
.add_switch("s4")
112 switch
.add_link(tgen
.gears
["rt3"], nodeif
="eth-rt5-1")
113 switch
.add_link(tgen
.gears
["rt5"], nodeif
="eth-rt3-1")
115 switch
= tgen
.add_switch("s5")
116 switch
.add_link(tgen
.gears
["rt3"], nodeif
="eth-rt5-2")
117 switch
.add_link(tgen
.gears
["rt5"], nodeif
="eth-rt3-2")
119 switch
= tgen
.add_switch("s6")
120 switch
.add_link(tgen
.gears
["rt4"], nodeif
="eth-rt5")
121 switch
.add_link(tgen
.gears
["rt5"], nodeif
="eth-rt4")
123 switch
= tgen
.add_switch("s7")
124 switch
.add_link(tgen
.gears
["rt4"], nodeif
="eth-rt6")
125 switch
.add_link(tgen
.gears
["rt6"], nodeif
="eth-rt4")
127 switch
= tgen
.add_switch("s8")
128 switch
.add_link(tgen
.gears
["rt5"], nodeif
="eth-rt6")
129 switch
.add_link(tgen
.gears
["rt6"], nodeif
="eth-rt5")
131 switch
= tgen
.add_switch("s9")
132 switch
.add_link(tgen
.gears
["rt6"], nodeif
="eth-dst")
133 switch
.add_link(tgen
.gears
["dst"], nodeif
="eth-rt6")
136 def setup_module(mod
):
137 "Sets up the pytest environment"
139 tgen
= Topogen(build_topo
, mod
.__name
__)
141 frrdir
= tgen
.config
.get(tgen
.CONFIG_SECTION
, "frrdir")
142 if not os
.path
.isfile(os
.path
.join(frrdir
, "pathd")):
143 pytest
.skip("pathd daemon wasn't built")
145 tgen
.start_topology()
147 router_list
= tgen
.routers()
149 # For all registered routers, load the zebra configuration file
150 for rname
, router
in router_list
.items():
152 TopoRouter
.RD_ZEBRA
, os
.path
.join(CWD
, "{}/zebra.conf".format(rname
))
155 TopoRouter
.RD_ISIS
, os
.path
.join(CWD
, "{}/isisd.conf".format(rname
))
158 TopoRouter
.RD_PATH
, os
.path
.join(CWD
, "{}/pathd.conf".format(rname
))
161 TopoRouter
.RD_BGP
, os
.path
.join(CWD
, "{}/bgpd.conf".format(rname
))
167 def teardown_module(mod
):
168 "Teardown the pytest environment"
171 # This function tears down the whole topology.
175 def setup_testcase(msg
):
179 # Skip if previous fatal error condition is raised
180 if tgen
.routers_have_failure():
181 pytest
.skip(tgen
.errors
)
186 def print_cmd_result(rname
, command
):
187 print(get_topogen().gears
[rname
].vtysh_cmd(command
, isjson
=False))
190 def compare_json_test(router
, command
, reference
, exact
):
191 output
= router
.vtysh_cmd(command
, isjson
=True)
192 result
= topotest
.json_cmp(output
, reference
)
194 # Note: topotest.json_cmp() just checks on inclusion of keys.
195 # For exact matching also compare the other way around.
196 if not result
and exact
:
197 return topotest
.json_cmp(reference
, output
)
202 def cmp_json_output(rname
, command
, reference
, exact
=False):
203 "Compare router JSON output"
205 logger
.info('Comparing router "%s" "%s" output', rname
, command
)
208 filename
= "{}/{}/{}".format(CWD
, rname
, reference
)
209 expected
= json
.loads(open(filename
).read())
211 # Run test function until we get an result. Wait at most 60 seconds.
212 test_func
= partial(compare_json_test
, tgen
.gears
[rname
], command
, expected
, exact
)
213 _
, diff
= topotest
.run_and_expect(test_func
, None, count
=120, wait
=0.5)
214 assertmsg
= '"{}" JSON output mismatches the expected result'.format(rname
)
215 assert diff
is None, assertmsg
218 def cmp_json_output_exact(rname
, command
, reference
):
219 return cmp_json_output(rname
, command
, reference
, True)
222 def compare_json_test_inverted(router
, command
, reference
, exact
):
223 "logically inverts result of compare_json_test"
225 # None vs something else
226 result
= compare_json_test(router
, command
, reference
, exact
)
232 def cmp_json_output_doesnt(rname
, command
, reference
):
233 "Compare router JSON output, shouldn't include reference"
235 logger
.info('Comparing (anti) router "%s" "%s" output', rname
, command
)
238 filename
= "{}/{}/{}".format(CWD
, rname
, reference
)
239 expected
= json
.loads(open(filename
).read())
241 # Run test function until we get an result. Wait at most 60 seconds.
243 compare_json_test_inverted
, tgen
.gears
[rname
], command
, expected
, exact
=False
245 _
, diff
= topotest
.run_and_expect(test_func
, None, count
=120, wait
=0.5)
246 assertmsg
= '"{}" JSON output mismatches the expected result'.format(rname
)
247 assert diff
is None, assertmsg
251 if isinstance(v
, (dict, list)):
252 return "\t" + "\t".join(
253 json
.dumps(v
, indent
=4, separators
=(",", ": ")).splitlines(True)
256 return "'{}'".format(v
)
259 def add_candidate_path(rname
, endpoint
, pref
, name
, segment_list
="default", color
=1):
260 get_topogen().net
[rname
].cmd(
263 -c "segment-routing" \
270 -c "candidate-path preference """
274 + """ explicit segment-list """
280 def delete_candidate_path(rname
, endpoint
, pref
, color
=1):
281 get_topogen().net
[rname
].cmd(
284 -c "segment-routing" \
291 -c "no candidate-path preference """
297 def add_segment(rname
, name
, index
, label
):
298 get_topogen().net
[rname
].cmd(
301 -c "segment-routing" \
314 def delete_segment(rname
, name
, index
):
315 get_topogen().net
[rname
].cmd(
318 -c "segment-routing" \
329 def create_sr_policy(rname
, endpoint
, bsid
):
330 get_topogen().net
[rname
].cmd(
333 -c "segment-routing" \
335 -c "policy color 1 endpoint """
345 def delete_sr_policy(rname
, endpoint
):
346 get_topogen().net
[rname
].cmd(
349 -c "segment-routing" \
351 -c "no policy color 1 endpoint """
357 def create_prefix_sid(rname
, prefix
, sid
):
358 get_topogen().net
[rname
].cmd(
362 -c "segment-routing prefix """
370 def delete_prefix_sid(rname
, prefix
):
371 get_topogen().net
[rname
].cmd(
375 -c "no segment-routing prefix "'''
380 def set_route_map_color(rname
, color
):
381 get_topogen().net
[rname
].cmd(
384 -c "route-map SET_SR_POLICY permit 10" \
385 -c "set sr-te color "'''
390 def router_bgp_shutdown_neighbor(rname
, neighbor
):
391 get_topogen().net
[rname
].cmd(
401 def router_bgp_no_shutdown_neighbor(rname
, neighbor
):
402 get_topogen().net
[rname
].cmd(
412 def show_running_cfg(rname
):
418 vtysh -c "show run" """
427 # Checking the MPLS table using a single SR Policy and a single Candidate Path
429 def test_srte_init_step1():
430 setup_testcase("Test (step 1): wait for IS-IS convergence / label distribution")
432 for rname
in ["rt1", "rt6"]:
434 rname
, "show mpls table json", "step1/show_mpls_table_without_candidate.ref"
438 def test_srte_add_candidate_check_mpls_table_step1():
439 setup_testcase("Test (step 1): check MPLS table regarding the added Candidate Path")
441 for rname
, endpoint
in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
442 add_candidate_path(rname
, endpoint
, 100, "default")
444 rname
, "show mpls table json", "step1/show_mpls_table_with_candidate.ref"
446 delete_candidate_path(rname
, endpoint
, 100)
449 def test_srte_reinstall_sr_policy_check_mpls_table_step1():
451 "Test (step 1): check MPLS table after the SR Policy was removed and reinstalled"
454 for rname
, endpoint
, bsid
in [("rt1", "6.6.6.6", 1111), ("rt6", "1.1.1.1", 6666)]:
455 add_candidate_path(rname
, endpoint
, 100, "default")
456 delete_sr_policy(rname
, endpoint
)
458 rname
, "show mpls table json", "step1/show_mpls_table_without_candidate.ref"
460 create_sr_policy(rname
, endpoint
, bsid
)
461 add_candidate_path(rname
, endpoint
, 100, "default")
463 rname
, "show mpls table json", "step1/show_mpls_table_with_candidate.ref"
465 delete_candidate_path(rname
, endpoint
, 100)
471 # Checking pathd operational data using a single SR Policy and a single Candidate Path
473 def test_srte_bare_policy_step2():
474 setup_testcase("Test (step 2): bare SR Policy should not be operational")
476 for rname
in ["rt1", "rt6"]:
477 cmp_json_output_exact(
479 "show yang operational-data /frr-pathd:pathd pathd",
480 "step2/show_operational_data.ref",
484 def test_srte_add_candidate_check_operational_data_step2():
486 "Test (step 2): add single Candidate Path, SR Policy should be operational"
489 for rname
, endpoint
in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
490 add_candidate_path(rname
, endpoint
, 100, "default")
493 "show yang operational-data /frr-pathd:pathd pathd",
494 "step2/show_operational_data_with_candidate.ref",
498 def test_srte_config_remove_candidate_check_operational_data_step2():
500 "Test (step 2): remove single Candidate Path, SR Policy should not be operational anymore"
503 for rname
, endpoint
in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
504 delete_candidate_path(rname
, endpoint
, 100)
505 cmp_json_output_exact(
507 "show yang operational-data /frr-pathd:pathd pathd",
508 "step2/show_operational_data.ref",
515 # Testing the Candidate Path selection
517 def test_srte_add_two_candidates_step3():
518 setup_testcase("Test (step 3): second Candidate Path has higher Priority")
520 for rname
, endpoint
in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
521 for pref
, cand_name
in [("100", "first"), ("200", "second")]:
522 add_candidate_path(rname
, endpoint
, pref
, cand_name
)
525 "show yang operational-data /frr-pathd:pathd pathd",
526 "step3/show_operational_data_with_two_candidates.ref",
530 for rname
, endpoint
in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
531 for pref
in ["100", "200"]:
532 delete_candidate_path(rname
, endpoint
, pref
)
535 def test_srte_add_two_candidates_with_reverse_priority_step3():
536 setup_testcase("Test (step 3): second Candidate Path has lower Priority")
538 # Use reversed priorities here
539 for rname
, endpoint
in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
540 for pref
, cand_name
in [("200", "first"), ("100", "second")]:
541 add_candidate_path(rname
, endpoint
, pref
, cand_name
)
544 "show yang operational-data /frr-pathd:pathd pathd",
545 "step3/show_operational_data_with_two_candidates.ref",
549 for rname
, endpoint
in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
550 for pref
in ["100", "200"]:
551 delete_candidate_path(rname
, endpoint
, pref
)
554 def test_srte_remove_best_candidate_step3():
555 setup_testcase("Test (step 3): delete the Candidate Path with higher priority")
557 for rname
, endpoint
in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
558 for pref
, cand_name
in [("100", "first"), ("200", "second")]:
559 add_candidate_path(rname
, endpoint
, pref
, cand_name
)
561 # Delete candidate with higher priority
562 for rname
, endpoint
in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
563 delete_candidate_path(rname
, endpoint
, 200)
565 # Candidate with lower priority should get active now
566 for rname
, endpoint
in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
569 "show yang operational-data /frr-pathd:pathd pathd",
570 "step3/show_operational_data_with_single_candidate.ref",
573 delete_candidate_path(rname
, endpoint
, 100)
579 # Checking MPLS table with a single SR Policy and a Candidate Path with different Segment Lists and other modifications
581 def test_srte_change_segment_list_check_mpls_table_step4():
582 setup_testcase("Test (step 4): check MPLS table for changed Segment List")
584 for rname
, endpoint
in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
585 add_candidate_path(rname
, endpoint
, 100, "default")
586 # now change the segment list name
587 add_candidate_path(rname
, endpoint
, 100, "default", "test")
588 cmp_json_output(rname
, "show mpls table json", "step4/show_mpls_table.ref")
589 delete_candidate_path(rname
, endpoint
, 100)
592 def test_srte_segment_list_add_segment_check_mpls_table_step4():
594 "Test (step 4): check MPLS table for added (then changed and finally deleted) segment"
597 add_candidate_path("rt1", "6.6.6.6", 100, "default", "test")
599 # first add a new segment
600 add_segment("rt1", "test", 25, 16050)
602 "rt1", "show mpls table json", "step4/show_mpls_table_add_segment.ref"
605 # ... then change it ...
606 add_segment("rt1", "test", 25, 16030)
608 "rt1", "show mpls table json", "step4/show_mpls_table_change_segment.ref"
611 # ... and finally delete it
612 delete_segment("rt1", "test", 25)
613 cmp_json_output("rt1", "show mpls table json", "step4/show_mpls_table.ref")
614 delete_candidate_path("rt1", "6.6.6.6", 100)
617 def save_rt(routername
, filename
):
618 save_filename
= routername
+ "/" + filename
620 router
= tgen
.gears
[routername
]
622 config_output
= router
.vtysh_cmd("sh run")
624 route_output_json
= json
.loads(router
.vtysh_cmd("show ip route bgp json"))
625 route_output
= dump_json(route_output_json
)
627 f
= open(save_filename
, "w")
628 f
.write(config_output
)
629 f
.write(route_output
)
636 # Checking the nexthop using a single SR Policy and a Candidate Path with configured route-map
638 def test_srte_route_map_with_sr_policy_check_nextop_step5():
640 "Test (step 5): recursive nexthop learned through BGP neighbour should be aligned with SR Policy from route-map"
643 # (re-)build the SR Policy two times to ensure that reinstalling still works
646 "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_inactive_srte.ref"
649 delete_sr_policy("rt1", "6.6.6.6")
651 "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_inactive_srte.ref"
654 create_sr_policy("rt1", "6.6.6.6", 1111)
656 "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_inactive_srte.ref"
659 add_candidate_path("rt1", "6.6.6.6", 100, "default")
661 "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_active_srte.ref"
664 delete_candidate_path("rt1", "6.6.6.6", 100)
667 def test_srte_route_map_sr_policy_vs_route_order_step5():
669 "Test (step 5): Config policy first, add route after and check route validity"
673 # BGP route and route-map are already configured.
674 # route-map sets color 1 on BGP routes
676 # Developer: to force pause here
677 # tgen = get_topogen()
681 # Configure policy/path
683 add_candidate_path("rt1", "6.6.6.6", 100, "default", "default", 1)
686 # Route should be valid
689 "BGP route and route-map are already configured, SR candidate path added after. Route should be valid"
692 "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_active_srte.ref"
696 # shutdown/no-shutdown on BGP neighbor to delete/re-add BGP route
698 router_bgp_shutdown_neighbor("rt1", "6.6.6.6")
699 router_bgp_no_shutdown_neighbor("rt1", "6.6.6.6")
702 # Route should be valid (but isn't)
705 "After shutdown + no-shutdown neighbor. Route should be valid, but isn't"
708 "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_active_srte.ref"
712 # delete and re-add policy/path
714 delete_candidate_path("rt1", "6.6.6.6", 100, 1)
715 add_candidate_path("rt1", "6.6.6.6", 100, "default", "default", 1)
718 # Route should be valid
720 logger
.info("After re-add candidate path. Route should be valid")
722 "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_active_srte.ref"
725 # Developer: to force pause here
726 # tgen = get_topogen()
730 delete_candidate_path("rt1", "6.6.6.6", 100, 2)
733 def test_srte_route_map_sr_policy_vs_routemap_order_step5():
735 "Test (step 5): Config policy first, set route-map after and check route validity"
739 # BGP route and route-map are already configured.
740 # route-map sets color 1 on BGP routes
744 # Configure policy/path
746 add_candidate_path("rt1", "6.6.6.6", 100, "default", "default", 1)
749 # Route should be valid
751 logger
.info("After add candidate path. Route should be valid")
753 "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_active_srte.ref"
756 # Developer: to force pause here
757 # tgen = get_topogen()
761 # change route-map color to someting else and back again
763 set_route_map_color("rt1", 2)
764 logger
.info("route-map color was set to 2")
765 # show_running_cfg("rt1")
766 # 220625 nexthop no longer becomes empty. Colored routes without
767 # matching SR policies now fall back to their non-colored equivalent
768 # nexthops. So the route to 9.9.9.9/32 will now be valid, but with
769 # different nexthop values.
770 logger
.info("now route table will lose policy-mapped route")
771 cmp_json_output_doesnt(
772 "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_active_srte.ref"
774 set_route_map_color("rt1", 1)
775 logger
.info("route-map color was set to 1")
776 # show_running_cfg("rt1")
779 # Route should be valid (but isn't)
781 logger
.info("After change route-map color. Route should be valid, but isn't")
783 "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_active_srte.ref"
787 # delete and re-add policy/path
789 delete_candidate_path("rt1", "6.6.6.6", 100, 1)
790 add_candidate_path("rt1", "6.6.6.6", 100, "default", "default", 1)
793 # Route should be valid
795 logger
.info("After delete/re-add candidate path. Route should be valid")
797 "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_active_srte.ref"
800 # Developer: force pause
801 # tgen = get_topogen()
805 delete_candidate_path("rt1", "6.6.6.6", 100, 2)
808 def test_srte_route_map_with_sr_policy_reinstall_prefix_sid_check_nextop_step5():
810 "Test (step 5): remove and re-install prefix SID on fist path element and check SR Policy activity"
813 # first add a candidate path so the SR Policy is active
814 add_candidate_path("rt1", "6.6.6.6", 100, "default")
817 "show yang operational-data /frr-pathd:pathd pathd",
818 "step5/show_operational_data_active.ref",
821 # delete prefix SID from first element of the configured path and check
822 # if the SR Policy is inactive since the label can't be resolved anymore
823 delete_prefix_sid("rt5", "5.5.5.5/32")
826 "show yang operational-data /frr-pathd:pathd pathd",
827 "step5/show_operational_data_inactive.ref",
830 "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_inactive_srte.ref"
833 # re-create the prefix SID and check if the SR Policy is active
834 create_prefix_sid("rt5", "5.5.5.5/32", 50)
837 "show yang operational-data /frr-pathd:pathd pathd",
838 "step5/show_operational_data_active.ref",
841 "rt1", "show ip route bgp json", "step5/show_ip_route_bgp_active_srte.ref"
845 # Memory leak test template
846 def test_memory_leak():
847 "Run the memory leak test and report results."
849 if not tgen
.is_memleak_enabled():
850 pytest
.skip("Memory leak test/report is disabled")
852 tgen
.report_memory_leaks()
855 if __name__
== "__main__":
856 args
= ["-s"] + sys
.argv
[1:]
857 sys
.exit(pytest
.main(args
))