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