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