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