]> git.proxmox.com Git - mirror_frr.git/blob - tests/topotests/ospf6_topo2/test_ospf6_topo2.py
Merge pull request #13649 from donaldsharp/unlock_the_node_or_else
[mirror_frr.git] / tests / topotests / ospf6_topo2 / test_ospf6_topo2.py
1 #!/usr/bin/env python
2 # SPDX-License-Identifier: ISC
3
4 #
5 # test_ospf6_topo2.py
6 # Part of NetDEF Topology Tests
7 #
8 # Copyright (c) 2021 by
9 # Network Device Education Foundation, Inc. ("NetDEF")
10 #
11
12 """
13 test_ospf6_topo2.py: Test the FRR OSPFv3 daemon.
14 """
15
16 import os
17 import sys
18 from functools import partial
19 import pytest
20
21 # Save the Current Working Directory to find configuration files.
22 CWD = os.path.dirname(os.path.realpath(__file__))
23 sys.path.append(os.path.join(CWD, "../"))
24
25 # pylint: disable=C0413
26 # Import topogen and topotest helpers
27 from lib import topotest
28 from lib.topogen import Topogen, TopoRouter, get_topogen
29 from lib.topolog import logger
30
31 # Required to instantiate the topology builder class.
32
33 pytestmark = [pytest.mark.ospf6d]
34
35
36 def expect_lsas(router, area, lsas, wait=5, extra_params=""):
37 """
38 Run the OSPFv3 show LSA database command and expect the supplied LSAs.
39
40 Optional parameters:
41 * `wait`: amount of seconds to wait.
42 * `extra_params`: extra LSA database parameters.
43 * `inverse`: assert the inverse of the expected.
44 """
45 tgen = get_topogen()
46
47 command = "show ipv6 ospf6 database {} json".format(extra_params)
48
49 logger.info("waiting OSPFv3 router '{}' LSA".format(router))
50 test_func = partial(
51 topotest.router_json_cmp,
52 tgen.gears[router],
53 command,
54 {"areaScopedLinkStateDb": [{"areaId": area, "lsa": lsas}]},
55 )
56 _, result = topotest.run_and_expect(test_func, None, count=wait, wait=1)
57 assertmsg = '"{}" convergence failure'.format(router)
58
59 assert result is None, assertmsg
60
61
62 def expect_ospfv3_routes(router, routes, wait=5, type=None, detail=False):
63 "Run command `ipv6 ospf6 route` and expect route with type."
64 tgen = get_topogen()
65
66 if detail == False:
67 if type == None:
68 cmd = "show ipv6 ospf6 route json"
69 else:
70 cmd = "show ipv6 ospf6 route {} json".format(type)
71 else:
72 if type == None:
73 cmd = "show ipv6 ospf6 route detail json"
74 else:
75 cmd = "show ipv6 ospf6 route {} detail json".format(type)
76
77 logger.info("waiting OSPFv3 router '{}' route".format(router))
78 test_func = partial(
79 topotest.router_json_cmp, tgen.gears[router], cmd, {"routes": routes}
80 )
81 _, result = topotest.run_and_expect(test_func, None, count=wait, wait=1)
82 assertmsg = '"{}" convergence failure'.format(router)
83
84 assert result is None, assertmsg
85
86
87 def dont_expect_route(router, unexpected_route, type=None):
88 "Specialized test function to expect route go missing"
89 tgen = get_topogen()
90
91 if type == None:
92 cmd = "show ipv6 ospf6 route json"
93 else:
94 cmd = "show ipv6 ospf6 route {} json".format(type)
95
96 output = tgen.gears[router].vtysh_cmd(cmd, isjson=True)
97 if unexpected_route in output["routes"]:
98 return output["routes"][unexpected_route]
99 return None
100
101
102 def build_topo(tgen):
103 "Build function"
104
105 # Create 4 routers
106 for routern in range(1, 5):
107 tgen.add_router("r{}".format(routern))
108
109 switch = tgen.add_switch("s1")
110 switch.add_link(tgen.gears["r1"])
111 switch.add_link(tgen.gears["r2"])
112
113 switch = tgen.add_switch("s2")
114 switch.add_link(tgen.gears["r2"])
115 switch.add_link(tgen.gears["r3"])
116
117 switch = tgen.add_switch("s3")
118 switch.add_link(tgen.gears["r2"])
119 switch.add_link(tgen.gears["r4"])
120
121 switch = tgen.add_switch("s4")
122 switch.add_link(tgen.gears["r4"], nodeif="r4-stubnet")
123
124
125 def setup_module(mod):
126 "Sets up the pytest environment"
127 tgen = Topogen(build_topo, mod.__name__)
128 tgen.start_topology()
129
130 router_list = tgen.routers()
131 for rname, router in router_list.items():
132
133 daemon_file = "{}/{}/zebra.conf".format(CWD, rname)
134 if os.path.isfile(daemon_file):
135 router.load_config(TopoRouter.RD_ZEBRA, daemon_file)
136
137 daemon_file = "{}/{}/ospf6d.conf".format(CWD, rname)
138 if os.path.isfile(daemon_file):
139 router.load_config(TopoRouter.RD_OSPF6, daemon_file)
140
141 # Initialize all routers.
142 tgen.start_router()
143
144
145 def test_wait_protocol_convergence():
146 "Wait for OSPFv3 to converge"
147 tgen = get_topogen()
148 if tgen.routers_have_failure():
149 pytest.skip(tgen.errors)
150
151 logger.info("waiting for protocols to converge")
152
153 def expect_neighbor_full(router, neighbor):
154 "Wait until OSPFv3 convergence."
155 logger.info("waiting OSPFv3 router '{}'".format(router))
156 test_func = partial(
157 topotest.router_json_cmp,
158 tgen.gears[router],
159 "show ipv6 ospf6 neighbor json",
160 {"neighbors": [{"neighborId": neighbor, "state": "Full"}]},
161 )
162 _, result = topotest.run_and_expect(test_func, None, count=130, wait=1)
163 assertmsg = '"{}" convergence failure'.format(router)
164 assert result is None, assertmsg
165
166 expect_neighbor_full("r1", "10.254.254.2")
167 expect_neighbor_full("r2", "10.254.254.1")
168 expect_neighbor_full("r2", "10.254.254.3")
169 expect_neighbor_full("r2", "10.254.254.4")
170 expect_neighbor_full("r3", "10.254.254.2")
171 expect_neighbor_full("r4", "10.254.254.2")
172
173
174 def test_ospfv3_expected_route_types():
175 "Test routers route type to determine if NSSA/Stub is working as expected."
176 tgen = get_topogen()
177 if tgen.routers_have_failure():
178 pytest.skip(tgen.errors)
179
180 logger.info("waiting for protocols to converge")
181
182 def expect_ospf6_route_types(router, expected_summary):
183 "Expect the correct route types."
184 logger.info("waiting OSPFv3 router '{}'".format(router))
185 test_func = partial(
186 topotest.router_json_cmp,
187 tgen.gears[router],
188 "show ipv6 ospf6 route summary json",
189 expected_summary,
190 )
191 _, result = topotest.run_and_expect(test_func, None, count=10, wait=1)
192 assertmsg = '"{}" convergence failure'.format(router)
193 assert result is None, assertmsg
194
195 # Stub router: no external routes.
196 expect_ospf6_route_types(
197 "r1",
198 {
199 "numberOfIntraAreaRoutes": 1,
200 "numberOfInterAreaRoutes": 3,
201 "numberOfExternal1Routes": 0,
202 "numberOfExternal2Routes": 0,
203 },
204 )
205 # NSSA router: no external routes.
206 expect_ospf6_route_types(
207 "r4",
208 {
209 "numberOfIntraAreaRoutes": 1,
210 "numberOfInterAreaRoutes": 2,
211 "numberOfExternal1Routes": 0,
212 "numberOfExternal2Routes": 3,
213 },
214 )
215
216
217 def test_ospf6_default_route():
218 "Wait for OSPFv3 default route in stub area."
219 tgen = get_topogen()
220 if tgen.routers_have_failure():
221 pytest.skip(tgen.errors)
222
223 logger.info("waiting for default route")
224
225 def expect_route(router, route, metric):
226 "Test OSPF6 route existence."
227 logger.info("waiting OSPFv3 router '{}' routes".format(router))
228 test_func = partial(
229 topotest.router_json_cmp,
230 tgen.gears[router],
231 "show ipv6 route json",
232 {route: [{"metric": metric}]},
233 )
234 _, result = topotest.run_and_expect(test_func, None, count=5, wait=1)
235 assertmsg = '"{}" convergence failure'.format(router)
236 assert result is None, assertmsg
237
238 metric = 123
239 expect_lsas(
240 "r1",
241 "0.0.0.1",
242 [{"prefix": "::/0", "metric": metric}],
243 extra_params="inter-prefix detail",
244 )
245 expect_route("r1", "::/0", metric + 10)
246
247
248 def test_redistribute_metrics():
249 """
250 Test that the configured metrics are honored when a static route is
251 redistributed.
252 """
253 tgen = get_topogen()
254 if tgen.routers_have_failure():
255 pytest.skip(tgen.errors)
256
257 # Add new static route on r3.
258 config = """
259 configure terminal
260 ipv6 route 2001:db8:500::/64 Null0
261 """
262 tgen.gears["r3"].vtysh_cmd(config)
263
264 route = {
265 "2001:db8:500::/64": {
266 "metricType": 2,
267 "metricCost": 10,
268 }
269 }
270 logger.info(
271 "Expecting AS-external route 2001:db8:500::/64 to show up with default metrics"
272 )
273 expect_ospfv3_routes("r2", route, wait=30, detail=True)
274
275 # Change the metric of redistributed routes of the static type on r3.
276 config = """
277 configure terminal
278 router ospf6
279 redistribute static metric 50 metric-type 1
280 """
281 tgen.gears["r3"].vtysh_cmd(config)
282
283 # Check if r3 reinstalled 2001:db8:500::/64 using the new metric type and value.
284 route = {
285 "2001:db8:500::/64": {
286 "metricType": 1,
287 "metricCost": 60,
288 }
289 }
290 logger.info(
291 "Expecting AS-external route 2001:db8:500::/64 to show up with updated metric type and value"
292 )
293 expect_ospfv3_routes("r2", route, wait=30, detail=True)
294
295
296 def test_nssa_lsa_type7():
297 """
298 Test that static route gets announced as external route when redistributed
299 and gets removed when redistribution stops.
300 """
301 tgen = get_topogen()
302 if tgen.routers_have_failure():
303 pytest.skip(tgen.errors)
304
305 #
306 # Add new static route and check if it gets announced as LSA Type-7.
307 #
308 config = """
309 configure terminal
310 ipv6 route 2001:db8:100::/64 Null0
311 """
312 tgen.gears["r2"].vtysh_cmd(config)
313
314 lsas = [
315 {
316 "type": "NSSA",
317 "advertisingRouter": "10.254.254.2",
318 "prefix": "2001:db8:100::/64",
319 "forwardingAddress": "2001:db8:3::1",
320 }
321 ]
322 route = {
323 "2001:db8:100::/64": {
324 "pathType": "E2",
325 "nextHops": [{"nextHop": "::", "interfaceName": "r4-eth0"}],
326 }
327 }
328
329 logger.info("Expecting LSA type-7 and OSPFv3 route 2001:db8:100::/64 to show up")
330 expect_lsas("r4", "0.0.0.2", lsas, wait=30, extra_params="type-7 detail")
331 expect_ospfv3_routes("r4", route, wait=30)
332
333 #
334 # Remove static route and check for LSA Type-7 removal.
335 #
336 config = """
337 configure terminal
338 no ipv6 route 2001:db8:100::/64 Null0
339 """
340 tgen.gears["r2"].vtysh_cmd(config)
341
342 def dont_expect_lsa(unexpected_lsa):
343 "Specialized test function to expect LSA go missing"
344 output = tgen.gears["r4"].vtysh_cmd(
345 "show ipv6 ospf6 database type-7 detail json", isjson=True
346 )
347 for lsa in output["areaScopedLinkStateDb"][0]["lsa"]:
348 if lsa["prefix"] == unexpected_lsa["prefix"]:
349 if lsa["forwardingAddress"] == unexpected_lsa["forwardingAddress"]:
350 return lsa
351 return None
352
353 logger.info("Expecting LSA type-7 and OSPFv3 route 2001:db8:100::/64 to go away")
354
355 # Test that LSA doesn't exist.
356 test_func = partial(dont_expect_lsa, lsas[0])
357 _, result = topotest.run_and_expect(test_func, None, count=130, wait=1)
358 assertmsg = '"{}" LSA still exists'.format("r4")
359 assert result is None, assertmsg
360
361 # Test that route doesn't exist.
362 test_func = partial(dont_expect_route, "r4", "2001:db8:100::/64")
363 _, result = topotest.run_and_expect(test_func, None, count=130, wait=1)
364 assertmsg = '"{}" route still exists'.format("r4")
365 assert result is None, assertmsg
366
367
368 def test_nssa_no_summary():
369 """
370 Test the following:
371 * Type-3 inter-area routes should be removed when the NSSA no-summary option
372 is configured;
373 * A type-3 inter-area default route should be originated into the NSSA area
374 when the no-summary option is configured;
375 * Once the no-summary option is unconfigured, all previously existing
376 Type-3 inter-area routes should be re-added, and the inter-area default
377 route removed.
378 """
379 tgen = get_topogen()
380 if tgen.routers_have_failure():
381 pytest.skip(tgen.errors)
382
383 #
384 # Configure area 1 as a NSSA totally stub area.
385 #
386 config = """
387 configure terminal
388 router ospf6
389 area 2 nssa no-summary
390 """
391 tgen.gears["r2"].vtysh_cmd(config)
392
393 logger.info("Expecting inter-area routes to be removed")
394 for route in ["2001:db8:1::/64", "2001:db8:2::/64"]:
395 test_func = partial(dont_expect_route, "r4", route, type="inter-area")
396 _, result = topotest.run_and_expect(test_func, None, count=130, wait=1)
397 assertmsg = "{}'s {} inter-area route still exists".format("r4", route)
398 assert result is None, assertmsg
399
400 logger.info("Expecting inter-area default-route to be added")
401 routes = {"::/0": {}}
402 expect_ospfv3_routes("r4", routes, wait=30, type="inter-area")
403
404 #
405 # Configure area 1 as a regular NSSA area.
406 #
407 config = """
408 configure terminal
409 router ospf6
410 area 2 nssa
411 """
412 tgen.gears["r2"].vtysh_cmd(config)
413
414 logger.info("Expecting inter-area routes to be re-added")
415 routes = {"2001:db8:1::/64": {}, "2001:db8:2::/64": {}}
416 expect_ospfv3_routes("r4", routes, wait=30, type="inter-area")
417
418 logger.info("Expecting inter-area default route to be removed")
419 test_func = partial(dont_expect_route, "r4", "::/0", type="inter-area")
420 _, result = topotest.run_and_expect(test_func, None, count=130, wait=1)
421 assertmsg = "{}'s inter-area default route still exists".format("r4")
422 assert result is None, assertmsg
423
424
425 def test_nssa_default_originate():
426 """
427 Test the following:
428 * A type-7 default route should be originated into the NSSA area
429 when the default-information-originate option is configured;
430 * Once the default-information-originate option is unconfigured, the
431 previously originated Type-7 default route should be removed.
432 """
433 tgen = get_topogen()
434 if tgen.routers_have_failure():
435 pytest.skip(tgen.errors)
436
437 #
438 # Configure r2 to announce a Type-7 default route.
439 #
440 config = """
441 configure terminal
442 router ospf6
443 no default-information originate
444 area 2 nssa default-information-originate
445 """
446 tgen.gears["r2"].vtysh_cmd(config)
447
448 logger.info("Expecting Type-7 default-route to be added")
449 routes = {"::/0": {}}
450 expect_ospfv3_routes("r4", routes, wait=30, type="external-2")
451
452 #
453 # Configure r2 to stop announcing a Type-7 default route.
454 #
455 config = """
456 configure terminal
457 router ospf6
458 area 2 nssa
459 """
460 tgen.gears["r2"].vtysh_cmd(config)
461
462 logger.info("Expecting Type-7 default route to be removed")
463 test_func = partial(dont_expect_route, "r4", "::/0", type="external-2")
464 _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
465 assertmsg = "r4's Type-7 default route still exists"
466 assert result is None, assertmsg
467
468
469 def test_area_filters():
470 """
471 Test ABR import/export filters.
472 """
473 tgen = get_topogen()
474 if tgen.routers_have_failure():
475 pytest.skip(tgen.errors)
476
477 #
478 # Configure import/export filters on r2 (ABR for area 2).
479 #
480 config = """
481 configure terminal
482 ipv6 access-list ACL_IMPORT seq 5 permit 2001:db8:2::/64
483 ipv6 access-list ACL_IMPORT seq 10 deny any
484 ipv6 access-list ACL_EXPORT seq 10 deny any
485 router ospf6
486 area 1 import-list ACL_IMPORT
487 area 1 export-list ACL_EXPORT
488 """
489 tgen.gears["r2"].vtysh_cmd(config)
490
491 logger.info("Expecting inter-area routes to be removed on r1")
492 for route in ["::/0", "2001:db8:3::/64"]:
493 test_func = partial(dont_expect_route, "r1", route, type="inter-area")
494 _, result = topotest.run_and_expect(test_func, None, count=130, wait=1)
495 assertmsg = "{}'s {} inter-area route still exists".format("r1", route)
496 assert result is None, assertmsg
497
498 logger.info("Expecting inter-area routes to be removed on r3")
499 for route in ["2001:db8:1::/64"]:
500 test_func = partial(dont_expect_route, "r3", route, type="inter-area")
501 _, result = topotest.run_and_expect(test_func, None, count=130, wait=1)
502 assertmsg = "{}'s {} inter-area route still exists".format("r3", route)
503 assert result is None, assertmsg
504
505 #
506 # Update the ACLs used by the import/export filters.
507 #
508 config = """
509 configure terminal
510 ipv6 access-list ACL_IMPORT seq 6 permit 2001:db8:3::/64
511 ipv6 access-list ACL_EXPORT seq 5 permit 2001:db8:1::/64
512 """
513 tgen.gears["r2"].vtysh_cmd(config)
514
515 logger.info("Expecting 2001:db8:3::/64 to be re-added on r1")
516 routes = {"2001:db8:3::/64": {}}
517 expect_ospfv3_routes("r1", routes, wait=30, type="inter-area")
518 logger.info("Expecting 2001:db8:1::/64 to be re-added on r3")
519 routes = {"2001:db8:1::/64": {}}
520 expect_ospfv3_routes("r3", routes, wait=30, type="inter-area")
521
522 #
523 # Unconfigure r2's ABR import/export filters.
524 #
525 config = """
526 configure terminal
527 router ospf6
528 no area 1 import-list ACL_IMPORT
529 no area 1 export-list ACL_EXPORT
530 """
531 tgen.gears["r2"].vtysh_cmd(config)
532
533 logger.info("Expecting ::/0 to be re-added on r1")
534 routes = {"::/0": {}}
535 expect_ospfv3_routes("r1", routes, wait=30, type="inter-area")
536
537
538 def test_nssa_range():
539 """
540 Test NSSA ABR ranges.
541 """
542 tgen = get_topogen()
543 if tgen.routers_have_failure():
544 pytest.skip(tgen.errors)
545
546 # Configure new addresses on r4 and enable redistribution of connected
547 # routes.
548 config = """
549 configure terminal
550 interface r4-stubnet
551 ipv6 address 2001:db8:1000::1/128
552 ipv6 address 2001:db8:1000::2/128
553 router ospf6
554 redistribute connected
555 """
556 tgen.gears["r4"].vtysh_cmd(config)
557 logger.info("Expecting NSSA-translated external routes to be added on r3")
558 routes = {"2001:db8:1000::1/128": {}, "2001:db8:1000::2/128": {}}
559 expect_ospfv3_routes("r3", routes, wait=30, type="external-2")
560
561 # Configure an NSSA range on r2 (ABR for area 2).
562 config = """
563 configure terminal
564 router ospf6
565 area 2 nssa range 2001:db8:1000::/64
566 """
567 tgen.gears["r2"].vtysh_cmd(config)
568 logger.info("Expecting summarized routes to be removed from r3")
569 for route in ["2001:db8:1000::1/128", "2001:db8:1000::2/128"]:
570 test_func = partial(dont_expect_route, "r3", route, type="external-2")
571 _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
572 assertmsg = "{}'s {} summarized route still exists".format("r3", route)
573 assert result is None, assertmsg
574 logger.info("Expecting NSSA range to be added on r3")
575 routes = {
576 "2001:db8:1000::/64": {
577 "metricType": 2,
578 "metricCost": 20,
579 "metricCostE2": 10,
580 }
581 }
582 expect_ospfv3_routes("r3", routes, wait=30, type="external-2", detail=True)
583
584 # Change the NSSA range cost.
585 config = """
586 configure terminal
587 router ospf6
588 area 2 nssa range 2001:db8:1000::/64 cost 1000
589 """
590 tgen.gears["r2"].vtysh_cmd(config)
591 logger.info("Expecting NSSA range to be updated with new cost")
592 routes = {
593 "2001:db8:1000::/64": {
594 "metricType": 2,
595 "metricCost": 20,
596 "metricCostE2": 1000,
597 }
598 }
599 expect_ospfv3_routes("r3", routes, wait=30, type="external-2", detail=True)
600
601 # Configure the NSSA range to not be advertised.
602 config = """
603 configure terminal
604 router ospf6
605 area 2 nssa range 2001:db8:1000::/64 not-advertise
606 """
607 tgen.gears["r2"].vtysh_cmd(config)
608 logger.info("Expecting NSSA summary route to be removed")
609 route = "2001:db8:1000::/64"
610 test_func = partial(dont_expect_route, "r3", route, type="external-2")
611 _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
612 assertmsg = "{}'s {} NSSA summary route still exists".format("r3", route)
613 assert result is None, assertmsg
614
615 # Remove the NSSA range.
616 config = """
617 configure terminal
618 router ospf6
619 no area 2 nssa range 2001:db8:1000::/64
620 """
621 tgen.gears["r2"].vtysh_cmd(config)
622 logger.info("Expecting previously summarized routes to be re-added")
623 routes = {
624 "2001:db8:1000::1/128": {
625 "metricType": 2,
626 "metricCostE2": 20,
627 },
628 "2001:db8:1000::2/128": {
629 "metricType": 2,
630 "metricCostE2": 20,
631 },
632 }
633 expect_ospfv3_routes("r3", routes, wait=30, type="external-2", detail=True)
634
635
636 def teardown_module(_mod):
637 "Teardown the pytest environment"
638 tgen = get_topogen()
639 tgen.stop_topology()
640
641
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))