]> git.proxmox.com Git - mirror_frr.git/blob - tests/topotests/ospf6_topo2/test_ospf6_topo2.py
Merge pull request #12798 from donaldsharp/rib_match_multicast
[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 daemon_file = "{}/{}/zebra.conf".format(CWD, rname)
133 if os.path.isfile(daemon_file):
134 router.load_config(TopoRouter.RD_ZEBRA, daemon_file)
135
136 daemon_file = "{}/{}/ospf6d.conf".format(CWD, rname)
137 if os.path.isfile(daemon_file):
138 router.load_config(TopoRouter.RD_OSPF6, daemon_file)
139
140 # Initialize all routers.
141 tgen.start_router()
142
143
144 def test_wait_protocol_convergence():
145 "Wait for OSPFv3 to converge"
146 tgen = get_topogen()
147 if tgen.routers_have_failure():
148 pytest.skip(tgen.errors)
149
150 logger.info("waiting for protocols to converge")
151
152 def expect_neighbor_full(router, neighbor):
153 "Wait until OSPFv3 convergence."
154 logger.info("waiting OSPFv3 router '{}'".format(router))
155 test_func = partial(
156 topotest.router_json_cmp,
157 tgen.gears[router],
158 "show ipv6 ospf6 neighbor json",
159 {"neighbors": [{"neighborId": neighbor, "state": "Full"}]},
160 )
161 _, result = topotest.run_and_expect(test_func, None, count=130, wait=1)
162 assertmsg = '"{}" convergence failure'.format(router)
163 assert result is None, assertmsg
164
165 expect_neighbor_full("r1", "10.254.254.2")
166 expect_neighbor_full("r2", "10.254.254.1")
167 expect_neighbor_full("r2", "10.254.254.3")
168 expect_neighbor_full("r2", "10.254.254.4")
169 expect_neighbor_full("r3", "10.254.254.2")
170 expect_neighbor_full("r4", "10.254.254.2")
171
172
173 def test_ospfv3_expected_route_types():
174 "Test routers route type to determine if NSSA/Stub is working as expected."
175 tgen = get_topogen()
176 if tgen.routers_have_failure():
177 pytest.skip(tgen.errors)
178
179 logger.info("waiting for protocols to converge")
180
181 def expect_ospf6_route_types(router, expected_summary):
182 "Expect the correct route types."
183 logger.info("waiting OSPFv3 router '{}'".format(router))
184 test_func = partial(
185 topotest.router_json_cmp,
186 tgen.gears[router],
187 "show ipv6 ospf6 route summary json",
188 expected_summary,
189 )
190 _, result = topotest.run_and_expect(test_func, None, count=10, wait=1)
191 assertmsg = '"{}" convergence failure'.format(router)
192 assert result is None, assertmsg
193
194 # Stub router: no external routes.
195 expect_ospf6_route_types(
196 "r1",
197 {
198 "numberOfIntraAreaRoutes": 1,
199 "numberOfInterAreaRoutes": 3,
200 "numberOfExternal1Routes": 0,
201 "numberOfExternal2Routes": 0,
202 },
203 )
204 # NSSA router: no external routes.
205 expect_ospf6_route_types(
206 "r4",
207 {
208 "numberOfIntraAreaRoutes": 1,
209 "numberOfInterAreaRoutes": 2,
210 "numberOfExternal1Routes": 0,
211 "numberOfExternal2Routes": 3,
212 },
213 )
214
215
216 def test_ospf6_default_route():
217 "Wait for OSPFv3 default route in stub area."
218 tgen = get_topogen()
219 if tgen.routers_have_failure():
220 pytest.skip(tgen.errors)
221
222 logger.info("waiting for default route")
223
224 def expect_route(router, route, metric):
225 "Test OSPF6 route existence."
226 logger.info("waiting OSPFv3 router '{}' routes".format(router))
227 test_func = partial(
228 topotest.router_json_cmp,
229 tgen.gears[router],
230 "show ipv6 route json",
231 {route: [{"metric": metric}]},
232 )
233 _, result = topotest.run_and_expect(test_func, None, count=5, wait=1)
234 assertmsg = '"{}" convergence failure'.format(router)
235 assert result is None, assertmsg
236
237 metric = 123
238 expect_lsas(
239 "r1",
240 "0.0.0.1",
241 [{"prefix": "::/0", "metric": metric}],
242 extra_params="inter-prefix detail",
243 )
244 expect_route("r1", "::/0", metric + 10)
245
246
247 def test_redistribute_metrics():
248 """
249 Test that the configured metrics are honored when a static route is
250 redistributed.
251 """
252 tgen = get_topogen()
253 if tgen.routers_have_failure():
254 pytest.skip(tgen.errors)
255
256 # Add new static route on r3.
257 config = """
258 configure terminal
259 ipv6 route 2001:db8:500::/64 Null0
260 """
261 tgen.gears["r3"].vtysh_cmd(config)
262
263 route = {
264 "2001:db8:500::/64": {
265 "metricType": 2,
266 "metricCost": 10,
267 }
268 }
269 logger.info(
270 "Expecting AS-external route 2001:db8:500::/64 to show up with default metrics"
271 )
272 expect_ospfv3_routes("r2", route, wait=30, detail=True)
273
274 # Change the metric of redistributed routes of the static type on r3.
275 config = """
276 configure terminal
277 router ospf6
278 redistribute static metric 50 metric-type 1
279 """
280 tgen.gears["r3"].vtysh_cmd(config)
281
282 # Check if r3 reinstalled 2001:db8:500::/64 using the new metric type and value.
283 route = {
284 "2001:db8:500::/64": {
285 "metricType": 1,
286 "metricCost": 60,
287 }
288 }
289 logger.info(
290 "Expecting AS-external route 2001:db8:500::/64 to show up with updated metric type and value"
291 )
292 expect_ospfv3_routes("r2", route, wait=30, detail=True)
293
294
295 def test_nssa_lsa_type7():
296 """
297 Test that static route gets announced as external route when redistributed
298 and gets removed when redistribution stops.
299 """
300 tgen = get_topogen()
301 if tgen.routers_have_failure():
302 pytest.skip(tgen.errors)
303
304 #
305 # Add new static route and check if it gets announced as LSA Type-7.
306 #
307 config = """
308 configure terminal
309 ipv6 route 2001:db8:100::/64 Null0
310 """
311 tgen.gears["r2"].vtysh_cmd(config)
312
313 lsas = [
314 {
315 "type": "NSSA",
316 "advertisingRouter": "10.254.254.2",
317 "prefix": "2001:db8:100::/64",
318 "forwardingAddress": "2001:db8:3::1",
319 }
320 ]
321 route = {
322 "2001:db8:100::/64": {
323 "pathType": "E2",
324 "nextHops": [{"nextHop": "::", "interfaceName": "r4-eth0"}],
325 }
326 }
327
328 logger.info("Expecting LSA type-7 and OSPFv3 route 2001:db8:100::/64 to show up")
329 expect_lsas("r4", "0.0.0.2", lsas, wait=30, extra_params="type-7 detail")
330 expect_ospfv3_routes("r4", route, wait=30)
331
332 #
333 # Remove static route and check for LSA Type-7 removal.
334 #
335 config = """
336 configure terminal
337 no ipv6 route 2001:db8:100::/64 Null0
338 """
339 tgen.gears["r2"].vtysh_cmd(config)
340
341 def dont_expect_lsa(unexpected_lsa):
342 "Specialized test function to expect LSA go missing"
343 output = tgen.gears["r4"].vtysh_cmd(
344 "show ipv6 ospf6 database type-7 detail json", isjson=True
345 )
346 for lsa in output["areaScopedLinkStateDb"][0]["lsa"]:
347 if lsa["prefix"] == unexpected_lsa["prefix"]:
348 if lsa["forwardingAddress"] == unexpected_lsa["forwardingAddress"]:
349 return lsa
350 return None
351
352 logger.info("Expecting LSA type-7 and OSPFv3 route 2001:db8:100::/64 to go away")
353
354 # Test that LSA doesn't exist.
355 test_func = partial(dont_expect_lsa, lsas[0])
356 _, result = topotest.run_and_expect(test_func, None, count=130, wait=1)
357 assertmsg = '"{}" LSA still exists'.format("r4")
358 assert result is None, assertmsg
359
360 # Test that route doesn't exist.
361 test_func = partial(dont_expect_route, "r4", "2001:db8:100::/64")
362 _, result = topotest.run_and_expect(test_func, None, count=130, wait=1)
363 assertmsg = '"{}" route still exists'.format("r4")
364 assert result is None, assertmsg
365
366
367 def test_nssa_no_summary():
368 """
369 Test the following:
370 * Type-3 inter-area routes should be removed when the NSSA no-summary option
371 is configured;
372 * A type-3 inter-area default route should be originated into the NSSA area
373 when the no-summary option is configured;
374 * Once the no-summary option is unconfigured, all previously existing
375 Type-3 inter-area routes should be re-added, and the inter-area default
376 route removed.
377 """
378 tgen = get_topogen()
379 if tgen.routers_have_failure():
380 pytest.skip(tgen.errors)
381
382 #
383 # Configure area 1 as a NSSA totally stub area.
384 #
385 config = """
386 configure terminal
387 router ospf6
388 area 2 nssa no-summary
389 """
390 tgen.gears["r2"].vtysh_cmd(config)
391
392 logger.info("Expecting inter-area routes to be removed")
393 for route in ["2001:db8:1::/64", "2001:db8:2::/64"]:
394 test_func = partial(dont_expect_route, "r4", route, type="inter-area")
395 _, result = topotest.run_and_expect(test_func, None, count=130, wait=1)
396 assertmsg = "{}'s {} inter-area route still exists".format("r4", route)
397 assert result is None, assertmsg
398
399 logger.info("Expecting inter-area default-route to be added")
400 routes = {"::/0": {}}
401 expect_ospfv3_routes("r4", routes, wait=30, type="inter-area")
402
403 #
404 # Configure area 1 as a regular NSSA area.
405 #
406 config = """
407 configure terminal
408 router ospf6
409 area 2 nssa
410 """
411 tgen.gears["r2"].vtysh_cmd(config)
412
413 logger.info("Expecting inter-area routes to be re-added")
414 routes = {"2001:db8:1::/64": {}, "2001:db8:2::/64": {}}
415 expect_ospfv3_routes("r4", routes, wait=30, type="inter-area")
416
417 logger.info("Expecting inter-area default route to be removed")
418 test_func = partial(dont_expect_route, "r4", "::/0", type="inter-area")
419 _, result = topotest.run_and_expect(test_func, None, count=130, wait=1)
420 assertmsg = "{}'s inter-area default route still exists".format("r4")
421 assert result is None, assertmsg
422
423
424 def test_nssa_default_originate():
425 """
426 Test the following:
427 * A type-7 default route should be originated into the NSSA area
428 when the default-information-originate option is configured;
429 * Once the default-information-originate option is unconfigured, the
430 previously originated Type-7 default route should be removed.
431 """
432 tgen = get_topogen()
433 if tgen.routers_have_failure():
434 pytest.skip(tgen.errors)
435
436 #
437 # Configure r2 to announce a Type-7 default route.
438 #
439 config = """
440 configure terminal
441 router ospf6
442 no default-information originate
443 area 2 nssa default-information-originate
444 """
445 tgen.gears["r2"].vtysh_cmd(config)
446
447 logger.info("Expecting Type-7 default-route to be added")
448 routes = {"::/0": {}}
449 expect_ospfv3_routes("r4", routes, wait=30, type="external-2")
450
451 #
452 # Configure r2 to stop announcing a Type-7 default route.
453 #
454 config = """
455 configure terminal
456 router ospf6
457 area 2 nssa
458 """
459 tgen.gears["r2"].vtysh_cmd(config)
460
461 logger.info("Expecting Type-7 default route to be removed")
462 test_func = partial(dont_expect_route, "r4", "::/0", type="external-2")
463 _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
464 assertmsg = "r4's Type-7 default route still exists"
465 assert result is None, assertmsg
466
467
468 def test_area_filters():
469 """
470 Test ABR import/export filters.
471 """
472 tgen = get_topogen()
473 if tgen.routers_have_failure():
474 pytest.skip(tgen.errors)
475
476 #
477 # Configure import/export filters on r2 (ABR for area 2).
478 #
479 config = """
480 configure terminal
481 ipv6 access-list ACL_IMPORT seq 5 permit 2001:db8:2::/64
482 ipv6 access-list ACL_IMPORT seq 10 deny any
483 ipv6 access-list ACL_EXPORT seq 10 deny any
484 router ospf6
485 area 1 import-list ACL_IMPORT
486 area 1 export-list ACL_EXPORT
487 """
488 tgen.gears["r2"].vtysh_cmd(config)
489
490 logger.info("Expecting inter-area routes to be removed on r1")
491 for route in ["::/0", "2001:db8:3::/64"]:
492 test_func = partial(dont_expect_route, "r1", route, type="inter-area")
493 _, result = topotest.run_and_expect(test_func, None, count=130, wait=1)
494 assertmsg = "{}'s {} inter-area route still exists".format("r1", route)
495 assert result is None, assertmsg
496
497 logger.info("Expecting inter-area routes to be removed on r3")
498 for route in ["2001:db8:1::/64"]:
499 test_func = partial(dont_expect_route, "r3", route, type="inter-area")
500 _, result = topotest.run_and_expect(test_func, None, count=130, wait=1)
501 assertmsg = "{}'s {} inter-area route still exists".format("r3", route)
502 assert result is None, assertmsg
503
504 #
505 # Update the ACLs used by the import/export filters.
506 #
507 config = """
508 configure terminal
509 ipv6 access-list ACL_IMPORT seq 6 permit 2001:db8:3::/64
510 ipv6 access-list ACL_EXPORT seq 5 permit 2001:db8:1::/64
511 """
512 tgen.gears["r2"].vtysh_cmd(config)
513
514 logger.info("Expecting 2001:db8:3::/64 to be re-added on r1")
515 routes = {"2001:db8:3::/64": {}}
516 expect_ospfv3_routes("r1", routes, wait=30, type="inter-area")
517 logger.info("Expecting 2001:db8:1::/64 to be re-added on r3")
518 routes = {"2001:db8:1::/64": {}}
519 expect_ospfv3_routes("r3", routes, wait=30, type="inter-area")
520
521 #
522 # Unconfigure r2's ABR import/export filters.
523 #
524 config = """
525 configure terminal
526 router ospf6
527 no area 1 import-list ACL_IMPORT
528 no area 1 export-list ACL_EXPORT
529 """
530 tgen.gears["r2"].vtysh_cmd(config)
531
532 logger.info("Expecting ::/0 to be re-added on r1")
533 routes = {"::/0": {}}
534 expect_ospfv3_routes("r1", routes, wait=30, type="inter-area")
535
536
537 def test_nssa_range():
538 """
539 Test NSSA ABR ranges.
540 """
541 tgen = get_topogen()
542 if tgen.routers_have_failure():
543 pytest.skip(tgen.errors)
544
545 # Configure new addresses on r4 and enable redistribution of connected
546 # routes.
547 config = """
548 configure terminal
549 interface r4-stubnet
550 ipv6 address 2001:db8:1000::1/128
551 ipv6 address 2001:db8:1000::2/128
552 router ospf6
553 redistribute connected
554 """
555 tgen.gears["r4"].vtysh_cmd(config)
556 logger.info("Expecting NSSA-translated external routes to be added on r3")
557 routes = {"2001:db8:1000::1/128": {}, "2001:db8:1000::2/128": {}}
558 expect_ospfv3_routes("r3", routes, wait=30, type="external-2")
559
560 # Configure an NSSA range on r2 (ABR for area 2).
561 config = """
562 configure terminal
563 router ospf6
564 area 2 nssa range 2001:db8:1000::/64
565 """
566 tgen.gears["r2"].vtysh_cmd(config)
567 logger.info("Expecting summarized routes to be removed from r3")
568 for route in ["2001:db8:1000::1/128", "2001:db8:1000::2/128"]:
569 test_func = partial(dont_expect_route, "r3", route, type="external-2")
570 _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
571 assertmsg = "{}'s {} summarized route still exists".format("r3", route)
572 assert result is None, assertmsg
573 logger.info("Expecting NSSA range to be added on r3")
574 routes = {
575 "2001:db8:1000::/64": {
576 "metricType": 2,
577 "metricCost": 20,
578 "metricCostE2": 10,
579 }
580 }
581 expect_ospfv3_routes("r3", routes, wait=30, type="external-2", detail=True)
582
583 # Change the NSSA range cost.
584 config = """
585 configure terminal
586 router ospf6
587 area 2 nssa range 2001:db8:1000::/64 cost 1000
588 """
589 tgen.gears["r2"].vtysh_cmd(config)
590 logger.info("Expecting NSSA range to be updated with new cost")
591 routes = {
592 "2001:db8:1000::/64": {
593 "metricType": 2,
594 "metricCost": 20,
595 "metricCostE2": 1000,
596 }
597 }
598 expect_ospfv3_routes("r3", routes, wait=30, type="external-2", detail=True)
599
600 # Configure the NSSA range to not be advertised.
601 config = """
602 configure terminal
603 router ospf6
604 area 2 nssa range 2001:db8:1000::/64 not-advertise
605 """
606 tgen.gears["r2"].vtysh_cmd(config)
607 logger.info("Expecting NSSA summary route to be removed")
608 route = "2001:db8:1000::/64"
609 test_func = partial(dont_expect_route, "r3", route, type="external-2")
610 _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
611 assertmsg = "{}'s {} NSSA summary route still exists".format("r3", route)
612 assert result is None, assertmsg
613
614 # Remove the NSSA range.
615 config = """
616 configure terminal
617 router ospf6
618 no area 2 nssa range 2001:db8:1000::/64
619 """
620 tgen.gears["r2"].vtysh_cmd(config)
621 logger.info("Expecting previously summarized routes to be re-added")
622 routes = {
623 "2001:db8:1000::1/128": {
624 "metricType": 2,
625 "metricCostE2": 20,
626 },
627 "2001:db8:1000::2/128": {
628 "metricType": 2,
629 "metricCostE2": 20,
630 },
631 }
632 expect_ospfv3_routes("r3", routes, wait=30, type="external-2", detail=True)
633
634
635 def teardown_module(_mod):
636 "Teardown the pytest environment"
637 tgen = get_topogen()
638 tgen.stop_topology()
639
640
641 def test_memory_leak():
642 "Run the memory leak test and report results."
643 tgen = get_topogen()
644 if not tgen.is_memleak_enabled():
645 pytest.skip("Memory leak test/report is disabled")
646
647 tgen.report_memory_leaks()
648
649
650 if __name__ == "__main__":
651 args = ["-s"] + sys.argv[1:]
652 sys.exit(pytest.main(args))