]>
Commit | Line | Data |
---|---|---|
66e5fbe0 RW |
1 | #!/usr/bin/env python |
2 | ||
3 | # | |
4 | # test_isis_tilfa_topo1.py | |
5 | # Part of NetDEF Topology Tests | |
6 | # | |
7 | # Copyright (c) 2020 by | |
8 | # Network Device Education Foundation, Inc. ("NetDEF") | |
9 | # | |
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 | |
13 | # in all copies. | |
14 | # | |
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 | |
22 | # OF THIS SOFTWARE. | |
23 | # | |
24 | ||
25 | """ | |
26 | test_isis_lfa_topo1.py: | |
27 | ||
28 | +---------+ | |
29 | | | | |
30 | +--------------------------------+ RT1 +-------------------------------+ | |
31 | | +-------------+ +-------------+ | | |
32 | | | | | | | | |
33 | | | +----+----+ | | | |
34 | | | | |20 | | |
35 | | | | | | | |
36 | | | | | | | |
37 | +----+----+ +----+----+ +----+----+ +----+----+ +----+----+ | |
38 | | | | | | | | | | | | |
39 | | RT2 | 5 | RT3 | | RT4 | | RT5 | | RT6 | | |
40 | | +--------+ | | | | | | | | |
41 | | | | | | | | | | | | |
42 | +----+----+ +----+----+ +----+----+ +----+----+ +----+----+ | |
43 | | | | | | | |
44 | | | |15 | | | |
45 | |5 | | | | | |
46 | | | +----+----+ | | | |
47 | | | | | | | | |
48 | | +-------------+ RT7 +-------------+ | | |
49 | +--------------------------------+ +-------------------------------+ | |
50 | | | | |
51 | +---------+ | |
52 | """ | |
53 | ||
54 | import os | |
55 | import sys | |
56 | import pytest | |
57 | import json | |
e87245d0 | 58 | import time |
66e5fbe0 | 59 | import tempfile |
66e5fbe0 RW |
60 | from functools import partial |
61 | ||
62 | # Save the Current Working Directory to find configuration files. | |
63 | CWD = os.path.dirname(os.path.realpath(__file__)) | |
9fa6ec14 | 64 | sys.path.append(os.path.join(CWD, "../")) |
66e5fbe0 RW |
65 | |
66 | # pylint: disable=C0413 | |
67 | # Import topogen and topotest helpers | |
68 | from lib import topotest | |
69 | from lib.topogen import Topogen, TopoRouter, get_topogen | |
70 | from lib.topolog import logger | |
71 | ||
72 | # Required to instantiate the topology builder class. | |
66e5fbe0 | 73 | |
6907ac7e DS |
74 | pytestmark = [pytest.mark.isisd] |
75 | ||
66e5fbe0 RW |
76 | # Global multi-dimensional dictionary containing all expected outputs |
77 | outputs = {} | |
78 | ||
9fa6ec14 | 79 | |
8db751b8 CH |
80 | def build_topo(tgen): |
81 | "Build function" | |
82 | ||
83 | # | |
84 | # Define FRR Routers | |
85 | # | |
86 | for router in ["rt1", "rt2", "rt3", "rt4", "rt5", "rt6", "rt7"]: | |
87 | tgen.add_router(router) | |
88 | ||
89 | # | |
90 | # Define connections | |
91 | # | |
92 | switch = tgen.add_switch("s1") | |
93 | switch.add_link(tgen.gears["rt1"], nodeif="eth-rt2") | |
94 | switch.add_link(tgen.gears["rt2"], nodeif="eth-rt1") | |
95 | switch = tgen.add_switch("s2") | |
96 | switch.add_link(tgen.gears["rt2"], nodeif="eth-rt3") | |
97 | switch.add_link(tgen.gears["rt3"], nodeif="eth-rt2") | |
98 | switch = tgen.add_switch("s3") | |
99 | switch.add_link(tgen.gears["rt1"], nodeif="eth-rt3") | |
100 | switch.add_link(tgen.gears["rt3"], nodeif="eth-rt1") | |
101 | switch = tgen.add_switch("s4") | |
102 | switch.add_link(tgen.gears["rt1"], nodeif="eth-rt4") | |
103 | switch.add_link(tgen.gears["rt4"], nodeif="eth-rt1") | |
104 | switch = tgen.add_switch("s5") | |
105 | switch.add_link(tgen.gears["rt1"], nodeif="eth-rt5") | |
106 | switch.add_link(tgen.gears["rt5"], nodeif="eth-rt1") | |
107 | switch = tgen.add_switch("s6") | |
108 | switch.add_link(tgen.gears["rt1"], nodeif="eth-rt6") | |
109 | switch.add_link(tgen.gears["rt6"], nodeif="eth-rt1") | |
110 | switch = tgen.add_switch("s7") | |
111 | switch.add_link(tgen.gears["rt2"], nodeif="eth-rt7") | |
112 | switch.add_link(tgen.gears["rt7"], nodeif="eth-rt2") | |
113 | switch = tgen.add_switch("s8") | |
114 | switch.add_link(tgen.gears["rt3"], nodeif="eth-rt7") | |
115 | switch.add_link(tgen.gears["rt7"], nodeif="eth-rt3") | |
116 | switch = tgen.add_switch("s9") | |
117 | switch.add_link(tgen.gears["rt4"], nodeif="eth-rt7") | |
118 | switch.add_link(tgen.gears["rt7"], nodeif="eth-rt4") | |
119 | switch = tgen.add_switch("s10") | |
120 | switch.add_link(tgen.gears["rt5"], nodeif="eth-rt7") | |
121 | switch.add_link(tgen.gears["rt7"], nodeif="eth-rt5") | |
122 | switch = tgen.add_switch("s11") | |
123 | switch.add_link(tgen.gears["rt6"], nodeif="eth-rt7") | |
124 | switch.add_link(tgen.gears["rt7"], nodeif="eth-rt6") | |
125 | ||
126 | # | |
127 | # Populate multi-dimensional dictionary containing all expected outputs | |
128 | # | |
129 | files = ["show_ipv6_route.ref", "show_yang_interface_isis_adjacencies.ref"] | |
130 | for rname in ["rt1", "rt2", "rt3", "rt4", "rt5", "rt6", "rt7"]: | |
131 | outputs[rname] = {} | |
f4d0de10 | 132 | for step in range(1, 16 + 1): |
8db751b8 CH |
133 | outputs[rname][step] = {} |
134 | for file in files: | |
135 | if step == 1: | |
136 | # Get snapshots relative to the expected initial network convergence | |
137 | filename = "{}/{}/step{}/{}".format(CWD, rname, step, file) | |
138 | outputs[rname][step][file] = open(filename).read() | |
139 | else: | |
140 | if rname != "rt1": | |
141 | continue | |
142 | if file == "show_yang_interface_isis_adjacencies.ref": | |
143 | continue | |
144 | ||
145 | # Get diff relative to the previous step | |
146 | filename = "{}/{}/step{}/{}.diff".format(CWD, rname, step, file) | |
147 | ||
148 | # Create temporary files in order to apply the diff | |
149 | f_in = tempfile.NamedTemporaryFile(mode="w") | |
150 | f_in.write(outputs[rname][step - 1][file]) | |
151 | f_in.flush() | |
152 | f_out = tempfile.NamedTemporaryFile(mode="r") | |
153 | os.system( | |
154 | "patch -s -o %s %s %s" % (f_out.name, f_in.name, filename) | |
155 | ) | |
156 | ||
157 | # Store the updated snapshot and remove the temporary files | |
158 | outputs[rname][step][file] = open(f_out.name).read() | |
159 | f_in.close() | |
160 | f_out.close() | |
66e5fbe0 | 161 | |
5980ad0a | 162 | |
66e5fbe0 RW |
163 | def setup_module(mod): |
164 | "Sets up the pytest environment" | |
e82b531d | 165 | tgen = Topogen(build_topo, mod.__name__) |
66e5fbe0 RW |
166 | tgen.start_topology() |
167 | ||
168 | router_list = tgen.routers() | |
169 | ||
170 | # For all registered routers, load the zebra configuration file | |
d7d21c3a | 171 | for rname, router in router_list.items(): |
66e5fbe0 | 172 | router.load_config( |
9fa6ec14 | 173 | TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) |
66e5fbe0 RW |
174 | ) |
175 | router.load_config( | |
9fa6ec14 | 176 | TopoRouter.RD_ISIS, os.path.join(CWD, "{}/isisd.conf".format(rname)) |
66e5fbe0 | 177 | ) |
1d5185ec LS |
178 | router.load_config( |
179 | TopoRouter.RD_BFD, os.path.join(CWD, "/dev/null".format(rname)) | |
180 | ) | |
66e5fbe0 RW |
181 | |
182 | tgen.start_router() | |
183 | ||
9fa6ec14 | 184 | |
66e5fbe0 RW |
185 | def teardown_module(mod): |
186 | "Teardown the pytest environment" | |
187 | tgen = get_topogen() | |
188 | ||
189 | # This function tears down the whole topology. | |
190 | tgen.stop_topology() | |
191 | ||
9fa6ec14 | 192 | |
f4d0de10 | 193 | def router_compare_json_output(rname, command, reference, wait=0.5, count=120): |
66e5fbe0 RW |
194 | "Compare router JSON output" |
195 | ||
196 | logger.info('Comparing router "%s" "%s" output', rname, command) | |
197 | ||
198 | tgen = get_topogen() | |
199 | expected = json.loads(reference) | |
200 | ||
201 | # Run test function until we get an result. Wait at most 60 seconds. | |
9fa6ec14 | 202 | test_func = partial(topotest.router_json_cmp, tgen.gears[rname], command, expected) |
f4d0de10 | 203 | _, diff = topotest.run_and_expect(test_func, None, count=count, wait=wait) |
66e5fbe0 RW |
204 | assertmsg = '"{}" JSON output mismatches the expected result'.format(rname) |
205 | assert diff is None, assertmsg | |
206 | ||
9fa6ec14 | 207 | |
66e5fbe0 RW |
208 | # |
209 | # Step 1 | |
210 | # | |
211 | # Test initial network convergence | |
212 | # | |
213 | def test_isis_adjacencies_step1(): | |
214 | logger.info("Test (step 1): check IS-IS adjacencies") | |
215 | tgen = get_topogen() | |
216 | ||
217 | # Skip if previous fatal error condition is raised | |
218 | if tgen.routers_have_failure(): | |
219 | pytest.skip(tgen.errors) | |
220 | ||
9fa6ec14 | 221 | for rname in ["rt1", "rt2", "rt3", "rt4", "rt5", "rt6", "rt7"]: |
222 | router_compare_json_output( | |
223 | rname, | |
224 | "show yang operational-data /frr-interface:lib isisd", | |
225 | outputs[rname][1]["show_yang_interface_isis_adjacencies.ref"], | |
226 | ) | |
227 | ||
66e5fbe0 RW |
228 | |
229 | def test_rib_ipv6_step1(): | |
230 | logger.info("Test (step 1): verify IPv6 RIB") | |
231 | tgen = get_topogen() | |
232 | ||
233 | # Skip if previous fatal error condition is raised | |
234 | if tgen.routers_have_failure(): | |
235 | pytest.skip(tgen.errors) | |
236 | ||
9fa6ec14 | 237 | for rname in ["rt1", "rt2", "rt3", "rt4", "rt5", "rt6", "rt7"]: |
238 | router_compare_json_output( | |
239 | rname, "show ipv6 route isis json", outputs[rname][1]["show_ipv6_route.ref"] | |
240 | ) | |
241 | ||
66e5fbe0 RW |
242 | |
243 | # | |
244 | # Step 2 | |
245 | # | |
246 | # Action(s): | |
247 | # -Disable LFA protection on all interfaces | |
248 | # | |
249 | # Expected changes: | |
250 | # -rt1 should uninstall all backup nexthops from all routes | |
251 | # | |
252 | def test_rib_ipv6_step2(): | |
253 | logger.info("Test (step 2): verify IPv6 RIB") | |
254 | tgen = get_topogen() | |
255 | ||
256 | # Skip if previous fatal error condition is raised | |
257 | if tgen.routers_have_failure(): | |
258 | pytest.skip(tgen.errors) | |
259 | ||
9fa6ec14 | 260 | logger.info("Disabling LFA protection on all rt1 interfaces") |
261 | tgen.net["rt1"].cmd( | |
262 | 'vtysh -c "conf t" -c "interface eth-rt2" -c "no isis fast-reroute lfa"' | |
263 | ) | |
264 | tgen.net["rt1"].cmd( | |
265 | 'vtysh -c "conf t" -c "interface eth-rt3" -c "no isis fast-reroute lfa"' | |
266 | ) | |
267 | tgen.net["rt1"].cmd( | |
268 | 'vtysh -c "conf t" -c "interface eth-rt4" -c "no isis fast-reroute lfa"' | |
269 | ) | |
270 | tgen.net["rt1"].cmd( | |
271 | 'vtysh -c "conf t" -c "interface eth-rt5" -c "no isis fast-reroute lfa"' | |
272 | ) | |
273 | tgen.net["rt1"].cmd( | |
274 | 'vtysh -c "conf t" -c "interface eth-rt6" -c "no isis fast-reroute lfa"' | |
275 | ) | |
276 | ||
277 | for rname in ["rt1"]: | |
278 | router_compare_json_output( | |
279 | rname, "show ipv6 route isis json", outputs[rname][2]["show_ipv6_route.ref"] | |
280 | ) | |
66e5fbe0 | 281 | |
66e5fbe0 RW |
282 | |
283 | # | |
284 | # Step 3 | |
285 | # | |
286 | # Action(s): | |
287 | # -Re-enable LFA protection on all interfaces | |
288 | # | |
289 | # Expected changes: | |
290 | # -Revert changes from the previous step | |
291 | # | |
292 | def test_rib_ipv6_step3(): | |
293 | logger.info("Test (step 3): verify IPv6 RIB") | |
294 | tgen = get_topogen() | |
295 | ||
296 | # Skip if previous fatal error condition is raised | |
297 | if tgen.routers_have_failure(): | |
298 | pytest.skip(tgen.errors) | |
299 | ||
9fa6ec14 | 300 | logger.info("Re-enabling LFA protection on all rt1 interfaces") |
301 | tgen.net["rt1"].cmd( | |
302 | 'vtysh -c "conf t" -c "interface eth-rt2" -c "isis fast-reroute lfa"' | |
303 | ) | |
304 | tgen.net["rt1"].cmd( | |
305 | 'vtysh -c "conf t" -c "interface eth-rt3" -c "isis fast-reroute lfa"' | |
306 | ) | |
307 | tgen.net["rt1"].cmd( | |
308 | 'vtysh -c "conf t" -c "interface eth-rt4" -c "isis fast-reroute lfa"' | |
309 | ) | |
310 | tgen.net["rt1"].cmd( | |
311 | 'vtysh -c "conf t" -c "interface eth-rt5" -c "isis fast-reroute lfa"' | |
312 | ) | |
313 | tgen.net["rt1"].cmd( | |
314 | 'vtysh -c "conf t" -c "interface eth-rt6" -c "isis fast-reroute lfa"' | |
315 | ) | |
316 | ||
317 | for rname in ["rt1"]: | |
318 | router_compare_json_output( | |
319 | rname, "show ipv6 route isis json", outputs[rname][3]["show_ipv6_route.ref"] | |
320 | ) | |
66e5fbe0 | 321 | |
66e5fbe0 RW |
322 | |
323 | # | |
324 | # Step 4 | |
325 | # | |
326 | # Action(s): | |
327 | # -Disable LFA load-sharing | |
328 | # | |
329 | # Expected changes: | |
330 | # -rt1 should use at most one backup nexthop for each route | |
331 | # | |
332 | def test_rib_ipv6_step4(): | |
333 | logger.info("Test (step 4): verify IPv6 RIB") | |
334 | tgen = get_topogen() | |
335 | ||
336 | # Skip if previous fatal error condition is raised | |
337 | if tgen.routers_have_failure(): | |
338 | pytest.skip(tgen.errors) | |
339 | ||
9fa6ec14 | 340 | logger.info("Disabling LFA load-sharing on rt1") |
341 | tgen.net["rt1"].cmd( | |
342 | 'vtysh -c "conf t" -c "router isis 1" -c "fast-reroute load-sharing disable"' | |
343 | ) | |
344 | ||
345 | for rname in ["rt1"]: | |
346 | router_compare_json_output( | |
347 | rname, "show ipv6 route isis json", outputs[rname][4]["show_ipv6_route.ref"] | |
348 | ) | |
66e5fbe0 | 349 | |
66e5fbe0 RW |
350 | |
351 | # | |
352 | # Step 5 | |
353 | # | |
354 | # Action(s): | |
355 | # -Re-enable LFA load-sharing | |
356 | # | |
357 | # Expected changes: | |
358 | # -Revert changes from the previous step | |
359 | # | |
360 | def test_rib_ipv6_step5(): | |
361 | logger.info("Test (step 5): verify IPv6 RIB") | |
362 | tgen = get_topogen() | |
363 | ||
364 | # Skip if previous fatal error condition is raised | |
365 | if tgen.routers_have_failure(): | |
366 | pytest.skip(tgen.errors) | |
367 | ||
9fa6ec14 | 368 | logger.info("Re-enabling LFA load-sharing on rt1") |
369 | tgen.net["rt1"].cmd( | |
370 | 'vtysh -c "conf t" -c "router isis 1" -c "no fast-reroute load-sharing disable"' | |
371 | ) | |
372 | ||
373 | for rname in ["rt1"]: | |
374 | router_compare_json_output( | |
375 | rname, "show ipv6 route isis json", outputs[rname][5]["show_ipv6_route.ref"] | |
376 | ) | |
66e5fbe0 | 377 | |
66e5fbe0 RW |
378 | |
379 | # | |
380 | # Step 6 | |
381 | # | |
382 | # Action(s): | |
383 | # -Limit backup computation to critical priority prefixes only | |
384 | # | |
385 | # Expected changes: | |
386 | # -rt1 should uninstall all backup nexthops from all routes | |
387 | # | |
388 | def test_rib_ipv6_step6(): | |
389 | logger.info("Test (step 6): verify IPv6 RIB") | |
390 | tgen = get_topogen() | |
391 | ||
392 | # Skip if previous fatal error condition is raised | |
393 | if tgen.routers_have_failure(): | |
394 | pytest.skip(tgen.errors) | |
395 | ||
9fa6ec14 | 396 | logger.info("Limiting backup computation to critical priority prefixes only") |
397 | tgen.net["rt1"].cmd( | |
398 | 'vtysh -c "conf t" -c "router isis 1" -c "fast-reroute priority-limit critical"' | |
399 | ) | |
400 | ||
401 | for rname in ["rt1"]: | |
402 | router_compare_json_output( | |
403 | rname, "show ipv6 route isis json", outputs[rname][6]["show_ipv6_route.ref"] | |
404 | ) | |
66e5fbe0 | 405 | |
66e5fbe0 RW |
406 | |
407 | # | |
408 | # Step 7 | |
409 | # | |
410 | # Action(s): | |
411 | # -Configure a prefix priority list to classify rt7's loopback as a | |
412 | # critical-priority prefix | |
413 | # | |
414 | # Expected changes: | |
415 | # -rt1 should install backup nexthops for rt7's loopback route. | |
416 | # | |
417 | def test_rib_ipv6_step7(): | |
418 | logger.info("Test (step 7): verify IPv6 RIB") | |
419 | tgen = get_topogen() | |
420 | ||
421 | # Skip if previous fatal error condition is raised | |
422 | if tgen.routers_have_failure(): | |
423 | pytest.skip(tgen.errors) | |
424 | ||
9fa6ec14 | 425 | logger.info("Configuring a prefix priority list") |
426 | tgen.net["rt1"].cmd( | |
427 | 'vtysh -c "conf t" -c "router isis 1" -c "spf prefix-priority critical CRITICAL_DESTINATIONS"' | |
428 | ) | |
429 | tgen.net["rt1"].cmd( | |
430 | 'vtysh -c "conf t" -c "ipv6 access-list CRITICAL_DESTINATIONS seq 5 permit 2001:db8:1000::7/128"' | |
431 | ) | |
432 | ||
433 | for rname in ["rt1"]: | |
434 | router_compare_json_output( | |
435 | rname, "show ipv6 route isis json", outputs[rname][7]["show_ipv6_route.ref"] | |
436 | ) | |
66e5fbe0 | 437 | |
66e5fbe0 RW |
438 | |
439 | # | |
440 | # Step 8 | |
441 | # | |
442 | # Action(s): | |
443 | # -Revert previous changes related to prefix priorities | |
444 | # | |
445 | # Expected changes: | |
446 | # -Revert changes from the previous two steps | |
447 | # | |
448 | def test_rib_ipv6_step8(): | |
449 | logger.info("Test (step 8): verify IPv6 RIB") | |
450 | tgen = get_topogen() | |
451 | ||
452 | # Skip if previous fatal error condition is raised | |
453 | if tgen.routers_have_failure(): | |
454 | pytest.skip(tgen.errors) | |
455 | ||
9fa6ec14 | 456 | logger.info("Reverting previous changes related to prefix priorities") |
457 | tgen.net["rt1"].cmd( | |
458 | 'vtysh -c "conf t" -c "no ipv6 access-list CRITICAL_DESTINATIONS seq 5 permit 2001:db8:1000::7/128"' | |
459 | ) | |
460 | tgen.net["rt1"].cmd( | |
461 | 'vtysh -c "conf t" -c "router isis 1" -c "no fast-reroute priority-limit critical"' | |
462 | ) | |
463 | tgen.net["rt1"].cmd( | |
464 | 'vtysh -c "conf t" -c "router isis 1" -c "no spf prefix-priority critical CRITICAL_DESTINATIONS"' | |
465 | ) | |
466 | ||
467 | for rname in ["rt1"]: | |
468 | router_compare_json_output( | |
469 | rname, "show ipv6 route isis json", outputs[rname][8]["show_ipv6_route.ref"] | |
470 | ) | |
66e5fbe0 | 471 | |
66e5fbe0 RW |
472 | |
473 | # | |
474 | # Step 9 | |
475 | # | |
476 | # Action(s): | |
477 | # -Exclude eth-rt6 from LFA computation for eth-rt2's failure | |
478 | # | |
479 | # Expected changes: | |
480 | # -Uninstall the eth-rt2 protecting backup nexthops that go through eth-rt6 | |
481 | # | |
482 | def test_rib_ipv6_step9(): | |
483 | logger.info("Test (step 9): verify IPv6 RIB") | |
484 | tgen = get_topogen() | |
485 | ||
486 | # Skip if previous fatal error condition is raised | |
487 | if tgen.routers_have_failure(): | |
488 | pytest.skip(tgen.errors) | |
489 | ||
9fa6ec14 | 490 | logger.info("Excluding eth-rt6 from LFA computation for eth-rt2's failure") |
491 | tgen.net["rt1"].cmd( | |
492 | 'vtysh -c "conf t" -c "interface eth-rt2" -c "isis fast-reroute lfa exclude interface eth-rt6"' | |
493 | ) | |
494 | ||
495 | for rname in ["rt1"]: | |
496 | router_compare_json_output( | |
497 | rname, "show ipv6 route isis json", outputs[rname][9]["show_ipv6_route.ref"] | |
498 | ) | |
66e5fbe0 | 499 | |
66e5fbe0 RW |
500 | |
501 | # | |
502 | # Step 10 | |
503 | # | |
504 | # Action(s): | |
505 | # -Remove exclusion of eth-rt6 from LFA computation for eth-rt2's failure | |
506 | # | |
507 | # Expected changes: | |
508 | # -Revert changes from the previous step | |
509 | # | |
510 | def test_rib_ipv6_step10(): | |
511 | logger.info("Test (step 10): verify IPv6 RIB") | |
512 | tgen = get_topogen() | |
513 | ||
514 | # Skip if previous fatal error condition is raised | |
515 | if tgen.routers_have_failure(): | |
516 | pytest.skip(tgen.errors) | |
517 | ||
9fa6ec14 | 518 | logger.info( |
519 | "Removing exclusion of eth-rt6 from LFA computation for eth-rt2's failure" | |
520 | ) | |
521 | tgen.net["rt1"].cmd( | |
522 | 'vtysh -c "conf t" -c "interface eth-rt2" -c "no isis fast-reroute lfa exclude interface eth-rt6"' | |
523 | ) | |
524 | ||
525 | for rname in ["rt1"]: | |
526 | router_compare_json_output( | |
527 | rname, | |
528 | "show ipv6 route isis json", | |
529 | outputs[rname][10]["show_ipv6_route.ref"], | |
530 | ) | |
66e5fbe0 | 531 | |
66e5fbe0 RW |
532 | |
533 | # | |
534 | # Step 11 | |
535 | # | |
536 | # Action(s): | |
537 | # -Add LFA tiebreaker: prefer node protecting backup path | |
538 | # | |
539 | # Expected changes: | |
540 | # -rt1 should prefer backup nexthops that provide node protection | |
541 | # | |
542 | def test_rib_ipv6_step11(): | |
543 | logger.info("Test (step 11): verify IPv6 RIB") | |
544 | tgen = get_topogen() | |
545 | ||
546 | # Skip if previous fatal error condition is raised | |
547 | if tgen.routers_have_failure(): | |
548 | pytest.skip(tgen.errors) | |
549 | ||
9fa6ec14 | 550 | logger.info("Adding LFA tiebreaker: prefer node protecting backup path") |
551 | tgen.net["rt1"].cmd( | |
552 | 'vtysh -c "conf t" -c "router isis 1" -c "fast-reroute lfa tiebreaker node-protecting index 10"' | |
553 | ) | |
554 | ||
555 | for rname in ["rt1"]: | |
556 | router_compare_json_output( | |
557 | rname, | |
558 | "show ipv6 route isis json", | |
559 | outputs[rname][11]["show_ipv6_route.ref"], | |
560 | ) | |
66e5fbe0 | 561 | |
66e5fbe0 RW |
562 | |
563 | # | |
564 | # Step 12 | |
565 | # | |
566 | # Action(s): | |
567 | # -Add LFA tiebreaker: prefer backup path via downstream node | |
568 | # | |
569 | # Expected changes: | |
570 | # -rt1 should prefer backup nexthops that satisfy the downstream condition | |
571 | # | |
572 | def test_rib_ipv6_step12(): | |
573 | logger.info("Test (step 12): verify IPv6 RIB") | |
574 | tgen = get_topogen() | |
575 | ||
576 | # Skip if previous fatal error condition is raised | |
577 | if tgen.routers_have_failure(): | |
578 | pytest.skip(tgen.errors) | |
579 | ||
9fa6ec14 | 580 | logger.info("Adding LFA tiebreaker: prefer backup path via downstream node") |
581 | tgen.net["rt1"].cmd( | |
582 | 'vtysh -c "conf t" -c "router isis 1" -c "fast-reroute lfa tiebreaker downstream index 20"' | |
583 | ) | |
584 | ||
585 | for rname in ["rt1"]: | |
586 | router_compare_json_output( | |
587 | rname, | |
588 | "show ipv6 route isis json", | |
589 | outputs[rname][12]["show_ipv6_route.ref"], | |
590 | ) | |
66e5fbe0 | 591 | |
66e5fbe0 RW |
592 | |
593 | # | |
594 | # Step 13 | |
595 | # | |
596 | # Action(s): | |
597 | # -Add LFA tiebreaker: prefer backup path with lowest total metric | |
598 | # | |
599 | # Expected changes: | |
600 | # -rt1 should prefer backup nexthops that have the best metric | |
601 | # | |
602 | def test_rib_ipv6_step13(): | |
603 | logger.info("Test (step 13): verify IPv6 RIB") | |
604 | tgen = get_topogen() | |
605 | ||
606 | # Skip if previous fatal error condition is raised | |
607 | if tgen.routers_have_failure(): | |
608 | pytest.skip(tgen.errors) | |
609 | ||
9fa6ec14 | 610 | logger.info("Adding LFA tiebreaker: prefer backup path with lowest total metric") |
611 | tgen.net["rt1"].cmd( | |
612 | 'vtysh -c "conf t" -c "router isis 1" -c "fast-reroute lfa tiebreaker lowest-backup-metric index 30"' | |
613 | ) | |
614 | ||
615 | for rname in ["rt1"]: | |
616 | router_compare_json_output( | |
617 | rname, | |
618 | "show ipv6 route isis json", | |
619 | outputs[rname][13]["show_ipv6_route.ref"], | |
620 | ) | |
66e5fbe0 | 621 | |
66e5fbe0 | 622 | |
f4d0de10 LS |
623 | # |
624 | # Step 14 | |
625 | # | |
626 | # Action(s): | |
627 | # - Setting spf-delay-ietf init-delay of 15s | |
628 | # | |
629 | # Expected changes: | |
630 | # - No routing table change | |
631 | # - At the end of test, SPF reacts to a failure in 15s | |
632 | # | |
633 | def test_rib_ipv6_step14(): | |
634 | logger.info("Test (step 14): verify IPv6 RIB") | |
635 | tgen = get_topogen() | |
636 | ||
637 | # Skip if previous fatal error condition is raised | |
638 | if tgen.routers_have_failure(): | |
639 | pytest.skip(tgen.errors) | |
640 | ||
641 | logger.info("Setting spf-delay-ietf init-delay of 15s") | |
642 | tgen.net["rt1"].cmd( | |
643 | 'vtysh -c "conf t" -c "router isis 1" -c "spf-delay-ietf init-delay 15000 short-delay 0 long-delay 0 holddown 0 time-to-learn 0"' | |
644 | ) | |
645 | ||
646 | for rname in ["rt1"]: | |
647 | router_compare_json_output( | |
648 | rname, | |
649 | "show ipv6 route isis json", | |
650 | outputs[rname][14]["show_ipv6_route.ref"], | |
651 | ) | |
652 | ||
653 | ||
654 | # | |
655 | # Step 15 | |
656 | # | |
657 | # Action(s): | |
658 | # - shut the eth-rt2 interface on rt1 | |
659 | # | |
660 | # Expected changes: | |
661 | # - Route switchover of routes via eth-rt2 | |
662 | # | |
663 | def test_rib_ipv6_step15(): | |
664 | logger.info("Test (step 15): verify IPv6 RIB") | |
665 | tgen = get_topogen() | |
666 | ||
667 | # Skip if previous fatal error condition is raised | |
668 | if tgen.routers_have_failure(): | |
669 | pytest.skip(tgen.errors) | |
670 | ||
671 | logger.info("Shut the interface to rt2 from the switch side and check fast-reroute") | |
672 | tgen.net.cmd_raises("ip link set %s down" % tgen.net["s1"].intfs[0]) | |
673 | ||
674 | for rname in ["rt1"]: | |
675 | router_compare_json_output( | |
676 | rname, | |
677 | "show ipv6 route isis json", | |
678 | outputs[rname][15]["show_ipv6_route.ref"], | |
c6653ab2 DS |
679 | count=10, |
680 | wait=0.5, | |
f4d0de10 LS |
681 | ) |
682 | ||
683 | ||
684 | # | |
685 | # Step 16 | |
686 | # | |
687 | # Action(s): wait for the convergence and SPF computation on rt1 | |
688 | # | |
689 | # Expected changes: | |
690 | # - convergence of IPv6 RIB | |
691 | # | |
692 | def test_rib_ipv6_step16(): | |
693 | logger.info("Test (step 16): verify IPv6 RIB") | |
694 | tgen = get_topogen() | |
695 | ||
696 | # Skip if previous fatal error condition is raised | |
697 | if tgen.routers_have_failure(): | |
698 | pytest.skip(tgen.errors) | |
699 | ||
700 | logger.info("Check SPF convergence") | |
701 | ||
702 | for rname in ["rt1"]: | |
703 | router_compare_json_output( | |
704 | rname, | |
705 | "show ipv6 route isis json", | |
706 | outputs[rname][16]["show_ipv6_route.ref"], | |
707 | ) | |
708 | ||
709 | ||
e87245d0 LS |
710 | # |
711 | # Step 17 | |
712 | # | |
713 | # Action(s): | |
714 | # - Unshut the interface to rt2 from the switch sid | |
715 | # | |
716 | # Expected changes: | |
717 | # - The routing table converges | |
718 | # | |
719 | def test_rib_ipv6_step17(): | |
720 | logger.info("Test (step 17): verify IPv6 RIB") | |
721 | tgen = get_topogen() | |
722 | ||
723 | # Skip if previous fatal error condition is raised | |
724 | if tgen.routers_have_failure(): | |
725 | pytest.skip(tgen.errors) | |
726 | ||
727 | rname = "rt1" | |
728 | ||
729 | logger.info("Unsetting spf-delay-ietf init-delay of 15s") | |
730 | tgen.net[rname].cmd('vtysh -c "conf t" -c "router isis 1" -c "no spf-delay-ietf"') | |
731 | ||
732 | logger.info( | |
733 | "Unshut the interface to rt2 from the switch side and check fast-reroute" | |
734 | ) | |
735 | tgen.net.cmd_raises("ip link set %s up" % tgen.net["s1"].intfs[0]) | |
736 | ||
737 | logger.info("Setting spf-delay-ietf init-delay of 15s") | |
738 | tgen.net[rname].cmd( | |
739 | 'vtysh -c "conf t" -c "router isis 1" -c "spf-delay-ietf init-delay 15000 short-delay 0 long-delay 0 holddown 0 time-to-learn 0"' | |
740 | ) | |
741 | ||
742 | router_compare_json_output( | |
743 | rname, | |
744 | "show ipv6 route isis json", | |
745 | outputs[rname][14]["show_ipv6_route.ref"], | |
746 | ) | |
747 | ||
748 | ||
749 | # | |
750 | # Step 18 | |
751 | # | |
752 | # Action(s): | |
753 | # - drop traffic between rt1 and rt2 by shutting down the bridge between | |
754 | # the routers. Interfaces on rt1 and rt2 stay up. | |
755 | # | |
756 | # | |
757 | # Expected changes: | |
758 | # - Route switchover of routes via eth-rt2 | |
759 | # | |
760 | def test_rib_ipv6_step18(): | |
f2393c75 LS |
761 | def _rt2_neigh_down(router): |
762 | output = json.loads(router.vtysh_cmd("show isis neighbor rt2 json")) | |
763 | ||
764 | """ | |
765 | Previous output was: | |
766 | { | |
767 | "areas":[ | |
768 | { | |
769 | "area":"1", | |
770 | "circuits":[ | |
771 | { | |
772 | "circuit":0, | |
773 | "adj":"rt2", | |
774 | "interface":{ | |
775 | "name":"eth-rt2", | |
776 | "state":"Up", | |
777 | "adj-flaps":1, | |
778 | "last-ago":"21s", | |
779 | "circuit-type":"L1", | |
780 | "speaks":"IPv6", | |
781 | "topologies":{ | |
782 | "topo-0":"ipv6-unicast" | |
783 | }, | |
784 | "snpa":"2020.2020.2020", | |
785 | "area-address":{ | |
786 | "isonet":"49.0000" | |
787 | }, | |
788 | "ipv6-link-local":{ | |
789 | "ipv6":"fe80::ac19:a8ff:fee5:f48f" | |
790 | }, | |
791 | "adj-sid":{ | |
792 | } | |
793 | }, | |
794 | "level":1, | |
795 | "expires-in":"2s" | |
796 | }, | |
797 | { | |
798 | "circuit":0 | |
799 | }, | |
800 | { | |
801 | "circuit":0 | |
802 | }, | |
803 | { | |
804 | "circuit":0 | |
805 | }, | |
806 | { | |
807 | "circuit":0 | |
808 | }, | |
809 | { | |
810 | "circuit":0 | |
811 | } | |
812 | ] | |
813 | } | |
814 | ] | |
815 | """ | |
816 | ||
817 | expected = { | |
818 | "areas": [ | |
819 | { | |
820 | "area": "1", | |
821 | "circuits": [ | |
822 | {"circuit": 0}, | |
823 | {"circuit": 0}, | |
824 | {"circuit": 0}, | |
825 | {"circuit": 0}, | |
826 | {"circuit": 0}, | |
827 | {"circuit": 0}, | |
828 | ], | |
829 | } | |
830 | ] | |
831 | } | |
832 | ||
833 | return topotest.json_cmp(output, expected, exact=True) | |
834 | ||
e87245d0 LS |
835 | logger.info("Test (step 18): verify IPv6 RIB") |
836 | tgen = get_topogen() | |
837 | ||
838 | # Skip if previous fatal error condition is raised | |
839 | if tgen.routers_have_failure(): | |
840 | pytest.skip(tgen.errors) | |
841 | ||
842 | logger.info("Drop traffic between rt1 and rt2") | |
843 | tgen.net.cmd_raises("ip link set s1 down") | |
844 | ||
845 | rname = "rt1" | |
f2393c75 LS |
846 | router = tgen.gears[rname] |
847 | test_func = partial(_rt2_neigh_down, router) | |
848 | success, result = topotest.run_and_expect(test_func, None, count=200, wait=0.05) | |
849 | assert result is None, 'rt2 neighbor is still present on "{}"'.format(router) | |
e87245d0 LS |
850 | |
851 | router_compare_json_output( | |
852 | rname, | |
853 | "show ipv6 route isis json", | |
854 | outputs[rname][15]["show_ipv6_route.ref"], | |
c6653ab2 DS |
855 | count=10, |
856 | wait=0.5, | |
e87245d0 LS |
857 | ) |
858 | ||
859 | ||
860 | # | |
861 | # Step 19 | |
862 | # | |
863 | # Action(s): wait for the convergence and SPF computation on rt1 | |
864 | # | |
865 | # Expected changes: | |
866 | # - convergence of IPv6 RIB | |
867 | # | |
868 | def test_rib_ipv6_step19(): | |
869 | logger.info("Test (step 19): verify IPv6 RIB") | |
870 | tgen = get_topogen() | |
871 | ||
872 | # Skip if previous fatal error condition is raised | |
873 | if tgen.routers_have_failure(): | |
874 | pytest.skip(tgen.errors) | |
875 | ||
876 | logger.info("Check SPF convergence") | |
877 | ||
878 | for rname in ["rt1"]: | |
879 | router_compare_json_output( | |
880 | rname, | |
881 | "show ipv6 route isis json", | |
882 | outputs[rname][16]["show_ipv6_route.ref"], | |
883 | ) | |
884 | ||
885 | ||
3a03bf9f LS |
886 | # |
887 | # Step 20 | |
888 | # | |
889 | # Action(s): | |
890 | # - Unshut the switch from rt1 to rt2 | |
891 | # | |
892 | # Expected changes: | |
893 | # - The routing table goes back to the nominal state | |
894 | # | |
895 | def test_rib_ipv6_step20(): | |
896 | logger.info("Test (step 20): verify IPv6 RIB") | |
897 | tgen = get_topogen() | |
898 | ||
899 | # Skip if previous fatal error condition is raised | |
900 | if tgen.routers_have_failure(): | |
901 | pytest.skip(tgen.errors) | |
902 | ||
903 | rname = "rt1" | |
904 | ||
905 | logger.info("Unsetting spf-delay-ietf init-delay of 15s") | |
906 | tgen.net[rname].cmd('vtysh -c "conf t" -c "router isis 1" -c "no spf-delay-ietf"') | |
907 | ||
908 | logger.info( | |
909 | "Unshut the interface to rt2 from the switch side and check fast-reroute" | |
910 | ) | |
911 | tgen.net.cmd_raises("ip link set s1 up") | |
912 | ||
913 | logger.info("Setting spf-delay-ietf init-delay of 15s") | |
914 | tgen.net[rname].cmd( | |
915 | 'vtysh -c "conf t" -c "router isis 1" -c "spf-delay-ietf init-delay 15000 short-delay 0 long-delay 0 holddown 0 time-to-learn 0"' | |
916 | ) | |
917 | ||
918 | router_compare_json_output( | |
919 | rname, | |
920 | "show ipv6 route isis json", | |
921 | outputs[rname][14]["show_ipv6_route.ref"], | |
922 | ) | |
923 | ||
924 | ||
925 | # | |
926 | # Step 21 | |
927 | # | |
928 | # Action(s): | |
929 | # - clear the rt2 ISIS neighbor on rt1 | |
930 | # | |
931 | # Expected changes: | |
932 | # - Route switchover of routes via eth-rt2 | |
933 | # | |
934 | def test_rib_ipv6_step21(): | |
935 | logger.info("Test (step 21): verify IPv6 RIB") | |
936 | tgen = get_topogen() | |
937 | ||
938 | # Skip if previous fatal error condition is raised | |
939 | if tgen.routers_have_failure(): | |
940 | pytest.skip(tgen.errors) | |
941 | ||
942 | rname = "rt1" | |
943 | ||
944 | logger.info("Clear the rt2 ISIS neighbor on rt1 and check fast-reroute") | |
945 | tgen.gears[rname].vtysh_cmd("clear isis neighbor rt2") | |
946 | ||
947 | router_compare_json_output( | |
948 | rname, | |
949 | "show ipv6 route isis json", | |
950 | outputs[rname][15]["show_ipv6_route.ref"], | |
c6653ab2 DS |
951 | count=10, |
952 | wait=0.5, | |
3a03bf9f LS |
953 | ) |
954 | ||
955 | ||
956 | # | |
957 | # Step 22 | |
958 | # | |
959 | # Action(s): wait for the convergence and SPF computation on rt1 | |
960 | # | |
961 | # Expected changes: | |
962 | # - convergence of IPv6 RIB | |
963 | # | |
964 | def test_rib_ipv6_step22(): | |
965 | logger.info("Test (step 22): verify IPv6 RIB") | |
966 | tgen = get_topogen() | |
967 | ||
968 | # Skip if previous fatal error condition is raised | |
969 | if tgen.routers_have_failure(): | |
970 | pytest.skip(tgen.errors) | |
971 | ||
972 | logger.info("Check SPF convergence") | |
973 | ||
974 | for rname in ["rt1"]: | |
975 | router_compare_json_output( | |
976 | rname, | |
977 | "show ipv6 route isis json", | |
978 | outputs[rname][16]["show_ipv6_route.ref"], | |
979 | ) | |
980 | ||
981 | ||
1d5185ec LS |
982 | # |
983 | # Step 23 | |
984 | # | |
985 | # Action(s): | |
986 | # - Setting BFD | |
987 | # | |
988 | # Expected changes: | |
989 | # - No routing table change | |
990 | # - BFD comes up | |
991 | # | |
992 | def test_rib_ipv6_step23(): | |
993 | logger.info("Test (step 23): verify IPv6 RIB") | |
994 | tgen = get_topogen() | |
995 | ||
996 | # Skip if previous fatal error condition is raised | |
997 | if tgen.routers_have_failure(): | |
998 | pytest.skip(tgen.errors) | |
999 | ||
1000 | logger.info("Setup BFD on rt1 and rt2") | |
1001 | for rname in ["rt1", "rt2"]: | |
1002 | conf_file = os.path.join(CWD, "{}/bfdd.conf".format(rname)) | |
1003 | tgen.net[rname].cmd("vtysh -f {}".format(conf_file)) | |
1004 | ||
1d5185ec LS |
1005 | logger.info("Set ISIS BFD") |
1006 | tgen.net["rt1"].cmd('vtysh -c "conf t" -c "int eth-rt2" -c "isis bfd"') | |
1007 | tgen.net["rt2"].cmd('vtysh -c "conf t" -c "int eth-rt1" -c "isis bfd"') | |
1008 | ||
17e8fa83 LS |
1009 | rname = "rt1" |
1010 | expect = '[{"multihop":false,"interface":"eth-rt2","status":"up"}]' | |
1011 | router_compare_json_output(rname, "show bfd peers json", expect) | |
1012 | ||
1d5185ec LS |
1013 | router_compare_json_output( |
1014 | rname, | |
1015 | "show ipv6 route isis json", | |
1016 | outputs[rname][14]["show_ipv6_route.ref"], | |
1017 | ) | |
1018 | ||
1019 | ||
1020 | # | |
1021 | # Step 24 | |
1022 | # | |
1023 | # Action(s): | |
1024 | # - drop traffic between rt1 and rt2 by shutting down the bridge between | |
1025 | # the routers. Interfaces on rt1 and rt2 stay up. | |
1026 | # | |
1027 | # Expected changes: | |
1028 | # - BFD comes down before IS-IS | |
1029 | # - Route switchover of routes via eth-rt2 | |
1030 | # | |
1031 | def test_rib_ipv6_step24(): | |
17e8fa83 LS |
1032 | def _bfd_down(router): |
1033 | output = json.loads(router.vtysh_cmd("show bfd peers json")) | |
1034 | expected = [] | |
1035 | return topotest.json_cmp(output, expected, exact=True) | |
1036 | ||
1d5185ec LS |
1037 | logger.info("Test (step 24): verify IPv6 RIB") |
1038 | tgen = get_topogen() | |
1039 | ||
1040 | # Skip if previous fatal error condition is raised | |
1041 | if tgen.routers_have_failure(): | |
1042 | pytest.skip(tgen.errors) | |
1043 | ||
1044 | logger.info("Shut the interface to rt2 from the switch side and check fast-reroute") | |
1045 | tgen.net.cmd_raises("ip link set s1 down") | |
1046 | ||
1047 | rname = "rt1" | |
17e8fa83 LS |
1048 | router = tgen.gears[rname] |
1049 | test_func = partial(_bfd_down, router) | |
c6653ab2 | 1050 | success, result = topotest.run_and_expect(test_func, None, count=30, wait=0.3) |
17e8fa83 | 1051 | assert result is None, 'BFD session is still up on "{}"'.format(router) |
1d5185ec LS |
1052 | |
1053 | router_compare_json_output( | |
1054 | rname, | |
1055 | "show ipv6 route isis json", | |
1056 | outputs[rname][15]["show_ipv6_route.ref"], | |
d3a6af08 | 1057 | count=10, |
1d5185ec LS |
1058 | ) |
1059 | ||
1060 | ||
1061 | # | |
1062 | # Step 25 | |
1063 | # | |
1064 | # Action(s): wait for the convergence and SPF computation on rt1 | |
1065 | # | |
1066 | # Expected changes: | |
1067 | # - convergence of IPv6 RIB | |
1068 | # | |
1069 | def test_rib_ipv6_step25(): | |
1070 | logger.info("Test (step 25): verify IPv6 RIB") | |
1071 | tgen = get_topogen() | |
1072 | ||
1073 | # Skip if previous fatal error condition is raised | |
1074 | if tgen.routers_have_failure(): | |
1075 | pytest.skip(tgen.errors) | |
1076 | ||
1077 | logger.info("Check SPF convergence") | |
1078 | ||
1079 | for rname in ["rt1"]: | |
1080 | router_compare_json_output( | |
1081 | rname, | |
1082 | "show ipv6 route isis json", | |
1083 | outputs[rname][16]["show_ipv6_route.ref"], | |
1084 | ) | |
1085 | ||
1086 | ||
66e5fbe0 RW |
1087 | # Memory leak test template |
1088 | def test_memory_leak(): | |
1089 | "Run the memory leak test and report results." | |
1090 | tgen = get_topogen() | |
1091 | if not tgen.is_memleak_enabled(): | |
9fa6ec14 | 1092 | pytest.skip("Memory leak test/report is disabled") |
66e5fbe0 RW |
1093 | |
1094 | tgen.report_memory_leaks() | |
1095 | ||
9fa6ec14 | 1096 | |
1097 | if __name__ == "__main__": | |
66e5fbe0 RW |
1098 | args = ["-s"] + sys.argv[1:] |
1099 | sys.exit(pytest.main(args)) |