]> git.proxmox.com Git - mirror_frr.git/blob - tests/topotests/ospf_sr_te_topo1/test_ospf_sr_te_topo1.py
tests: remove legacy Topo class (fixes many pylint errors)
[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 lib.micronet_compat import Topo
97
98 pytestmark = [pytest.mark.bgpd, pytest.mark.ospfd, pytest.mark.pathd]
99
100
101 def build_topo(tgen):
102 "Build function"
103
104 #
105 # Define FRR Routers
106 #
107 for router in ["rt1", "rt2", "rt3", "rt4", "rt5", "rt6", "dst"]:
108 tgen.add_router(router)
109
110 #
111 # Define connections
112 #
113 switch = tgen.add_switch("s1")
114 switch.add_link(tgen.gears["rt1"], nodeif="eth-sw1")
115 switch.add_link(tgen.gears["rt2"], nodeif="eth-sw1")
116 #switch.add_link(tgen.gears["rt3"], nodeif="eth-sw1")
117
118 switch = tgen.add_switch("s2")
119 switch.add_link(tgen.gears["rt2"], nodeif="eth-rt4-1")
120 switch.add_link(tgen.gears["rt4"], nodeif="eth-rt2-1")
121
122 #switch = tgen.add_switch("s3")
123 #switch.add_link(tgen.gears["rt2"], nodeif="eth-rt4-2")
124 #switch.add_link(tgen.gears["rt4"], nodeif="eth-rt2-2")
125
126 switch = tgen.add_switch("s4")
127 switch.add_link(tgen.gears["rt3"], nodeif="eth-rt5-1")
128 switch.add_link(tgen.gears["rt5"], nodeif="eth-rt3-1")
129
130 switch = tgen.add_switch("s5")
131 switch.add_link(tgen.gears["rt3"], nodeif="eth-rt5-2")
132 switch.add_link(tgen.gears["rt5"], nodeif="eth-rt3-2")
133
134 switch = tgen.add_switch("s6")
135 switch.add_link(tgen.gears["rt4"], nodeif="eth-rt5")
136 switch.add_link(tgen.gears["rt5"], nodeif="eth-rt4")
137
138 switch = tgen.add_switch("s7")
139 switch.add_link(tgen.gears["rt4"], nodeif="eth-rt6")
140 switch.add_link(tgen.gears["rt6"], nodeif="eth-rt4")
141
142 switch = tgen.add_switch("s8")
143 switch.add_link(tgen.gears["rt5"], nodeif="eth-rt6")
144 switch.add_link(tgen.gears["rt6"], nodeif="eth-rt5")
145
146 switch = tgen.add_switch("s9")
147 switch.add_link(tgen.gears["rt6"], nodeif="eth-dst")
148 switch.add_link(tgen.gears["dst"], nodeif="eth-rt6")
149
150
151 def setup_module(mod):
152 "Sets up the pytest environment"
153
154 tgen = Topogen(build_topo, mod.__name__)
155
156 frrdir = tgen.config.get(tgen.CONFIG_SECTION, "frrdir")
157 if not os.path.isfile(os.path.join(frrdir, "pathd")):
158 pytest.skip("pathd daemon wasn't built in:"+frrdir)
159
160 tgen.start_topology()
161
162 router_list = tgen.routers()
163
164 # For all registered routers, load the zebra configuration file
165 for rname, router in router_list.items():
166 router.load_config(
167 TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname))
168 )
169 router.load_config(
170 TopoRouter.RD_OSPF, os.path.join(CWD, "{}/ospfd.conf".format(rname))
171 )
172 router.load_config(
173 TopoRouter.RD_PATH, os.path.join(CWD, "{}/pathd.conf".format(rname))
174 )
175 router.load_config(
176 TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname))
177 )
178
179 tgen.start_router()
180
181
182 def teardown_module(mod):
183 "Teardown the pytest environment"
184 tgen = get_topogen()
185
186 # This function tears down the whole topology.
187 tgen.stop_topology()
188
189
190 def setup_testcase(msg):
191 logger.info(msg)
192 tgen = get_topogen()
193
194 # Skip if previous fatal error condition is raised
195 if tgen.routers_have_failure():
196 pytest.skip(tgen.errors)
197
198 return tgen
199
200
201 def print_cmd_result(rname, command):
202 print(get_topogen().gears[rname].vtysh_cmd(command, isjson=False))
203
204
205 def compare_json_test(router, command, reference, exact):
206 output = router.vtysh_cmd(command, isjson=True)
207 result = topotest.json_cmp(output, reference)
208
209 # Note: topotest.json_cmp() just checks on inclusion of keys.
210 # For exact matching also compare the other way around.
211 if not result and exact:
212 return topotest.json_cmp(reference, output)
213 else:
214 return result
215
216
217 def cmp_json_output(rname, command, reference, exact=False):
218 "Compare router JSON output"
219
220 logger.info('Comparing router "%s" "%s" output', rname, command)
221
222 tgen = get_topogen()
223 filename = "{}/{}/{}".format(CWD, rname, reference)
224 expected = json.loads(open(filename).read())
225
226 # Run test function until we get an result. Wait at most 60 seconds.
227 test_func = partial(compare_json_test, tgen.gears[rname], command, expected, exact)
228 _, diff = topotest.run_and_expect(test_func, None, count=60, wait=0.5)
229 assertmsg = '"{}" JSON output mismatches the expected result'.format(rname)
230 assert diff is None, assertmsg
231
232
233 def cmp_json_output_exact(rname, command, reference):
234 return cmp_json_output(rname, command, reference, True)
235
236
237 def add_candidate_path(rname, endpoint, pref, name, segment_list="default"):
238 get_topogen().net[rname].cmd(
239 """ \
240 vtysh -c "conf t" \
241 -c "segment-routing" \
242 -c "traffic-eng" \
243 -c "policy color 1 endpoint """
244 + endpoint
245 + """" \
246 -c "candidate-path preference """
247 + str(pref)
248 + """ name """
249 + name
250 + """ explicit segment-list """
251 + segment_list
252 + '''"'''
253 )
254
255
256 def delete_candidate_path(rname, endpoint, pref):
257 get_topogen().net[rname].cmd(
258 """ \
259 vtysh -c "conf t" \
260 -c "segment-routing" \
261 -c "traffic-eng" \
262 -c "policy color 1 endpoint """
263 + endpoint
264 + """" \
265 -c "no candidate-path preference """
266 + str(pref)
267 + '''"'''
268 )
269
270
271 def add_segment(rname, name, index, label):
272 get_topogen().net[rname].cmd(
273 """ \
274 vtysh -c "conf t" \
275 -c "segment-routing" \
276 -c "traffic-eng" \
277 -c "segment-list """
278 + name
279 + """" \
280 -c "index """
281 + str(index)
282 + """ mpls label """
283 + str(label)
284 + '''"'''
285 )
286
287
288 def delete_segment(rname, name, index):
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 "no index """
298 + str(index)
299 + '''"'''
300 )
301
302
303 def add_segment_adj(rname, name, index, src, dst):
304 get_topogen().net[rname].cmd(
305 """ \
306 vtysh -c "conf t" \
307 -c "segment-routing" \
308 -c "traffic-eng" \
309 -c "segment-list """
310 + name
311 + """" \
312 -c "index """
313 + str(index)
314 + """ nai adjacency """
315 + str(src)
316 + """ """
317 + str(dst)
318 + '''"'''
319 )
320
321
322 def create_sr_policy(rname, endpoint, bsid):
323 get_topogen().net[rname].cmd(
324 """ \
325 vtysh -c "conf t" \
326 -c "segment-routing" \
327 -c "traffic-eng" \
328 -c "policy color 1 endpoint """
329 + endpoint
330 + """" \
331 -c "name default" \
332 -c "binding-sid """
333 + str(bsid)
334 + '''"'''
335 )
336
337
338 def delete_sr_policy(rname, endpoint):
339 get_topogen().net[rname].cmd(
340 """ \
341 vtysh -c "conf t" \
342 -c "segment-routing" \
343 -c "traffic-eng" \
344 -c "no policy color 1 endpoint """
345 + endpoint
346 + '''"'''
347 )
348
349
350 def create_prefix_sid(rname, prefix, sid):
351 get_topogen().net[rname].cmd(
352 """ \
353 vtysh -c "conf t" \
354 -c "router ospf " \
355 -c "segment-routing prefix """
356 + prefix
357 + " index "
358 + str(sid)
359 + '''"'''
360 )
361
362
363 def delete_prefix_sid(rname, prefix):
364 get_topogen().net[rname].cmd(
365 ''' \
366 vtysh -c "conf t" \
367 -c "router ospf " \
368 -c "no segment-routing prefix "'''
369 + prefix
370 )
371
372
373 def check_bsid(rt, bsid, fn_name, positive):
374 """
375 Search for a bsid in rt1 and rt6
376 Positive means that check is true is bsid is found
377 Positive="False" means that check is true is bsid is NOT found
378 """
379
380 logger.info('Checking "%s" bsid "%s" for router "%s" ', positive, bsid, rt)
381
382 count = 0
383 candidate_key = bsid
384 candidate_output = ""
385 # First wait for convergence
386 tgen = get_topogen()
387 while count < 30:
388 matched = False
389 matched_key = False
390 sleep(1)
391 count += 1
392 router = tgen.gears[rt]
393 candidate_output = router.vtysh_cmd("show mpls table json")
394 candidate_output_json = json.loads(candidate_output)
395 for item in candidate_output_json.items():
396 # logger.info('item "%s"', item)
397 if item[0] == candidate_key:
398 matched_key = True
399 if positive:
400 break
401 if positive:
402 if matched_key:
403 matched = True
404 assertmsg = "{} don't has entry {} but is was expected".format(
405 router.name, candidate_key)
406 else:
407 if not matched_key:
408 matched = True
409 assertmsg = "{} has entry {} but is wans't expected".format(
410 router.name, candidate_key)
411 if matched:
412 logger.info('Success "%s" in "%s"', router.name, fn_name)
413 return
414 assert matched, assertmsg
415
416
417 #
418 # Step 1
419 #
420 # Checking the MPLS table using a single SR Policy and a single Candidate Path
421 # Segment list are base in adjacency that query TED
422 #
423 def test_srte_init_step1():
424 setup_testcase("Test (step 1): wait OSPF convergence / label distribution")
425
426 check_bsid("rt1", "1111", test_srte_init_step1.__name__, False)
427 check_bsid("rt6", "6666", test_srte_init_step1.__name__, False)
428
429
430 def test_srte_add_candidate_check_mpls_table_step1():
431 setup_testcase("Test (step 1): check MPLS table regarding the added Candidate Path")
432
433 for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
434 add_candidate_path(rname, endpoint, 100, "default")
435 check_bsid(rname, "1111" if rname == "rt1" else "6666", test_srte_init_step1.__name__, True)
436 delete_candidate_path(rname, endpoint, 100)
437
438
439 def test_srte_reinstall_sr_policy_check_mpls_table_step1():
440 setup_testcase(
441 "Test (step 1): check MPLS table after the SR Policy was removed and reinstalled"
442 )
443
444 for rname, endpoint, bsid in [("rt1", "6.6.6.6", 1111), ("rt6", "1.1.1.1", 6666)]:
445 add_candidate_path(rname, endpoint, 100, "default")
446 delete_sr_policy(rname, endpoint)
447 check_bsid(rname, bsid, test_srte_init_step1.__name__, False)
448 create_sr_policy(rname, endpoint, bsid)
449 add_candidate_path(rname, endpoint, 100, "default")
450 check_bsid(rname, "1111" if rname == "rt1" else "6666", test_srte_init_step1.__name__, True)
451 delete_candidate_path(rname, endpoint, 100)
452
453
454 #
455 # Step 2
456 #
457 # Checking pathd operational data using a single SR Policy and a single Candidate Path
458 # Segment list are base in adjacency that query TED
459 #
460 def test_srte_bare_policy_step2():
461 setup_testcase("Test (step 2): bare SR Policy should not be operational")
462
463 for rname in ["rt1", "rt6"]:
464 cmp_json_output_exact(
465 rname,
466 "show yang operational-data /frr-pathd:pathd pathd",
467 "step2/show_operational_data.ref",
468 )
469
470
471 def test_srte_add_candidate_check_operational_data_step2():
472 setup_testcase(
473 "Test (step 2): add single Candidate Path, SR Policy should be operational"
474 )
475
476 for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
477 add_candidate_path(rname, endpoint, 100, "default")
478 cmp_json_output(
479 rname,
480 "show yang operational-data /frr-pathd:pathd pathd",
481 "step2/show_operational_data_with_candidate.ref",
482 )
483
484
485 def test_srte_config_remove_candidate_check_operational_data_step2():
486 setup_testcase(
487 "Test (step 2): remove single Candidate Path, SR Policy should not be operational anymore"
488 )
489
490 for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
491 delete_candidate_path(rname, endpoint, 100)
492 cmp_json_output_exact(
493 rname,
494 "show yang operational-data /frr-pathd:pathd pathd",
495 "step2/show_operational_data.ref",
496 )
497
498
499 #
500 # Step 3
501 #
502 # Testing the Candidate Path selection
503 # Segment list are based in adjacencies resolved by query TED
504 #
505 def test_srte_add_two_candidates_step3():
506 setup_testcase("Test (step 3): second Candidate Path has higher Priority")
507
508 for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
509 for pref, cand_name in [("100", "first"), ("200", "second")]:
510 add_candidate_path(rname, endpoint, pref, cand_name)
511 cmp_json_output(
512 rname,
513 "show yang operational-data /frr-pathd:pathd pathd",
514 "step3/show_operational_data_with_two_candidates.ref",
515 )
516
517 # cleanup
518 for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
519 for pref in ["100", "200"]:
520 delete_candidate_path(rname, endpoint, pref)
521
522
523 def test_srte_add_two_candidates_with_reverse_priority_step3():
524 setup_testcase("Test (step 3): second Candidate Path has lower Priority")
525
526 # Use reversed priorities here
527 for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
528 for pref, cand_name in [("200", "first"), ("100", "second")]:
529 add_candidate_path(rname, endpoint, pref, cand_name)
530 cmp_json_output(
531 rname,
532 "show yang operational-data /frr-pathd:pathd pathd",
533 "step3/show_operational_data_with_two_candidates.ref",
534 )
535
536 # cleanup
537 for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
538 for pref in ["100", "200"]:
539 delete_candidate_path(rname, endpoint, pref)
540
541
542 def test_srte_remove_best_candidate_step3():
543 setup_testcase("Test (step 3): delete the Candidate Path with higher priority")
544
545 for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
546 for pref, cand_name in [("100", "first"), ("200", "second")]:
547 add_candidate_path(rname, endpoint, pref, cand_name)
548
549 # Delete candidate with higher priority
550 for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
551 delete_candidate_path(rname, endpoint, 200)
552
553 # Candidate with lower priority should get active now
554 for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
555 cmp_json_output(
556 rname,
557 "show yang operational-data /frr-pathd:pathd pathd",
558 "step3/show_operational_data_with_single_candidate.ref",
559 )
560 # cleanup
561 delete_candidate_path(rname, endpoint, 100)
562
563
564 #
565 # Step 4
566 #
567 # Checking MPLS table with a single SR Policy and a Candidate Path with different Segment Lists and other modifications
568 # Segment list are base in adjacency that query TED
569 #
570 def test_srte_change_segment_list_check_mpls_table_step4():
571 setup_testcase("Test (step 4): check MPLS table for changed Segment List")
572
573 for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
574 add_candidate_path(rname, endpoint, 100, "default")
575 # now change the segment list name
576 add_candidate_path(rname, endpoint, 100, "default", "test")
577 check_bsid(rname, "1111" if rname == "rt1" else "6666", test_srte_init_step1.__name__, True)
578 delete_segment(rname, "test", 10)
579 delete_segment(rname, "test", 20)
580 delete_segment(rname, "test", 30)
581 delete_segment(rname, "test", 40)
582 if rname == "rt1":
583 add_segment_adj(rname, "test", 10, "10.0.1.1", "10.0.1.2")
584 add_segment_adj(rname, "test", 20, "10.0.2.2", "10.0.2.4")
585 add_segment_adj(rname, "test", 30, "10.0.6.4", "10.0.6.5")
586 add_segment_adj(rname, "test", 40, "10.0.8.5", "10.0.8.6")
587 else:
588 add_segment_adj(rname, "test", 10, "10.0.8.6", "10.0.8.5")
589 add_segment_adj(rname, "test", 20, "10.0.6.5", "10.0.6.4")
590 add_segment_adj(rname, "test", 30, "10.0.2.4", "10.0.2.2")
591 add_segment_adj(rname, "test", 40, "10.0.1.2", "10.0.1.1")
592 check_bsid(rname, "1111" if rname == "rt1" else "6666", test_srte_init_step1.__name__, True)
593 delete_candidate_path(rname, endpoint, 100)
594
595
596 def test_srte_change_sl_priority_error_ted_check_mpls_table_step4():
597 setup_testcase("Test (step 4): check MPLS table keeps low prio sl")
598
599 for rname, endpoint in [("rt1", "6.6.6.6"), ("rt6", "1.1.1.1")]:
600 add_candidate_path(rname, endpoint, 100, "default")
601 # now change the segment list name
602 add_candidate_path(rname, endpoint, 200, "test", "test")
603 check_bsid(rname, "1111" if rname == "rt1" else "6666", test_srte_init_step1.__name__, True)
604 delete_segment(rname, "test", 10)
605 delete_segment(rname, "test", 20)
606 delete_segment(rname, "test", 30)
607 delete_segment(rname, "test", 40)
608 # These won't resolv
609 if rname == "rt1":
610 add_segment_adj(rname, "test", 10, "10.0.1.99", "10.0.1.99")
611 add_segment_adj(rname, "test", 20, "10.0.2.99", "10.0.2.99")
612 add_segment_adj(rname, "test", 30, "10.0.6.99", "10.0.6.99")
613 add_segment_adj(rname, "test", 40, "10.0.8.99", "10.0.8.99")
614 else:
615 add_segment_adj(rname, "test", 10, "10.0.8.99", "10.0.8.99")
616 add_segment_adj(rname, "test", 20, "10.0.6.99", "10.0.6.99")
617 add_segment_adj(rname, "test", 30, "10.0.2.99", "10.0.2.99")
618 add_segment_adj(rname, "test", 40, "10.0.1.99", "10.0.1.99")
619 # So policy sticks with default sl even higher prio
620 check_bsid(rname, "1111" if rname == "rt1" else "6666", test_srte_init_step1.__name__, True)
621 delete_candidate_path(rname, endpoint, 100)
622
623
624 # Memory leak test template
625 def test_memory_leak():
626 "Run the memory leak test and report results."
627 tgen = get_topogen()
628 if not tgen.is_memleak_enabled():
629 pytest.skip("Memory leak test/report is disabled")
630
631 tgen.report_memory_leaks()
632
633
634 if __name__ == "__main__":
635 args = ["-s"] + sys.argv[1:]
636 sys.exit(pytest.main(args))