]>
Commit | Line | Data |
---|---|---|
0681195e | 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") | |
d01673b4 | 8 | # Used Copyright (c) 2016 by Network Device Education Foundation, |
0681195e | 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 | ||
8db751b8 | 83 | from lib.micronet_compat import Topo |
0681195e | 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 | |
36eef858 | 95 | from lib.common_config import required_linux_kernel_version |
0681195e | 96 | |
6ff492b1 DS |
97 | pytestmark = [pytest.mark.ospfd] |
98 | ||
99 | ||
0681195e | 100 | ##################################################### |
101 | ## | |
102 | ## Network Topology Definition | |
103 | ## | |
104 | ##################################################### | |
105 | ||
106 | ||
107 | class NetworkTopo(Topo): | |
108 | "OSPFv3 (IPv6) Test Topology 1" | |
109 | ||
110 | def build(self, **_opts): | |
111 | "Build function" | |
112 | ||
113 | tgen = get_topogen(self) | |
114 | ||
115 | # Create 4 routers | |
116 | for routern in range(1, 5): | |
117 | tgen.add_router("r{}".format(routern)) | |
118 | ||
119 | # | |
120 | # Wire up the switches and routers | |
121 | # Note that we specify the link names so we match the config files | |
122 | # | |
123 | ||
124 | # Create a empty network for router 1 | |
125 | switch = tgen.add_switch("s1") | |
126 | switch.add_link(tgen.gears["r1"], nodeif="r1-stubnet") | |
127 | ||
128 | # Create a empty network for router 2 | |
129 | switch = tgen.add_switch("s2") | |
130 | switch.add_link(tgen.gears["r2"], nodeif="r2-stubnet") | |
131 | ||
132 | # Create a empty network for router 3 | |
133 | switch = tgen.add_switch("s3") | |
134 | switch.add_link(tgen.gears["r3"], nodeif="r3-stubnet") | |
135 | ||
136 | # Create a empty network for router 4 | |
137 | switch = tgen.add_switch("s4") | |
138 | switch.add_link(tgen.gears["r4"], nodeif="r4-stubnet") | |
139 | ||
140 | # Interconnect routers 1, 2, and 3 | |
141 | switch = tgen.add_switch("s5") | |
142 | switch.add_link(tgen.gears["r1"], nodeif="r1-sw5") | |
143 | switch.add_link(tgen.gears["r2"], nodeif="r2-sw5") | |
144 | switch.add_link(tgen.gears["r3"], nodeif="r3-sw5") | |
145 | ||
146 | # Interconnect routers 3 and 4 | |
147 | switch = tgen.add_switch("s6") | |
148 | switch.add_link(tgen.gears["r3"], nodeif="r3-sw6") | |
149 | switch.add_link(tgen.gears["r4"], nodeif="r4-sw6") | |
150 | ||
151 | ||
152 | ##################################################### | |
153 | ## | |
154 | ## Tests starting | |
155 | ## | |
156 | ##################################################### | |
157 | ||
158 | ||
159 | def setup_module(mod): | |
160 | "Sets up the pytest environment" | |
161 | ||
36eef858 IR |
162 | # Required linux kernel version for this suite to run. |
163 | result = required_linux_kernel_version("5.0") | |
164 | if result is not True: | |
165 | pytest.skip("Kernel requirements are not met") | |
166 | ||
0681195e | 167 | tgen = Topogen(NetworkTopo, mod.__name__) |
168 | tgen.start_topology() | |
169 | ||
170 | logger.info("** %s: Setup Topology" % mod.__name__) | |
171 | logger.info("******************************************") | |
172 | ||
173 | # For debugging after starting net, but before starting FRR, | |
174 | # uncomment the next line | |
175 | # tgen.mininet_cli() | |
176 | ||
177 | logger.info("Testing with VRF Lite support") | |
178 | ||
179 | cmds = [ | |
180 | "ip link add {0}-cust1 type vrf table 1001", | |
181 | "ip link add loop1 type dummy", | |
182 | "ip link set {0}-stubnet master {0}-cust1", | |
183 | ] | |
184 | ||
d01673b4 | 185 | cmds1 = ["ip link set {0}-sw5 master {0}-cust1"] |
0681195e | 186 | |
d01673b4 | 187 | cmds2 = ["ip link set {0}-sw6 master {0}-cust1"] |
0681195e | 188 | |
189 | # For all registered routers, load the zebra configuration file | |
190 | for rname, router in tgen.routers().items(): | |
191 | # create VRF rx-cust1 and link rx-eth0 to rx-cust1 | |
192 | for cmd in cmds: | |
193 | output = tgen.net[rname].cmd(cmd.format(rname)) | |
194 | if rname == "r1" or rname == "r2" or rname == "r3": | |
195 | for cmd in cmds1: | |
196 | output = tgen.net[rname].cmd(cmd.format(rname)) | |
197 | if rname == "r3" or rname == "r4": | |
198 | for cmd in cmds2: | |
199 | output = tgen.net[rname].cmd(cmd.format(rname)) | |
200 | ||
0681195e | 201 | for rname, router in tgen.routers().items(): |
202 | router.load_config( | |
203 | TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) | |
204 | ) | |
205 | router.load_config( | |
206 | TopoRouter.RD_OSPF6, os.path.join(CWD, "{}/ospf6d.conf".format(rname)) | |
207 | ) | |
208 | ||
209 | # Initialize all routers. | |
210 | tgen.start_router() | |
211 | ||
212 | # For debugging after starting FRR daemons, uncomment the next line | |
213 | # tgen.mininet_cli() | |
214 | ||
215 | ||
216 | def teardown_module(mod): | |
217 | "Teardown the pytest environment" | |
218 | tgen = get_topogen() | |
219 | tgen.stop_topology() | |
220 | ||
d01673b4 | 221 | |
0681195e | 222 | def test_wait_protocol_convergence(): |
223 | "Wait for OSPFv3 to converge" | |
224 | tgen = get_topogen() | |
225 | if tgen.routers_have_failure(): | |
226 | pytest.skip(tgen.errors) | |
227 | ||
228 | logger.info("waiting for protocols to converge") | |
229 | ||
230 | def expect_neighbor_full(router, neighbor): | |
231 | "Wait until OSPFv3 convergence." | |
232 | logger.info("waiting OSPFv3 router '{}'".format(router)) | |
233 | test_func = partial( | |
234 | topotest.router_json_cmp, | |
235 | tgen.gears[router], | |
236 | "show ipv6 ospf6 vrf {0}-cust1 neighbor json".format(router), | |
237 | {"neighbors": [{"neighborId": neighbor, "state": "Full"}]}, | |
238 | ) | |
239 | _, result = topotest.run_and_expect(test_func, None, count=130, wait=1) | |
240 | assertmsg = '"{}" convergence failure'.format(router) | |
241 | assert result is None, assertmsg | |
242 | ||
243 | expect_neighbor_full("r1", "10.0.0.2") | |
244 | expect_neighbor_full("r1", "10.0.0.3") | |
245 | ||
246 | expect_neighbor_full("r2", "10.0.0.1") | |
247 | expect_neighbor_full("r2", "10.0.0.3") | |
248 | ||
249 | expect_neighbor_full("r3", "10.0.0.1") | |
250 | expect_neighbor_full("r3", "10.0.0.2") | |
251 | expect_neighbor_full("r3", "10.0.0.4") | |
252 | ||
253 | ||
254 | def compare_show_ipv6_vrf(rname, expected): | |
255 | """ | |
256 | Calls 'show ipv6 route' for router `rname` and compare the obtained | |
257 | result with the expected output. | |
258 | """ | |
259 | tgen = get_topogen() | |
260 | ||
261 | # Use the vtysh output, with some masking to make comparison easy | |
262 | vrf_name = "{0}-cust1".format(rname) | |
263 | current = topotest.ip6_route_zebra(tgen.gears[rname], vrf_name) | |
d01673b4 | 264 | |
0681195e | 265 | # Use just the 'O'spf lines of the output |
266 | linearr = [] | |
267 | for line in current.splitlines(): | |
268 | if re.match("^O", line): | |
269 | linearr.append(line) | |
270 | ||
271 | current = "\n".join(linearr) | |
272 | ||
273 | return topotest.difflines( | |
274 | topotest.normalize_text(current), | |
275 | topotest.normalize_text(expected), | |
276 | title1="Current output", | |
277 | title2="Expected output", | |
278 | ) | |
279 | ||
280 | ||
281 | def test_ospfv3_routingTable(): | |
282 | ||
283 | tgen = get_topogen() | |
284 | if tgen.routers_have_failure(): | |
285 | pytest.skip("skipped because of router(s) failure") | |
286 | ||
287 | # For debugging, uncomment the next line | |
288 | # tgen.mininet_cli() | |
289 | # Verify OSPFv3 Routing Table | |
290 | for router, rnode in tgen.routers().iteritems(): | |
291 | logger.info('Waiting for router "%s" convergence', router) | |
292 | ||
293 | # Load expected results from the command | |
294 | reffile = os.path.join(CWD, "{}/show_ipv6_vrf_route.ref".format(router)) | |
295 | expected = open(reffile).read() | |
296 | ||
297 | # Run test function until we get an result. Wait at most 60 seconds. | |
298 | test_func = partial(compare_show_ipv6_vrf, router, expected) | |
299 | result, diff = topotest.run_and_expect(test_func, "", count=120, wait=0.5) | |
300 | assert result, "OSPFv3 did not converge on {}:\n{}".format(router, diff) | |
301 | ||
302 | ||
303 | def test_linux_ipv6_kernel_routingTable(): | |
304 | ||
305 | # Required linux kernel version for this suite to run. | |
306 | result = required_linux_kernel_version("4.15") | |
307 | if result is not True: | |
308 | pytest.skip("Kernel requirements are not met") | |
309 | ||
310 | # iproute2 needs to support VRFs for this suite to run. | |
311 | if not iproute2_is_vrf_capable(): | |
312 | pytest.skip("Installed iproute2 version does not support VRFs") | |
313 | ||
314 | tgen = get_topogen() | |
315 | ||
316 | if tgen.routers_have_failure(): | |
317 | pytest.skip("skipped because of router(s) failure") | |
318 | ||
319 | # Verify Linux Kernel Routing Table | |
320 | logger.info("Verifying Linux IPv6 Kernel Routing Table") | |
321 | ||
322 | failures = 0 | |
323 | ||
324 | # Get a list of all current link-local addresses first as they change for | |
325 | # each run and we need to translate them | |
326 | linklocals = [] | |
327 | for i in range(1, 5): | |
328 | linklocals += tgen.net["r{}".format(i)].get_ipv6_linklocal() | |
329 | ||
330 | # Now compare the routing tables (after substituting link-local addresses) | |
331 | ||
332 | for i in range(1, 5): | |
333 | # Actual output from router | |
d01673b4 PR |
334 | actual = ( |
335 | tgen.gears["r{}".format(i)] | |
336 | .run("ip -6 route show vrf r{}-cust1".format(i)) | |
337 | .rstrip() | |
338 | ) | |
0681195e | 339 | if "nhid" in actual: |
340 | refTableFile = os.path.join(CWD, "r{}/ip_6_address.nhg.ref".format(i)) | |
341 | else: | |
342 | refTableFile = os.path.join(CWD, "r{}/ip_6_address.ref".format(i)) | |
343 | ||
344 | if os.path.isfile(refTableFile): | |
345 | expected = open(refTableFile).read().rstrip() | |
346 | # Fix newlines (make them all the same) | |
347 | expected = ("\n".join(expected.splitlines())).splitlines(1) | |
348 | ||
349 | # Mask out Link-Local mac addresses | |
350 | for ll in linklocals: | |
351 | actual = actual.replace(ll[1], "fe80::__(%s)__" % ll[0]) | |
352 | # Mask out protocol name or number | |
353 | actual = re.sub(r"[ ]+proto [0-9a-z]+ +", " proto XXXX ", actual) | |
354 | actual = re.sub(r"[ ]+nhid [0-9]+ +", " nhid XXXX ", actual) | |
355 | # Remove ff00::/8 routes (seen on some kernels - not from FRR) | |
356 | actual = re.sub(r"ff00::/8.*", "", actual) | |
357 | ||
358 | # Strip empty lines | |
359 | actual = actual.lstrip() | |
360 | actual = actual.rstrip() | |
361 | actual = re.sub(r" +", " ", actual) | |
362 | ||
363 | filtered_lines = [] | |
364 | for line in sorted(actual.splitlines()): | |
365 | if line.startswith("fe80::/64 ") or line.startswith( | |
366 | "unreachable fe80::/64 " | |
367 | ): | |
368 | continue | |
d01673b4 | 369 | if "anycast" in line: |
0681195e | 370 | continue |
d01673b4 | 371 | if "multicast" in line: |
0681195e | 372 | continue |
373 | filtered_lines.append(line) | |
374 | actual = "\n".join(filtered_lines).splitlines(1) | |
375 | ||
376 | # Print Actual table | |
377 | # logger.info("Router r%s table" % i) | |
378 | # for line in actual: | |
379 | # logger.info(line.rstrip()) | |
380 | ||
381 | # Generate Diff | |
382 | diff = topotest.get_textdiff( | |
383 | actual, | |
384 | expected, | |
385 | title1="actual OSPFv3 IPv6 routing table", | |
386 | title2="expected OSPFv3 IPv6 routing table", | |
387 | ) | |
388 | ||
389 | # Empty string if it matches, otherwise diff contains unified diff | |
390 | if diff: | |
391 | sys.stderr.write( | |
392 | "r%s failed Linux IPv6 Kernel Routing Table Check:\n%s\n" | |
393 | % (i, diff) | |
394 | ) | |
395 | failures += 1 | |
396 | else: | |
397 | logger.info("r%s ok" % i) | |
398 | ||
399 | assert failures == 0, ( | |
400 | "Linux Kernel IPv6 Routing Table verification failed for router r%s:\n%s" | |
401 | % (i, diff) | |
402 | ) | |
403 | ||
404 | ||
d01673b4 PR |
405 | def test_ospfv3_routingTable_write_multiplier(): |
406 | ||
407 | tgen = get_topogen() | |
408 | if tgen.routers_have_failure(): | |
409 | pytest.skip("skipped because of router(s) failure") | |
410 | ||
411 | # For debugging, uncomment the next line | |
412 | # tgen.mininet_cli() | |
413 | # Modify R1 write muliplier and reset the interfaces | |
414 | r1 = tgen.gears["r1"] | |
415 | ||
416 | r1.vtysh_cmd("conf t\nrouter ospf6 vrf r1-cust1 \n write-multiplier 100") | |
417 | r1.vtysh_cmd("clear ipv6 ospf interface r1-stubnet") | |
418 | r1.vtysh_cmd("clear ipv6 ospf interface r1-sw5") | |
419 | ||
420 | # Verify OSPFv3 Routing Table | |
421 | for router, rnode in tgen.routers().iteritems(): | |
422 | logger.info('Waiting for router "%s" convergence', router) | |
423 | ||
424 | # Load expected results from the command | |
425 | reffile = os.path.join(CWD, "{}/show_ipv6_vrf_route.ref".format(router)) | |
426 | expected = open(reffile).read() | |
427 | ||
428 | # Run test function until we get an result. Wait at most 60 seconds. | |
429 | test_func = partial(compare_show_ipv6_vrf, router, expected) | |
430 | result, diff = topotest.run_and_expect(test_func, "", count=120, wait=0.5) | |
431 | assert result, "OSPFv3 did not converge on {}:\n{}".format(router, diff) | |
432 | ||
433 | ||
0681195e | 434 | def test_shutdown_check_stderr(): |
435 | ||
436 | tgen = get_topogen() | |
437 | ||
438 | if tgen.routers_have_failure(): | |
439 | pytest.skip("skipped because of router(s) failure") | |
440 | ||
441 | if os.environ.get("TOPOTESTS_CHECK_STDERR") is None: | |
442 | logger.info( | |
443 | "SKIPPED final check on StdErr output: Disabled (TOPOTESTS_CHECK_STDERR undefined)\n" | |
444 | ) | |
445 | pytest.skip("Skipping test for Stderr output") | |
446 | ||
447 | net = tgen.net | |
448 | ||
449 | logger.info("\n\n** Verifying unexpected STDERR output from daemons") | |
450 | logger.info("******************************************") | |
451 | ||
452 | for i in range(1, 5): | |
453 | net["r%s" % i].stopRouter() | |
454 | log = net["r%s" % i].getStdErr("ospf6d") | |
455 | if log: | |
456 | logger.info("\nRouter r%s OSPF6d StdErr Log:\n%s" % (i, log)) | |
457 | log = net["r%s" % i].getStdErr("zebra") | |
458 | if log: | |
459 | logger.info("\nRouter r%s Zebra StdErr Log:\n%s" % (i, log)) | |
460 | ||
461 | ||
462 | def test_shutdown_check_memleak(): | |
463 | "Run the memory leak test and report results." | |
464 | ||
465 | if os.environ.get("TOPOTESTS_CHECK_MEMLEAK") is None: | |
466 | logger.info( | |
467 | "SKIPPED final check on Memory leaks: Disabled (TOPOTESTS_CHECK_MEMLEAK undefined)" | |
468 | ) | |
469 | pytest.skip("Skipping test for memory leaks") | |
470 | ||
471 | tgen = get_topogen() | |
472 | ||
473 | net = tgen.net | |
474 | ||
475 | for i in range(1, 5): | |
476 | net["r%s" % i].stopRouter() | |
477 | net["r%s" % i].report_memory_leaks( | |
478 | os.environ.get("TOPOTESTS_CHECK_MEMLEAK"), os.path.basename(__file__) | |
479 | ) | |
480 | ||
481 | ||
482 | if __name__ == "__main__": | |
483 | ||
484 | # To suppress tracebacks, either use the following pytest call or | |
485 | # add "--tb=no" to cli | |
486 | # retval = pytest.main(["-s", "--tb=no"]) | |
487 | ||
488 | retval = pytest.main(["-s"]) | |
489 | sys.exit(retval) |