]>
Commit | Line | Data |
---|---|---|
9f3e0f64 MW |
1 | #!/usr/bin/env python |
2 | ||
3 | # | |
4 | # test_ripng_topo1.py | |
5 | # Part of NetDEF Topology Tests | |
6 | # | |
7 | # Copyright (c) 2017 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_ripng_topo1.py: Test of RIPng Topology | |
27 | ||
28 | """ | |
29 | ||
30 | import os | |
31 | import re | |
32 | import sys | |
33 | import difflib | |
34 | import pytest | |
35 | import unicodedata | |
36 | from time import sleep | |
37 | ||
38 | from mininet.topo import Topo | |
39 | from mininet.net import Mininet | |
40 | from mininet.node import Node, OVSSwitch, Host | |
41 | from mininet.log import setLogLevel, info | |
42 | from mininet.cli import CLI | |
43 | from mininet.link import Intf | |
44 | ||
45 | from functools import partial | |
46 | ||
47 | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | |
48 | from lib import topotest | |
49 | ||
50 | fatal_error = "" | |
51 | ||
52 | ||
53 | ##################################################### | |
54 | ## | |
55 | ## Network Topology Definition | |
56 | ## | |
57 | ##################################################### | |
58 | ||
59 | class NetworkTopo(Topo): | |
60 | "RIPng Topology 1" | |
61 | ||
62 | def build(self, **_opts): | |
63 | ||
64 | # Setup Routers | |
65 | router = {} | |
66 | # | |
67 | # Setup Main Router | |
68 | router[1] = topotest.addRouter(self, 'r1') | |
69 | # | |
70 | # Setup RIPng Routers | |
71 | for i in range(2, 4): | |
72 | router[i] = topotest.addRouter(self, 'r%s' % i) | |
73 | ||
74 | # Setup Switches | |
75 | switch = {} | |
76 | # | |
77 | # On main router | |
78 | # First switch is for a dummy interface (for local network) | |
79 | switch[1] = self.addSwitch('sw1', cls=topotest.LegacySwitch) | |
80 | self.addLink(switch[1], router[1], intfName2='r1-eth0') | |
81 | # | |
82 | # Switches for RIPng | |
83 | # switch 2 switch is for connection to RIP router | |
84 | switch[2] = self.addSwitch('sw2', cls=topotest.LegacySwitch) | |
85 | self.addLink(switch[2], router[1], intfName2='r1-eth1') | |
86 | self.addLink(switch[2], router[2], intfName2='r2-eth0') | |
87 | # switch 3 is between RIP routers | |
88 | switch[3] = self.addSwitch('sw3', cls=topotest.LegacySwitch) | |
89 | self.addLink(switch[3], router[2], intfName2='r2-eth1') | |
90 | self.addLink(switch[3], router[3], intfName2='r3-eth1') | |
91 | # switch 4 is stub on remote RIP router | |
92 | switch[4] = self.addSwitch('sw4', cls=topotest.LegacySwitch) | |
93 | self.addLink(switch[4], router[3], intfName2='r3-eth0') | |
94 | ||
95 | ||
96 | ||
97 | ##################################################### | |
98 | ## | |
99 | ## Tests starting | |
100 | ## | |
101 | ##################################################### | |
102 | ||
103 | def setup_module(module): | |
104 | global topo, net | |
105 | ||
106 | print("\n\n** %s: Setup Topology" % module.__name__) | |
107 | print("******************************************\n") | |
108 | ||
109 | print("Cleanup old Mininet runs") | |
110 | os.system('sudo mn -c > /dev/null 2>&1') | |
111 | ||
112 | thisDir = os.path.dirname(os.path.realpath(__file__)) | |
113 | topo = NetworkTopo() | |
114 | ||
115 | net = Mininet(controller=None, topo=topo) | |
116 | net.start() | |
117 | ||
118 | # Starting Routers | |
119 | # | |
120 | for i in range(1, 4): | |
121 | net['r%s' % i].loadConf('zebra', '%s/r%s/zebra.conf' % (thisDir, i)) | |
122 | net['r%s' % i].loadConf('ripngd', '%s/r%s/ripngd.conf' % (thisDir, i)) | |
123 | net['r%s' % i].startRouter() | |
124 | ||
125 | # For debugging after starting Quagga/FRR daemons, uncomment the next line | |
126 | # CLI(net) | |
127 | ||
128 | ||
129 | def teardown_module(module): | |
130 | global net | |
131 | ||
132 | print("\n\n** %s: Shutdown Topology" % module.__name__) | |
133 | print("******************************************\n") | |
134 | ||
135 | # End - Shutdown network | |
136 | net.stop() | |
137 | ||
138 | ||
139 | def test_router_running(): | |
140 | global fatal_error | |
141 | global net | |
142 | ||
143 | # Skip if previous fatal error condition is raised | |
144 | if (fatal_error != ""): | |
145 | pytest.skip(fatal_error) | |
146 | ||
147 | print("\n\n** Check if FRR/Quagga is running on each Router node") | |
148 | print("******************************************\n") | |
149 | sleep(5) | |
150 | ||
151 | # Starting Routers | |
152 | for i in range(1, 4): | |
153 | fatal_error = net['r%s' % i].checkRouterRunning() | |
154 | assert fatal_error == "", fatal_error | |
155 | ||
156 | # For debugging after starting FRR/Quagga daemons, uncomment the next line | |
157 | # CLI(net) | |
158 | ||
159 | ||
160 | def test_converge_protocols(): | |
161 | global fatal_error | |
162 | global net | |
163 | ||
164 | # Skip if previous fatal error condition is raised | |
165 | if (fatal_error != ""): | |
166 | pytest.skip(fatal_error) | |
167 | ||
168 | thisDir = os.path.dirname(os.path.realpath(__file__)) | |
169 | ||
170 | print("\n\n** Waiting for protocols convergence") | |
171 | print("******************************************\n") | |
172 | ||
173 | # Not really implemented yet - just sleep 60 secs for now | |
174 | sleep(60) | |
175 | ||
7e7fc73b MW |
176 | # Make sure that all daemons are running |
177 | for i in range(1, 4): | |
178 | fatal_error = net['r%s' % i].checkRouterRunning() | |
179 | assert fatal_error == "", fatal_error | |
180 | ||
9f3e0f64 MW |
181 | # For debugging after starting FRR/Quagga daemons, uncomment the next line |
182 | #CLI(net) | |
183 | ||
184 | ||
185 | def test_ripng_status(): | |
186 | global fatal_error | |
187 | global net | |
188 | ||
189 | # Skip if previous fatal error condition is raised | |
190 | if (fatal_error != ""): | |
191 | pytest.skip(fatal_error) | |
192 | ||
193 | thisDir = os.path.dirname(os.path.realpath(__file__)) | |
194 | ||
195 | # Verify RIP Status | |
b2764f90 | 196 | print("\n\n** Verifying RIPng status") |
9f3e0f64 MW |
197 | print("******************************************\n") |
198 | failures = 0 | |
199 | for i in range(1, 4): | |
200 | refTableFile = '%s/r%s/ripng_status.ref' % (thisDir, i) | |
201 | if os.path.isfile(refTableFile): | |
202 | # Read expected result from file | |
203 | expected = open(refTableFile).read().rstrip() | |
204 | # Fix newlines (make them all the same) | |
205 | expected = ('\n'.join(expected.splitlines()) + '\n').splitlines(1) | |
206 | ||
207 | # Actual output from router | |
208 | actual = net['r%s' % i].cmd('vtysh -c "show ipv6 ripng status" 2> /dev/null').rstrip() | |
209 | # Mask out Link-Local mac address portion. They are random... | |
210 | actual = re.sub(r" fe80::[0-9a-f:]+", " fe80::XXXX:XXXX:XXXX:XXXX", actual) | |
211 | # Drop time in next due | |
212 | actual = re.sub(r"in [0-9]+ seconds", "in XX seconds", actual) | |
213 | # Drop time in last update | |
214 | actual = re.sub(r" [0-2][0-9]:[0-5][0-9]:[0-5][0-9]", " XX:XX:XX", actual) | |
215 | # Fix newlines (make them all the same) | |
216 | actual = ('\n'.join(actual.splitlines()) + '\n').splitlines(1) | |
217 | ||
218 | # Generate Diff | |
219 | diff = ''.join(difflib.context_diff(actual, expected, | |
220 | fromfile="actual IPv6 RIPng status", | |
221 | tofile="expected IPv6 RIPng status")) | |
222 | ||
223 | # Empty string if it matches, otherwise diff contains unified diff | |
224 | if diff: | |
225 | sys.stderr.write('r%s failed IPv6 RIPng status check:\n%s\n' % (i, diff)) | |
226 | failures += 1 | |
227 | else: | |
228 | print("r%s ok" % i) | |
229 | ||
230 | assert failures == 0, "IPv6 RIPng status failed for router r%s:\n%s" % (i, diff) | |
231 | ||
7e7fc73b MW |
232 | # Make sure that all daemons are running |
233 | for i in range(1, 4): | |
234 | fatal_error = net['r%s' % i].checkRouterRunning() | |
235 | assert fatal_error == "", fatal_error | |
236 | ||
9f3e0f64 MW |
237 | # For debugging after starting FRR/Quagga daemons, uncomment the next line |
238 | # CLI(net) | |
239 | ||
240 | ||
241 | def test_ripng_routes(): | |
242 | global fatal_error | |
243 | global net | |
244 | ||
245 | # Skip if previous fatal error condition is raised | |
246 | if (fatal_error != ""): | |
247 | pytest.skip(fatal_error) | |
248 | ||
249 | thisDir = os.path.dirname(os.path.realpath(__file__)) | |
250 | ||
251 | # Verify RIPng Status | |
b2764f90 | 252 | print("\n\n** Verifying RIPng routes") |
9f3e0f64 MW |
253 | print("******************************************\n") |
254 | failures = 0 | |
255 | for i in range(1, 4): | |
256 | refTableFile = '%s/r%s/show_ipv6_ripng.ref' % (thisDir, i) | |
257 | if os.path.isfile(refTableFile): | |
258 | # Read expected result from file | |
259 | expected = open(refTableFile).read().rstrip() | |
260 | # Fix newlines (make them all the same) | |
261 | expected = ('\n'.join(expected.splitlines()) + '\n').splitlines(1) | |
262 | ||
263 | # Actual output from router | |
264 | actual = net['r%s' % i].cmd('vtysh -c "show ipv6 ripng" 2> /dev/null').rstrip() | |
265 | # Drop Time | |
266 | actual = re.sub(r" [0-9][0-9]:[0-5][0-9]", " XX:XX", actual) | |
267 | # Mask out Link-Local mac address portion. They are random... | |
268 | actual = re.sub(r" fe80::[0-9a-f: ]+", " fe80::XXXX:XXXX:XXXX:XXXX ", actual) | |
269 | # Remove trailing spaces on all lines | |
270 | actual = '\n'.join([line.rstrip() for line in actual.splitlines()]) | |
271 | ||
272 | # Fix newlines (make them all the same) | |
273 | actual = ('\n'.join(actual.splitlines()) + '\n').splitlines(1) | |
274 | ||
275 | # Generate Diff | |
276 | diff = ''.join(difflib.context_diff(actual, expected, | |
277 | fromfile="actual SHOW IPv6 RIPng", | |
278 | tofile="expected SHOW IPv6 RIPng")) | |
279 | ||
280 | # Empty string if it matches, otherwise diff contains unified diff | |
281 | if diff: | |
282 | sys.stderr.write('r%s failed SHOW IPv6 RIPng check:\n%s\n' % (i, diff)) | |
283 | failures += 1 | |
284 | else: | |
285 | print("r%s ok" % i) | |
286 | ||
287 | assert failures == 0, "SHOW IPv6 RIPng failed for router r%s:\n%s" % (i, diff) | |
288 | ||
7e7fc73b MW |
289 | # Make sure that all daemons are running |
290 | for i in range(1, 4): | |
291 | fatal_error = net['r%s' % i].checkRouterRunning() | |
292 | assert fatal_error == "", fatal_error | |
293 | ||
9f3e0f64 MW |
294 | # For debugging after starting FRR/Quagga daemons, uncomment the next line |
295 | # CLI(net) | |
296 | ||
297 | ||
298 | def test_zebra_ipv6_routingTable(): | |
299 | global fatal_error | |
300 | global net | |
301 | ||
302 | # Skip if previous fatal error condition is raised | |
303 | if (fatal_error != ""): | |
304 | pytest.skip(fatal_error) | |
305 | ||
306 | thisDir = os.path.dirname(os.path.realpath(__file__)) | |
307 | ||
308 | # Verify OSPFv3 Routing Table | |
b2764f90 | 309 | print("\n\n** Verifying Zebra IPv6 Routing Table") |
9f3e0f64 MW |
310 | print("******************************************\n") |
311 | failures = 0 | |
312 | for i in range(1, 4): | |
313 | refTableFile = '%s/r%s/show_ipv6_route.ref' % (thisDir, i) | |
314 | if os.path.isfile(refTableFile): | |
315 | # Read expected result from file | |
316 | expected = open(refTableFile).read().rstrip() | |
317 | # Fix newlines (make them all the same) | |
318 | expected = ('\n'.join(expected.splitlines()) + '\n').splitlines(1) | |
319 | ||
320 | # Actual output from router | |
321 | actual = net['r%s' % i].cmd('vtysh -c "show ipv6 route" 2> /dev/null | grep "^R"').rstrip() | |
322 | # Mask out Link-Local mac address portion. They are random... | |
323 | actual = re.sub(r" fe80::[0-9a-f:]+", " fe80::XXXX:XXXX:XXXX:XXXX", actual) | |
324 | # Drop timers on end of line (older Quagga Versions) | |
325 | actual = re.sub(r", [0-2][0-9]:[0-5][0-9]:[0-5][0-9]", "", actual) | |
326 | # Fix newlines (make them all the same) | |
327 | actual = ('\n'.join(actual.splitlines()) + '\n').splitlines(1) | |
328 | ||
329 | # Generate Diff | |
330 | diff = ''.join(difflib.context_diff(actual, expected, | |
331 | fromfile="actual Zebra IPv6 routing table", | |
332 | tofile="expected Zebra IPv6 routing table")) | |
333 | ||
334 | # Empty string if it matches, otherwise diff contains unified diff | |
335 | if diff: | |
336 | sys.stderr.write('r%s failed Zebra IPv6 Routing Table Check:\n%s\n' % (i, diff)) | |
337 | failures += 1 | |
338 | else: | |
339 | print("r%s ok" % i) | |
340 | ||
341 | assert failures == 0, "Zebra IPv6 Routing Table verification failed for router r%s:\n%s" % (i, diff) | |
342 | ||
7e7fc73b MW |
343 | # Make sure that all daemons are running |
344 | for i in range(1, 4): | |
345 | fatal_error = net['r%s' % i].checkRouterRunning() | |
346 | assert fatal_error == "", fatal_error | |
347 | ||
9f3e0f64 MW |
348 | # For debugging after starting FRR/Quagga daemons, uncomment the next line |
349 | # CLI(net) | |
350 | ||
351 | ||
352 | def test_shutdown_check_stderr(): | |
353 | global fatal_error | |
354 | global net | |
355 | ||
356 | # Skip if previous fatal error condition is raised | |
357 | if (fatal_error != ""): | |
358 | pytest.skip(fatal_error) | |
359 | ||
360 | if os.environ.get('TOPOTESTS_CHECK_STDERR') is None: | |
50c40bde MW |
361 | print("SKIPPED final check on StdErr output: Disabled (TOPOTESTS_CHECK_STDERR undefined)\n") |
362 | pytest.skip('Skipping test for Stderr output') | |
9f3e0f64 MW |
363 | |
364 | thisDir = os.path.dirname(os.path.realpath(__file__)) | |
365 | ||
b2764f90 | 366 | print("\n\n** Verifying unexpected STDERR output from daemons") |
9f3e0f64 MW |
367 | print("******************************************\n") |
368 | ||
369 | net['r1'].stopRouter() | |
370 | ||
371 | log = net['r1'].getStdErr('ripngd') | |
372 | print("\nRIPngd StdErr Log:\n" + log) | |
373 | log = net['r1'].getStdErr('zebra') | |
374 | print("\nZebra StdErr Log:\n" + log) | |
375 | ||
376 | ||
50c40bde MW |
377 | def test_shutdown_check_memleak(): |
378 | global fatal_error | |
379 | global net | |
380 | ||
381 | # Skip if previous fatal error condition is raised | |
382 | if (fatal_error != ""): | |
383 | pytest.skip(fatal_error) | |
384 | ||
385 | if os.environ.get('TOPOTESTS_CHECK_MEMLEAK') is None: | |
386 | print("SKIPPED final check on Memory leaks: Disabled (TOPOTESTS_CHECK_MEMLEAK undefined)\n") | |
387 | pytest.skip('Skipping test for memory leaks') | |
388 | ||
389 | thisDir = os.path.dirname(os.path.realpath(__file__)) | |
390 | ||
391 | net['r1'].stopRouter() | |
392 | net['r1'].report_memory_leaks(os.environ.get('TOPOTESTS_CHECK_MEMLEAK'), os.path.basename(__file__)) | |
393 | ||
394 | ||
9f3e0f64 MW |
395 | if __name__ == '__main__': |
396 | ||
397 | setLogLevel('info') | |
398 | # To suppress tracebacks, either use the following pytest call or add "--tb=no" to cli | |
399 | # retval = pytest.main(["-s", "--tb=no"]) | |
400 | retval = pytest.main(["-s"]) | |
401 | sys.exit(retval) |