]> git.proxmox.com Git - mirror_frr.git/blob - tests/topotests/ospf6_topo1_vrf/test_ospf6_topo1_vrf.py
Merge pull request #8744 from sworleys/RTADV-Fix-Upstream
[mirror_frr.git] / tests / topotests / ospf6_topo1_vrf / test_ospf6_topo1_vrf.py
1 #!/usr/bin/env python
2
3 #
4 # test_ospf6_topo1_vrf.py
5 # Part of NetDEF Topology Tests
6 #
7 # Copyright (c) 2021 by Niral Networks, Inc. ("Niral Networks")
8 # Used Copyright (c) 2016 by Network Device Education Foundation,
9 # Inc. ("NetDEF") in this file.
10 #
11 # Permission to use, copy, modify, and/or distribute this software
12 # for any purpose with or without fee is hereby granted, provided
13 # that the above copyright notice and this permission notice appear
14 # in all copies.
15 #
16 # THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES
17 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
18 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR
19 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
20 # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
21 # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
22 # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
23 # OF THIS SOFTWARE.
24 #
25
26 """
27 test_ospf6_topo1_vrf.py:
28
29 -----\
30 SW1 - Stub Net 1 SW2 - Stub Net 2 \
31 fc00:1:1:1::/64 fc00:2:2:2::/64 \
32 \___________________/ \___________________/ |
33 | | |
34 | | |
35 | ::1 | ::2 |
36 +---------+---------+ +---------+---------+ |
37 | R1 | | R2 | |
38 | FRRouting | | FRRouting | |
39 | Rtr-ID: 10.0.0.1 | | Rtr-ID: 10.0.0.2 | |
40 +---------+---------+ +---------+---------+ |
41 | ::1 | ::2 \
42 \______ ___________/ OSPFv3
43 \ / Area 0.0.0.0
44 \ / /
45 ~~~~~~~~~~~~~~~~~~ |
46 ~~ SW5 ~~ |
47 ~~ Switch ~~ |
48 ~~ fc00:A:A:A::/64 ~~ |
49 ~~~~~~~~~~~~~~~~~~ |
50 | /---- |
51 | ::3 | SW3 - Stub Net 3 |
52 +---------+---------+ /-+ fc00:3:3:3::/64 |
53 | R3 | / | /
54 | FRRouting +--/ \---- /
55 | Rtr-ID: 10.0.0.3 | ::3 ___________/
56 +---------+---------+ \
57 | ::3 \
58 | \
59 ~~~~~~~~~~~~~~~~~~ |
60 ~~ SW6 ~~ |
61 ~~ Switch ~~ |
62 ~~ fc00:B:B:B::/64 ~~ \
63 ~~~~~~~~~~~~~~~~~~ OSPFv3
64 | Area 0.0.0.1
65 | ::4 /
66 +---------+---------+ /---- |
67 | R4 | | SW4 - Stub Net 4 |
68 | FRRouting +------+ fc00:4:4:4::/64 |
69 | Rtr-ID: 10.0.0.4 | ::4 | /
70 +-------------------+ \---- /
71 -----/
72 """
73
74 import os
75 import re
76 import sys
77 import pytest
78 import platform
79 from time import sleep
80
81 from functools import partial
82
83 from mininet.topo import Topo
84
85 # Save the Current Working Directory to find configuration files later.
86 CWD = os.path.dirname(os.path.realpath(__file__))
87 sys.path.append(os.path.join(CWD, "../"))
88
89 # pylint: disable=C0413
90 # Import topogen and topotest helpers
91 from lib import topotest
92 from lib.topogen import Topogen, TopoRouter, get_topogen
93 from lib.topolog import logger
94 from lib.topotest import iproute2_is_vrf_capable
95 from lib.common_config import required_linux_kernel_version
96
97 #####################################################
98 ##
99 ## Network Topology Definition
100 ##
101 #####################################################
102
103
104 class NetworkTopo(Topo):
105 "OSPFv3 (IPv6) Test Topology 1"
106
107 def build(self, **_opts):
108 "Build function"
109
110 tgen = get_topogen(self)
111
112 # Create 4 routers
113 for routern in range(1, 5):
114 tgen.add_router("r{}".format(routern))
115
116 #
117 # Wire up the switches and routers
118 # Note that we specify the link names so we match the config files
119 #
120
121 # Create a empty network for router 1
122 switch = tgen.add_switch("s1")
123 switch.add_link(tgen.gears["r1"], nodeif="r1-stubnet")
124
125 # Create a empty network for router 2
126 switch = tgen.add_switch("s2")
127 switch.add_link(tgen.gears["r2"], nodeif="r2-stubnet")
128
129 # Create a empty network for router 3
130 switch = tgen.add_switch("s3")
131 switch.add_link(tgen.gears["r3"], nodeif="r3-stubnet")
132
133 # Create a empty network for router 4
134 switch = tgen.add_switch("s4")
135 switch.add_link(tgen.gears["r4"], nodeif="r4-stubnet")
136
137 # Interconnect routers 1, 2, and 3
138 switch = tgen.add_switch("s5")
139 switch.add_link(tgen.gears["r1"], nodeif="r1-sw5")
140 switch.add_link(tgen.gears["r2"], nodeif="r2-sw5")
141 switch.add_link(tgen.gears["r3"], nodeif="r3-sw5")
142
143 # Interconnect routers 3 and 4
144 switch = tgen.add_switch("s6")
145 switch.add_link(tgen.gears["r3"], nodeif="r3-sw6")
146 switch.add_link(tgen.gears["r4"], nodeif="r4-sw6")
147
148
149 #####################################################
150 ##
151 ## Tests starting
152 ##
153 #####################################################
154
155
156 def setup_module(mod):
157 "Sets up the pytest environment"
158
159 # Required linux kernel version for this suite to run.
160 result = required_linux_kernel_version("5.0")
161 if result is not True:
162 pytest.skip("Kernel requirements are not met")
163
164 tgen = Topogen(NetworkTopo, mod.__name__)
165 tgen.start_topology()
166
167 logger.info("** %s: Setup Topology" % mod.__name__)
168 logger.info("******************************************")
169
170 # For debugging after starting net, but before starting FRR,
171 # uncomment the next line
172 # tgen.mininet_cli()
173
174 logger.info("Testing with VRF Lite support")
175
176 cmds = [
177 "ip link add {0}-cust1 type vrf table 1001",
178 "ip link add loop1 type dummy",
179 "ip link set {0}-stubnet master {0}-cust1",
180 ]
181
182 cmds1 = ["ip link set {0}-sw5 master {0}-cust1"]
183
184 cmds2 = ["ip link set {0}-sw6 master {0}-cust1"]
185
186 # For all registered routers, load the zebra configuration file
187 for rname, router in tgen.routers().items():
188 # create VRF rx-cust1 and link rx-eth0 to rx-cust1
189 for cmd in cmds:
190 output = tgen.net[rname].cmd(cmd.format(rname))
191 if rname == "r1" or rname == "r2" or rname == "r3":
192 for cmd in cmds1:
193 output = tgen.net[rname].cmd(cmd.format(rname))
194 if rname == "r3" or rname == "r4":
195 for cmd in cmds2:
196 output = tgen.net[rname].cmd(cmd.format(rname))
197
198 for rname, router in tgen.routers().items():
199 router.load_config(
200 TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname))
201 )
202 router.load_config(
203 TopoRouter.RD_OSPF6, os.path.join(CWD, "{}/ospf6d.conf".format(rname))
204 )
205
206 # Initialize all routers.
207 tgen.start_router()
208
209 # For debugging after starting FRR daemons, uncomment the next line
210 # tgen.mininet_cli()
211
212
213 def teardown_module(mod):
214 "Teardown the pytest environment"
215 tgen = get_topogen()
216 tgen.stop_topology()
217
218
219 def test_wait_protocol_convergence():
220 "Wait for OSPFv3 to converge"
221 tgen = get_topogen()
222 if tgen.routers_have_failure():
223 pytest.skip(tgen.errors)
224
225 logger.info("waiting for protocols to converge")
226
227 def expect_neighbor_full(router, neighbor):
228 "Wait until OSPFv3 convergence."
229 logger.info("waiting OSPFv3 router '{}'".format(router))
230 test_func = partial(
231 topotest.router_json_cmp,
232 tgen.gears[router],
233 "show ipv6 ospf6 vrf {0}-cust1 neighbor json".format(router),
234 {"neighbors": [{"neighborId": neighbor, "state": "Full"}]},
235 )
236 _, result = topotest.run_and_expect(test_func, None, count=130, wait=1)
237 assertmsg = '"{}" convergence failure'.format(router)
238 assert result is None, assertmsg
239
240 expect_neighbor_full("r1", "10.0.0.2")
241 expect_neighbor_full("r1", "10.0.0.3")
242
243 expect_neighbor_full("r2", "10.0.0.1")
244 expect_neighbor_full("r2", "10.0.0.3")
245
246 expect_neighbor_full("r3", "10.0.0.1")
247 expect_neighbor_full("r3", "10.0.0.2")
248 expect_neighbor_full("r3", "10.0.0.4")
249
250
251 def compare_show_ipv6_vrf(rname, expected):
252 """
253 Calls 'show ipv6 route' for router `rname` and compare the obtained
254 result with the expected output.
255 """
256 tgen = get_topogen()
257
258 # Use the vtysh output, with some masking to make comparison easy
259 vrf_name = "{0}-cust1".format(rname)
260 current = topotest.ip6_route_zebra(tgen.gears[rname], vrf_name)
261
262 # Use just the 'O'spf lines of the output
263 linearr = []
264 for line in current.splitlines():
265 if re.match("^O", line):
266 linearr.append(line)
267
268 current = "\n".join(linearr)
269
270 return topotest.difflines(
271 topotest.normalize_text(current),
272 topotest.normalize_text(expected),
273 title1="Current output",
274 title2="Expected output",
275 )
276
277
278 def test_ospfv3_routingTable():
279
280 tgen = get_topogen()
281 if tgen.routers_have_failure():
282 pytest.skip("skipped because of router(s) failure")
283
284 # For debugging, uncomment the next line
285 # tgen.mininet_cli()
286 # Verify OSPFv3 Routing Table
287 for router, rnode in tgen.routers().iteritems():
288 logger.info('Waiting for router "%s" convergence', router)
289
290 # Load expected results from the command
291 reffile = os.path.join(CWD, "{}/show_ipv6_vrf_route.ref".format(router))
292 expected = open(reffile).read()
293
294 # Run test function until we get an result. Wait at most 60 seconds.
295 test_func = partial(compare_show_ipv6_vrf, router, expected)
296 result, diff = topotest.run_and_expect(test_func, "", count=120, wait=0.5)
297 assert result, "OSPFv3 did not converge on {}:\n{}".format(router, diff)
298
299
300 def test_linux_ipv6_kernel_routingTable():
301
302 # Required linux kernel version for this suite to run.
303 result = required_linux_kernel_version("4.15")
304 if result is not True:
305 pytest.skip("Kernel requirements are not met")
306
307 # iproute2 needs to support VRFs for this suite to run.
308 if not iproute2_is_vrf_capable():
309 pytest.skip("Installed iproute2 version does not support VRFs")
310
311 tgen = get_topogen()
312
313 if tgen.routers_have_failure():
314 pytest.skip("skipped because of router(s) failure")
315
316 # Verify Linux Kernel Routing Table
317 logger.info("Verifying Linux IPv6 Kernel Routing Table")
318
319 failures = 0
320
321 # Get a list of all current link-local addresses first as they change for
322 # each run and we need to translate them
323 linklocals = []
324 for i in range(1, 5):
325 linklocals += tgen.net["r{}".format(i)].get_ipv6_linklocal()
326
327 # Now compare the routing tables (after substituting link-local addresses)
328
329 for i in range(1, 5):
330 # Actual output from router
331 actual = (
332 tgen.gears["r{}".format(i)]
333 .run("ip -6 route show vrf r{}-cust1".format(i))
334 .rstrip()
335 )
336 if "nhid" in actual:
337 refTableFile = os.path.join(CWD, "r{}/ip_6_address.nhg.ref".format(i))
338 else:
339 refTableFile = os.path.join(CWD, "r{}/ip_6_address.ref".format(i))
340
341 if os.path.isfile(refTableFile):
342 expected = open(refTableFile).read().rstrip()
343 # Fix newlines (make them all the same)
344 expected = ("\n".join(expected.splitlines())).splitlines(1)
345
346 # Mask out Link-Local mac addresses
347 for ll in linklocals:
348 actual = actual.replace(ll[1], "fe80::__(%s)__" % ll[0])
349 # Mask out protocol name or number
350 actual = re.sub(r"[ ]+proto [0-9a-z]+ +", " proto XXXX ", actual)
351 actual = re.sub(r"[ ]+nhid [0-9]+ +", " nhid XXXX ", actual)
352 # Remove ff00::/8 routes (seen on some kernels - not from FRR)
353 actual = re.sub(r"ff00::/8.*", "", actual)
354
355 # Strip empty lines
356 actual = actual.lstrip()
357 actual = actual.rstrip()
358 actual = re.sub(r" +", " ", actual)
359
360 filtered_lines = []
361 for line in sorted(actual.splitlines()):
362 if line.startswith("fe80::/64 ") or line.startswith(
363 "unreachable fe80::/64 "
364 ):
365 continue
366 if "anycast" in line:
367 continue
368 if "multicast" in line:
369 continue
370 filtered_lines.append(line)
371 actual = "\n".join(filtered_lines).splitlines(1)
372
373 # Print Actual table
374 # logger.info("Router r%s table" % i)
375 # for line in actual:
376 # logger.info(line.rstrip())
377
378 # Generate Diff
379 diff = topotest.get_textdiff(
380 actual,
381 expected,
382 title1="actual OSPFv3 IPv6 routing table",
383 title2="expected OSPFv3 IPv6 routing table",
384 )
385
386 # Empty string if it matches, otherwise diff contains unified diff
387 if diff:
388 sys.stderr.write(
389 "r%s failed Linux IPv6 Kernel Routing Table Check:\n%s\n"
390 % (i, diff)
391 )
392 failures += 1
393 else:
394 logger.info("r%s ok" % i)
395
396 assert failures == 0, (
397 "Linux Kernel IPv6 Routing Table verification failed for router r%s:\n%s"
398 % (i, diff)
399 )
400
401
402 def test_ospfv3_routingTable_write_multiplier():
403
404 tgen = get_topogen()
405 if tgen.routers_have_failure():
406 pytest.skip("skipped because of router(s) failure")
407
408 # For debugging, uncomment the next line
409 # tgen.mininet_cli()
410 # Modify R1 write muliplier and reset the interfaces
411 r1 = tgen.gears["r1"]
412
413 r1.vtysh_cmd("conf t\nrouter ospf6 vrf r1-cust1 \n write-multiplier 100")
414 r1.vtysh_cmd("clear ipv6 ospf interface r1-stubnet")
415 r1.vtysh_cmd("clear ipv6 ospf interface r1-sw5")
416
417 # Verify OSPFv3 Routing Table
418 for router, rnode in tgen.routers().iteritems():
419 logger.info('Waiting for router "%s" convergence', router)
420
421 # Load expected results from the command
422 reffile = os.path.join(CWD, "{}/show_ipv6_vrf_route.ref".format(router))
423 expected = open(reffile).read()
424
425 # Run test function until we get an result. Wait at most 60 seconds.
426 test_func = partial(compare_show_ipv6_vrf, router, expected)
427 result, diff = topotest.run_and_expect(test_func, "", count=120, wait=0.5)
428 assert result, "OSPFv3 did not converge on {}:\n{}".format(router, diff)
429
430
431 def test_shutdown_check_stderr():
432
433 tgen = get_topogen()
434
435 if tgen.routers_have_failure():
436 pytest.skip("skipped because of router(s) failure")
437
438 if os.environ.get("TOPOTESTS_CHECK_STDERR") is None:
439 logger.info(
440 "SKIPPED final check on StdErr output: Disabled (TOPOTESTS_CHECK_STDERR undefined)\n"
441 )
442 pytest.skip("Skipping test for Stderr output")
443
444 net = tgen.net
445
446 logger.info("\n\n** Verifying unexpected STDERR output from daemons")
447 logger.info("******************************************")
448
449 for i in range(1, 5):
450 net["r%s" % i].stopRouter()
451 log = net["r%s" % i].getStdErr("ospf6d")
452 if log:
453 logger.info("\nRouter r%s OSPF6d StdErr Log:\n%s" % (i, log))
454 log = net["r%s" % i].getStdErr("zebra")
455 if log:
456 logger.info("\nRouter r%s Zebra StdErr Log:\n%s" % (i, log))
457
458
459 def test_shutdown_check_memleak():
460 "Run the memory leak test and report results."
461
462 if os.environ.get("TOPOTESTS_CHECK_MEMLEAK") is None:
463 logger.info(
464 "SKIPPED final check on Memory leaks: Disabled (TOPOTESTS_CHECK_MEMLEAK undefined)"
465 )
466 pytest.skip("Skipping test for memory leaks")
467
468 tgen = get_topogen()
469
470 net = tgen.net
471
472 for i in range(1, 5):
473 net["r%s" % i].stopRouter()
474 net["r%s" % i].report_memory_leaks(
475 os.environ.get("TOPOTESTS_CHECK_MEMLEAK"), os.path.basename(__file__)
476 )
477
478
479 if __name__ == "__main__":
480
481 # To suppress tracebacks, either use the following pytest call or
482 # add "--tb=no" to cli
483 # retval = pytest.main(["-s", "--tb=no"])
484
485 retval = pytest.main(["-s"])
486 sys.exit(retval)