]> git.proxmox.com Git - mirror_frr.git/blob - tests/topotests/ospf_sr_te_topo1/test_ospf_sr_te_topo1.py
Merge pull request #12798 from donaldsharp/rib_match_multicast
[mirror_frr.git] / tests / topotests / ospf_sr_te_topo1 / test_ospf_sr_te_topo1.py
1 #!/usr/bin/env python
2 # SPDX-License-Identifier: ISC
3
4 #
5 # test_ospf_sr_te_topo1.py
6 #
7 # Copyright (c) 2021 by
8 # Volta Networks
9 #
10
11 """
12 test_ospf_sr_te_topo1.py:
13
14 +---------+
15 | |
16 | RT1 |
17 | 1.1.1.1 |
18 | |
19 +---------+
20 |eth-sw1
21 |
22 |
23 |
24 +---------+ | +---------+
25 | | | | |
26 | RT2 |eth-sw1 | eth-sw1| RT3 |
27 | 2.2.2.2 +----------+ + 3.3.3.3 |
28 | | 10.0.1.0/24 | |
29 +---------+ +---------+
30 eth-rt4-1| eth-rt5-1| |eth-rt5-2
31 | | |
32 10.0.2.0/24| 10.0.4.0/24| |10.0.5.0/24
33 | | |
34 eth-rt2-1| eth-rt3-1| |eth-rt3-2
35 +---------+ +---------+
36 | | | |
37 | RT4 | 10.0.6.0/24 | RT5 |
38 | 4.4.4.4 +---------------------+ 5.5.5.5 |
39 | |eth-rt5 eth-rt4| |
40 +---------+ +---------+
41 eth-rt6| |eth-rt6
42 | |
43 10.0.7.0/24| |10.0.8.0/24
44 | +---------+ |
45 | | | |
46 | | RT6 | |
47 +----------+ 6.6.6.6 +-----------+
48 eth-rt4| |eth-rt5
49 +---------+
50 |eth-dst (.1)
51 |
52 |10.0.11.0/24
53 |
54 |eth-rt6 (.2)
55 +---------+
56 | |
57 | DST |
58 | 9.9.9.2 |
59 | |
60 +---------+
61
62 """
63
64 import os
65 import sys
66 import pytest
67 import json
68 from time import sleep
69 from functools import partial
70
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, "../"))
74
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
80
81 # Required to instantiate the topology builder class.
82
83 pytestmark = [pytest.mark.bgpd, pytest.mark.ospfd, pytest.mark.pathd]
84
85
86 def build_topo(tgen):
87 "Build function"
88
89 #
90 # Define FRR Routers
91 #
92 for router in ["rt1", "rt2", "rt3", "rt4", "rt5", "rt6", "dst"]:
93 tgen.add_router(router)
94
95 #
96 # Define connections
97 #
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")
102
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")
106
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")
110
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")
114
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")
118
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")
122
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")
126
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")
130
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")
134
135
136 def setup_module(mod):
137 "Sets up the pytest environment"
138
139 tgen = Topogen(build_topo, mod.__name__)
140
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 in:" + frrdir)
144
145 tgen.start_topology()
146
147 router_list = tgen.routers()
148
149 # For all registered routers, load the zebra configuration file
150 for rname, router in router_list.items():
151 router.load_config(
152 TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname))
153 )
154 router.load_config(
155 TopoRouter.RD_OSPF, os.path.join(CWD, "{}/ospfd.conf".format(rname))
156 )
157 router.load_config(
158 TopoRouter.RD_PATH, os.path.join(CWD, "{}/pathd.conf".format(rname))
159 )
160 router.load_config(
161 TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname))
162 )
163
164 tgen.start_router()
165
166
167 def teardown_module(mod):
168 "Teardown the pytest environment"
169 tgen = get_topogen()
170
171 # This function tears down the whole topology.
172 tgen.stop_topology()
173
174
175 def setup_testcase(msg):
176 logger.info(msg)
177 tgen = get_topogen()
178
179 # Skip if previous fatal error condition is raised
180 if tgen.routers_have_failure():
181 pytest.skip(tgen.errors)
182
183 return tgen
184
185
186 def print_cmd_result(rname, command):
187 print(get_topogen().gears[rname].vtysh_cmd(command, isjson=False))
188
189
190 def compare_json_test(router, command, reference, exact):
191 output = router.vtysh_cmd(command, isjson=True)
192 result = topotest.json_cmp(output, reference)
193
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)
198 else:
199 return result
200
201
202 def cmp_json_output(rname, command, reference, exact=False):
203 "Compare router JSON output"
204
205 logger.info('Comparing router "%s" "%s" output', rname, command)
206
207 tgen = get_topogen()
208 filename = "{}/{}/{}".format(CWD, rname, reference)
209 expected = json.loads(open(filename).read())
210
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=60, wait=0.5)
214 assertmsg = '"{}" JSON output mismatches the expected result'.format(rname)
215 assert diff is None, assertmsg
216
217
218 def cmp_json_output_exact(rname, command, reference):
219 return cmp_json_output(rname, command, reference, True)
220
221
222 def add_candidate_path(rname, endpoint, pref, name, segment_list="default"):
223 get_topogen().net[rname].cmd(
224 """ \
225 vtysh -c "conf t" \
226 -c "segment-routing" \
227 -c "traffic-eng" \
228 -c "policy color 1 endpoint """
229 + endpoint
230 + """" \
231 -c "candidate-path preference """
232 + str(pref)
233 + """ name """
234 + name
235 + """ explicit segment-list """
236 + segment_list
237 + '''"'''
238 )
239
240
241 def delete_candidate_path(rname, endpoint, pref):
242 get_topogen().net[rname].cmd(
243 """ \
244 vtysh -c "conf t" \
245 -c "segment-routing" \
246 -c "traffic-eng" \
247 -c "policy color 1 endpoint """
248 + endpoint
249 + """" \
250 -c "no candidate-path preference """
251 + str(pref)
252 + '''"'''
253 )
254
255
256 def add_segment(rname, name, index, label):
257 get_topogen().net[rname].cmd(
258 """ \
259 vtysh -c "conf t" \
260 -c "segment-routing" \
261 -c "traffic-eng" \
262 -c "segment-list """
263 + name
264 + """" \
265 -c "index """
266 + str(index)
267 + """ mpls label """
268 + str(label)
269 + '''"'''
270 )
271
272
273 def delete_segment(rname, name, index):
274 get_topogen().net[rname].cmd(
275 """ \
276 vtysh -c "conf t" \
277 -c "segment-routing" \
278 -c "traffic-eng" \
279 -c "segment-list """
280 + name
281 + """" \
282 -c "no index """
283 + str(index)
284 + '''"'''
285 )
286
287
288 def add_segment_adj(rname, name, index, src, dst):
289 get_topogen().net[rname].cmd(
290 """ \
291 vtysh -c "conf t" \
292 -c "segment-routing" \
293 -c "traffic-eng" \
294 -c "segment-list """
295 + name
296 + """" \
297 -c "index """
298 + str(index)
299 + """ nai adjacency """
300 + str(src)
301 + """ """
302 + str(dst)
303 + '''"'''
304 )
305
306
307 def create_sr_policy(rname, endpoint, bsid):
308 get_topogen().net[rname].cmd(
309 """ \
310 vtysh -c "conf t" \
311 -c "segment-routing" \
312 -c "traffic-eng" \
313 -c "policy color 1 endpoint """
314 + endpoint
315 + """" \
316 -c "name default" \
317 -c "binding-sid """
318 + str(bsid)
319 + '''"'''
320 )
321
322
323 def delete_sr_policy(rname, endpoint):
324 get_topogen().net[rname].cmd(
325 """ \
326 vtysh -c "conf t" \
327 -c "segment-routing" \
328 -c "traffic-eng" \
329 -c "no policy color 1 endpoint """
330 + endpoint
331 + '''"'''
332 )
333
334
335 def create_prefix_sid(rname, prefix, sid):
336 get_topogen().net[rname].cmd(
337 """ \
338 vtysh -c "conf t" \
339 -c "router ospf " \
340 -c "segment-routing prefix """
341 + prefix
342 + " index "
343 + str(sid)
344 + '''"'''
345 )
346
347
348 def delete_prefix_sid(rname, prefix):
349 get_topogen().net[rname].cmd(
350 ''' \
351 vtysh -c "conf t" \
352 -c "router ospf " \
353 -c "no segment-routing prefix "'''
354 + prefix
355 )
356
357
358 def check_bsid(rt, bsid, fn_name, positive):
359 """
360 Search for a bsid in rt1 and rt6
361 Positive means that check is true is bsid is found
362 Positive="False" means that check is true is bsid is NOT found
363 """
364
365 logger.info('Checking "%s" bsid "%s" for router "%s" ', positive, bsid, rt)
366
367 count = 0
368 candidate_key = bsid
369 candidate_output = ""
370 # First wait for convergence
371 tgen = get_topogen()
372 while count < 30:
373 matched = False
374 matched_key = False
375 sleep(1)
376 count += 1
377 router = tgen.gears[rt]
378 candidate_output = router.vtysh_cmd("show mpls table json")
379 candidate_output_json = json.loads(candidate_output)
380 for item in candidate_output_json.items():
381 # logger.info('item "%s"', item)
382 if item[0] == candidate_key:
383 matched_key = True
384 if positive:
385 break
386 if positive:
387 if matched_key:
388 matched = True
389 assertmsg = "{} don't has entry {} but is was expected".format(
390 router.name, candidate_key
391 )
392 else:
393 if not matched_key:
394 matched = True
395 assertmsg = "{} has entry {} but is wans't expected".format(
396 router.name, candidate_key
397 )
398 if matched:
399 logger.info('Success "%s" in "%s"', router.name, fn_name)
400 return
401 assert matched, assertmsg
402
403
404 #
405 # Step 1
406 #
407 # Checking the MPLS table using a single SR Policy and a single Candidate Path
408 # Segment list are base in adjacency that query TED
409 #
410 def test_srte_init_step1():
411 setup_testcase("Test (step 1): wait OSPF convergence / label distribution")
412
413 check_bsid("rt1", "1111", test_srte_init_step1.__name__, False)
414 check_bsid("rt6", "6666", test_srte_init_step1.__name__, False)
415
416
417 def test_srte_add_candidate_check_mpls_table_step1():
418 setup_testcase("Test (step 1): check MPLS table regarding the added Candidate Path")
419
420 for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
421 add_candidate_path(rname, endpoint, 100, "default")
422 check_bsid(
423 rname,
424 "1111" if rname == "rt1" else "6666",
425 test_srte_init_step1.__name__,
426 True,
427 )
428 delete_candidate_path(rname, endpoint, 100)
429
430
431 def test_srte_reinstall_sr_policy_check_mpls_table_step1():
432 setup_testcase(
433 "Test (step 1): check MPLS table after the SR Policy was removed and reinstalled"
434 )
435
436 for rname, endpoint, bsid in [("rt1", "6.6.6.6", 1111), ("rt6", "1.1.1.1", 6666)]:
437 add_candidate_path(rname, endpoint, 100, "default")
438 delete_sr_policy(rname, endpoint)
439 check_bsid(rname, bsid, test_srte_init_step1.__name__, False)
440 create_sr_policy(rname, endpoint, bsid)
441 add_candidate_path(rname, endpoint, 100, "default")
442 check_bsid(
443 rname,
444 "1111" if rname == "rt1" else "6666",
445 test_srte_init_step1.__name__,
446 True,
447 )
448 delete_candidate_path(rname, endpoint, 100)
449
450
451 #
452 # Step 2
453 #
454 # Checking pathd operational data using a single SR Policy and a single Candidate Path
455 # Segment list are base in adjacency that query TED
456 #
457 def test_srte_bare_policy_step2():
458 setup_testcase("Test (step 2): bare SR Policy should not be operational")
459
460 for rname in ["rt1", "rt6"]:
461 cmp_json_output_exact(
462 rname,
463 "show yang operational-data /frr-pathd:pathd pathd",
464 "step2/show_operational_data.ref",
465 )
466
467
468 def test_srte_add_candidate_check_operational_data_step2():
469 setup_testcase(
470 "Test (step 2): add single Candidate Path, SR Policy should be operational"
471 )
472
473 for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
474 add_candidate_path(rname, endpoint, 100, "default")
475 cmp_json_output(
476 rname,
477 "show yang operational-data /frr-pathd:pathd pathd",
478 "step2/show_operational_data_with_candidate.ref",
479 )
480
481
482 def test_srte_config_remove_candidate_check_operational_data_step2():
483 setup_testcase(
484 "Test (step 2): remove single Candidate Path, SR Policy should not be operational anymore"
485 )
486
487 for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
488 delete_candidate_path(rname, endpoint, 100)
489 cmp_json_output_exact(
490 rname,
491 "show yang operational-data /frr-pathd:pathd pathd",
492 "step2/show_operational_data.ref",
493 )
494
495
496 #
497 # Step 3
498 #
499 # Testing the Candidate Path selection
500 # Segment list are based in adjacencies resolved by query TED
501 #
502 def test_srte_add_two_candidates_step3():
503 setup_testcase("Test (step 3): second Candidate Path has higher Priority")
504
505 for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
506 for pref, cand_name in [("100", "first"), ("200", "second")]:
507 add_candidate_path(rname, endpoint, pref, cand_name)
508 cmp_json_output(
509 rname,
510 "show yang operational-data /frr-pathd:pathd pathd",
511 "step3/show_operational_data_with_two_candidates.ref",
512 )
513
514 # cleanup
515 for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
516 for pref in ["100", "200"]:
517 delete_candidate_path(rname, endpoint, pref)
518
519
520 def test_srte_add_two_candidates_with_reverse_priority_step3():
521 setup_testcase("Test (step 3): second Candidate Path has lower Priority")
522
523 # Use reversed priorities here
524 for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
525 for pref, cand_name in [("200", "first"), ("100", "second")]:
526 add_candidate_path(rname, endpoint, pref, cand_name)
527 cmp_json_output(
528 rname,
529 "show yang operational-data /frr-pathd:pathd pathd",
530 "step3/show_operational_data_with_two_candidates.ref",
531 )
532
533 # cleanup
534 for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
535 for pref in ["100", "200"]:
536 delete_candidate_path(rname, endpoint, pref)
537
538
539 def test_srte_remove_best_candidate_step3():
540 setup_testcase("Test (step 3): delete the Candidate Path with higher priority")
541
542 for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
543 for pref, cand_name in [("100", "first"), ("200", "second")]:
544 add_candidate_path(rname, endpoint, pref, cand_name)
545
546 # Delete candidate with higher priority
547 for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
548 delete_candidate_path(rname, endpoint, 200)
549
550 # Candidate with lower priority should get active now
551 for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
552 cmp_json_output(
553 rname,
554 "show yang operational-data /frr-pathd:pathd pathd",
555 "step3/show_operational_data_with_single_candidate.ref",
556 )
557 # cleanup
558 delete_candidate_path(rname, endpoint, 100)
559
560
561 #
562 # Step 4
563 #
564 # Checking MPLS table with a single SR Policy and a Candidate Path with different Segment Lists and other modifications
565 # Segment list are base in adjacency that query TED
566 #
567 def test_srte_change_segment_list_check_mpls_table_step4():
568 setup_testcase("Test (step 4): check MPLS table for changed Segment List")
569
570 for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
571 add_candidate_path(rname, endpoint, 100, "default")
572 # now change the segment list name
573 add_candidate_path(rname, endpoint, 100, "default", "test")
574 check_bsid(
575 rname,
576 "1111" if rname == "rt1" else "6666",
577 test_srte_init_step1.__name__,
578 True,
579 )
580 delete_segment(rname, "test", 10)
581 delete_segment(rname, "test", 20)
582 delete_segment(rname, "test", 30)
583 delete_segment(rname, "test", 40)
584 if rname == "rt1":
585 add_segment_adj(rname, "test", 10, "10.0.1.1", "10.0.1.2")
586 add_segment_adj(rname, "test", 20, "10.0.2.2", "10.0.2.4")
587 add_segment_adj(rname, "test", 30, "10.0.6.4", "10.0.6.5")
588 add_segment_adj(rname, "test", 40, "10.0.8.5", "10.0.8.6")
589 else:
590 add_segment_adj(rname, "test", 10, "10.0.8.6", "10.0.8.5")
591 add_segment_adj(rname, "test", 20, "10.0.6.5", "10.0.6.4")
592 add_segment_adj(rname, "test", 30, "10.0.2.4", "10.0.2.2")
593 add_segment_adj(rname, "test", 40, "10.0.1.2", "10.0.1.1")
594 check_bsid(
595 rname,
596 "1111" if rname == "rt1" else "6666",
597 test_srte_init_step1.__name__,
598 True,
599 )
600 delete_candidate_path(rname, endpoint, 100)
601
602
603 def test_srte_change_sl_priority_error_ted_check_mpls_table_step4():
604 setup_testcase("Test (step 4): check MPLS table keeps low prio sl")
605
606 for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
607 add_candidate_path(rname, endpoint, 100, "default")
608 # now change the segment list name
609 add_candidate_path(rname, endpoint, 200, "test", "test")
610 check_bsid(
611 rname,
612 "1111" if rname == "rt1" else "6666",
613 test_srte_init_step1.__name__,
614 True,
615 )
616 delete_segment(rname, "test", 10)
617 delete_segment(rname, "test", 20)
618 delete_segment(rname, "test", 30)
619 delete_segment(rname, "test", 40)
620 # These won't resolv
621 if rname == "rt1":
622 add_segment_adj(rname, "test", 10, "10.0.1.99", "10.0.1.99")
623 add_segment_adj(rname, "test", 20, "10.0.2.99", "10.0.2.99")
624 add_segment_adj(rname, "test", 30, "10.0.6.99", "10.0.6.99")
625 add_segment_adj(rname, "test", 40, "10.0.8.99", "10.0.8.99")
626 else:
627 add_segment_adj(rname, "test", 10, "10.0.8.99", "10.0.8.99")
628 add_segment_adj(rname, "test", 20, "10.0.6.99", "10.0.6.99")
629 add_segment_adj(rname, "test", 30, "10.0.2.99", "10.0.2.99")
630 add_segment_adj(rname, "test", 40, "10.0.1.99", "10.0.1.99")
631 # So policy sticks with default sl even higher prio
632 check_bsid(
633 rname,
634 "1111" if rname == "rt1" else "6666",
635 test_srte_init_step1.__name__,
636 True,
637 )
638 delete_candidate_path(rname, endpoint, 100)
639
640
641 # Memory leak test template
642 def test_memory_leak():
643 "Run the memory leak test and report results."
644 tgen = get_topogen()
645 if not tgen.is_memleak_enabled():
646 pytest.skip("Memory leak test/report is disabled")
647
648 tgen.report_memory_leaks()
649
650
651 if __name__ == "__main__":
652 args = ["-s"] + sys.argv[1:]
653 sys.exit(pytest.main(args))