]> git.proxmox.com Git - mirror_frr.git/blob - tests/topotests/ospf6-topo1/test_ospf6_topo1.py
09dc538bafc86a2d554e500cb9add081ab7678b8
[mirror_frr.git] / tests / topotests / ospf6-topo1 / test_ospf6_topo1.py
1 #!/usr/bin/env python
2
3 #
4 # test_ospf6_topo1.py
5 # Part of NetDEF Topology Tests
6 #
7 # Copyright (c) 2016 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_topo1.py:
27
28 -----\
29 SW1 - Stub Net 1 SW2 - Stub Net 2 \
30 fc00:1:1:1::/64 fc00:2:2:2::/64 \
31 \___________________/ \___________________/ |
32 | | |
33 | | |
34 | ::1 | ::2 |
35 +---------+---------+ +---------+---------+ |
36 | R1 | | R2 | |
37 | Quagga | | Quagga | |
38 | Rtr-ID: 10.0.0.1 | | Rtr-ID: 10.0.0.2 | |
39 +---------+---------+ +---------+---------+ |
40 | ::1 | ::2 \
41 \______ ___________/ OSPFv3
42 \ / Area 0.0.0.0
43 \ / /
44 ~~~~~~~~~~~~~~~~~~ |
45 ~~ SW5 ~~ |
46 ~~ Switch ~~ |
47 ~~ fc00:A:A:A::/64 ~~ |
48 ~~~~~~~~~~~~~~~~~~ |
49 | /---- |
50 | ::3 | SW3 - Stub Net 3 |
51 +---------+---------+ /-+ fc00:3:3:3::/64 |
52 | R3 | / | /
53 | Quagga +--/ \---- /
54 | Rtr-ID: 10.0.0.3 | ::3 ___________/
55 +---------+---------+ \
56 | ::3 \
57 | \
58 ~~~~~~~~~~~~~~~~~~ |
59 ~~ SW6 ~~ |
60 ~~ Switch ~~ |
61 ~~ fc00:B:B:B::/64 ~~ \
62 ~~~~~~~~~~~~~~~~~~ OSPFv3
63 | Area 0.0.0.1
64 | ::4 /
65 +---------+---------+ /---- |
66 | R4 | | SW4 - Stub Net 4 |
67 | Quagga +------+ fc00:4:4:4::/64 |
68 | Rtr-ID: 10.0.0.4 | ::4 | /
69 +-------------------+ \---- /
70 -----/
71 """
72
73 import os
74 import re
75 import sys
76 import difflib
77 import StringIO
78 import glob
79 import subprocess
80
81 from mininet.topo import Topo
82 from mininet.net import Mininet
83 from mininet.node import Node, OVSSwitch, Host
84 from mininet.log import setLogLevel, info
85 from mininet.cli import CLI
86
87 from functools import partial
88 from time import sleep
89
90 import pytest
91
92 fatal_error = ""
93
94 def int2dpid(dpid):
95 "Converting Integer to DPID"
96
97 try:
98 dpid = hex(dpid)[2:]
99 dpid = '0'*(16-len(dpid))+dpid
100 return dpid
101 except IndexError:
102 raise Exception('Unable to derive default datapath ID - '
103 'please either specify a dpid or use a '
104 'canonical switch name such as s23.')
105
106 class LinuxRouter(Node):
107 "A Node with IPv4/IPv6 forwarding enabled."
108
109 def config(self, **params):
110 super(LinuxRouter, self).config(**params)
111 # Enable forwarding on the router
112 self.cmd('sysctl net.ipv4.ip_forward=1')
113 self.cmd('sysctl net.ipv6.conf.all.forwarding=1')
114 def terminate(self):
115 """
116 Terminate generic LinuxRouter Mininet instance
117 """
118 self.cmd('sysctl net.ipv4.ip_forward=0')
119 self.cmd('sysctl net.ipv6.conf.all.forwarding=0')
120 super(LinuxRouter, self).terminate()
121
122 class QuaggaRouter(Node):
123 "A Node with IPv4/IPv6 forwarding enabled and Quagga as Routing Engine"
124
125 def config(self, **params):
126 super(QuaggaRouter, self).config(**params)
127 # Enable forwarding on the router
128 self.cmd('sysctl net.ipv4.ip_forward=1')
129 self.cmd('sysctl net.ipv6.conf.all.forwarding=1')
130 # Enable coredumps
131 self.cmd('sysctl kernel.core_uses_pid=1')
132 self.cmd('sysctl fs.suid_dumpable=2')
133 self.cmd("sysctl kernel.core_pattern=/tmp/%s_%%e_core-sig_%%s-pid_%%p.dmp" % self.name)
134 self.cmd('ulimit -c unlimited')
135 # Set ownership of config files
136 self.cmd('chown quagga:quaggavty /etc/quagga')
137 self.daemons = {'zebra': 0, 'ripd': 0, 'ripngd': 0, 'ospfd': 0,
138 'ospf6d': 0, 'isisd': 0, 'bgpd': 0, 'pimd': 0}
139 def terminate(self):
140 # Delete Running Quagga Daemons
141 rundaemons = self.cmd('ls -1 /var/run/quagga/*.pid')
142 for d in StringIO.StringIO(rundaemons):
143 self.cmd('kill -7 `cat %s`' % d.rstrip())
144 self.waitOutput()
145 # Disable forwarding
146 self.cmd('sysctl net.ipv4.ip_forward=0')
147 self.cmd('sysctl net.ipv6.conf.all.forwarding=0')
148 super(QuaggaRouter, self).terminate()
149 def removeIPs(self):
150 for interface in self.intfNames():
151 self.cmd('ip address flush', interface)
152 def loadConf(self, daemon, source=None):
153 # print "Daemons before:", self.daemons
154 if daemon in self.daemons.keys():
155 self.daemons[daemon] = 1
156 if source is None:
157 self.cmd('touch /etc/quagga/%s.conf' % daemon)
158 self.waitOutput()
159 else:
160 self.cmd('cp %s /etc/quagga/%s.conf' % (source, daemon))
161 self.waitOutput()
162 self.cmd('chmod 640 /etc/quagga/%s.conf' % daemon)
163 self.waitOutput()
164 self.cmd('chown quagga:quagga /etc/quagga/%s.conf' % daemon)
165 self.waitOutput()
166 else:
167 print("No daemon %s known" % daemon)
168 # print "Daemons after:", self.daemons
169 def startQuagga(self):
170 # Disable integrated-vtysh-config
171 self.cmd('echo "no service integrated-vtysh-config" > /etc/quagga/vtysh.conf')
172 with open("/etc/quagga/vtysh.conf", "w") as vtyshfile:
173 vtyshfile.write('no service integrated-vtysh-config')
174 self.cmd('chown quagga:quaggavty /etc/quagga/vtysh.conf')
175 # Try to find relevant old logfiles in /tmp and delete them
176 map(os.remove, glob.glob("/tmp/*%s*.log" % self.name))
177 # Remove old core files
178 map(os.remove, glob.glob("/tmp/%s*.dmp" % self.name))
179 # Remove IP addresses from OS first - we have them in zebra.conf
180 self.removeIPs()
181 # Start Zebra first
182 if self.daemons['zebra'] == 1:
183 self.cmd('/usr/lib/quagga/zebra -d')
184 self.waitOutput()
185 print('%s: zebra started' % self)
186 sleep(1)
187 # Fix Link-Local Addresses
188 # Somehow (on Mininet only), Zebra removes the IPv6 Link-Local addresses on start. Fix this
189 self.cmd('for i in `ls /sys/class/net/` ; do mac=`cat /sys/class/net/$i/address`; IFS=\':\'; set $mac; unset IFS; ip address add dev $i scope link fe80::$(printf %02x $((0x$1 ^ 2)))$2:${3}ff:fe$4:$5$6/64; done')
190 # Now start all the other daemons
191 for daemon in self.daemons:
192 if (self.daemons[daemon] == 1) and (daemon != 'zebra'):
193 self.cmd('/usr/lib/quagga/%s -d' % daemon)
194 self.waitOutput()
195 print('%s: %s started' % (self, daemon))
196 def checkQuaggaRunning(self):
197 global fatal_error
198
199 daemonsRunning = self.cmd('vtysh -c "show log" | grep "Logging configuration for"')
200 for daemon in self.daemons:
201 if (self.daemons[daemon] == 1) and not (daemon in daemonsRunning):
202 sys.stderr.write("%s: Daemon %s not running\n" % (self.name, daemon))
203 # Look for core file
204 corefiles = glob.glob("/tmp/%s_%s_core*.dmp" % (self.name, daemon))
205 if (len(corefiles) > 0):
206 backtrace = subprocess.check_output(["gdb /usr/lib/quagga/%s %s --batch -ex bt 2> /dev/null" % (daemon, corefiles[0])], shell=True)
207 sys.stderr.write("\n%s: %s crashed. Core file found - Backtrace follows:\n" % (self.name, daemon))
208 sys.stderr.write("%s\n" % backtrace)
209 else:
210 # No core found - If we find matching logfile in /tmp, then print last 20 lines from it.
211 if os.path.isfile("/tmp/%s-%s.log" % (self.name, daemon)):
212 log_tail = subprocess.check_output(["tail -n20 /tmp/%s-%s.log 2> /dev/null" % (self.name, daemon)], shell=True)
213 sys.stderr.write("\nFrom quagga %s %s log file:\n" % (self.name, daemon))
214 sys.stderr.write("%s\n" % log_tail)
215
216 fatal_error = "%s: Daemon %s not running" % (self.name, daemon)
217 assert False, "%s: Daemon %s not running" % (self.name, daemon)
218 def get_ipv6_linklocal(self):
219 "Get LinkLocal Addresses from interfaces"
220
221 linklocal = []
222
223 ifaces = self.cmd('ip -6 address')
224 # Fix newlines (make them all the same)
225 ifaces = ('\n'.join(ifaces.splitlines()) + '\n').splitlines()
226 interface=""
227 ll_per_if_count=0
228 for line in ifaces:
229 m = re.search('[0-9]+: ([^:@]+)[@if0-9:]+ <', line)
230 if m:
231 interface = m.group(1)
232 ll_per_if_count = 0
233 m = re.search('inet6 (fe80::[0-9a-f]+:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+)[/0-9]* scope link', line)
234 if m:
235 local = m.group(1)
236 ll_per_if_count += 1
237 if (ll_per_if_count > 1):
238 linklocal += [["%s-%s" % (interface, ll_per_if_count), local]]
239 else:
240 linklocal += [[interface, local]]
241 return linklocal
242
243
244 class LegacySwitch(OVSSwitch):
245 "A Legacy Switch without OpenFlow"
246
247 def __init__(self, name, **params):
248 OVSSwitch.__init__(self, name, failMode='standalone', **params)
249 self.switchIP = None
250
251 #####################################################
252 ##
253 ## Network Topology Definition
254 ##
255 #####################################################
256
257 class NetworkTopo(Topo):
258 "A Quagga Topology with direct peering router and IXP connection"
259
260 def build(self, **_opts):
261
262 quaggaPrivateDirs = ['/etc/quagga',
263 '/var/run/quagga',
264 '/var/log']
265 #
266 # Define Switches first
267 #
268 switch = {}
269 for i in range(1, 7):
270 switch[i] = self.addSwitch('SW%s' % i, dpid=int2dpid(i),
271 cls=LegacySwitch)
272 #
273 # Define Quagga Routers
274 #
275 router = {}
276 for i in range(1, 5):
277 router[i] = self.addNode('r%s' % i, cls=QuaggaRouter,
278 privateDirs=quaggaPrivateDirs)
279 #
280 # Wire up the switches and routers
281 #
282 # Stub nets
283 for i in range(1, 5):
284 self.addLink(switch[i], router[i], intfName2='r%s-stubnet' % i)
285 # Switch 5
286 self.addLink(switch[5], router[1], intfName2='r1-sw5')
287 self.addLink(switch[5], router[2], intfName2='r2-sw5')
288 self.addLink(switch[5], router[3], intfName2='r3-sw5')
289 # Switch 6
290 self.addLink(switch[6], router[3], intfName2='r3-sw6')
291 self.addLink(switch[6], router[4], intfName2='r4-sw6')
292
293
294 #####################################################
295 ##
296 ## Tests starting
297 ##
298 #####################################################
299
300 def setup_module(module):
301 global topo, net
302
303 print("\n\n** %s: Setup Topology" % module.__name__)
304 print("******************************************\n")
305
306 print("Cleanup old Mininet runs")
307 os.system('sudo mn -c > /dev/null 2>&1')
308
309 thisDir = os.path.dirname(os.path.realpath(__file__))
310 topo = NetworkTopo()
311
312 net = Mininet(controller=None, topo=topo)
313 net.start()
314
315 # For debugging after starting net, but before starting Quagga, uncomment the next line
316 # CLI(net)
317
318 # Starting Routers
319 for i in range(1, 5):
320 net['r%s' % i].loadConf('zebra', '%s/r%s/zebra.conf' % (thisDir, i))
321 net['r%s' % i].loadConf('ospf6d', '%s/r%s/ospf6d.conf' % (thisDir, i))
322 net['r%s' % i].startQuagga()
323
324 # For debugging after starting Quagga daemons, uncomment the next line
325 # CLI(net)
326
327
328 def teardown_module(module):
329 global net
330
331 print("\n\n** %s: Shutdown Topology" % module.__name__)
332 print("******************************************\n")
333
334 # End - Shutdown network
335 net.stop()
336
337
338 def test_quagga_running():
339 global fatal_error
340 global net
341
342 # Skip if previous fatal error condition is raised
343 if (fatal_error != ""):
344 pytest.skip(fatal_error)
345
346 print("\n\n** Check if Quagga is running on each Router node")
347 print("******************************************\n")
348 sleep(5)
349
350 # Starting Routers
351 for i in range(1, 5):
352 net['r%s' % i].checkQuaggaRunning()
353
354
355 def test_ospf6_converged():
356 global fatal_error
357 global net
358
359 # Skip if previous fatal error condition is raised
360 if (fatal_error != ""):
361 pytest.skip(fatal_error)
362
363 # Wait for OSPF6 to converge (All Neighbors in either Full or TwoWay State)
364 print("\n\n** Verify OSPF6 daemons to converge")
365 print("******************************************\n")
366 timeout = 60
367 while timeout > 0:
368 print("Timeout in %s: " % timeout),
369 sys.stdout.flush()
370 # Look for any node not yet converged
371 for i in range(1, 5):
372 notConverged = net['r%s' % i].cmd('vtysh -c "show ipv6 ospf neigh" 2> /dev/null | grep ^[0-9] | grep -v Full')
373 if notConverged:
374 print('Waiting for r%s' %i)
375 sys.stdout.flush()
376 break
377 if notConverged:
378 sleep(5)
379 timeout -= 5
380 else:
381 print('Done')
382 print(notConverged)
383 break
384 else:
385 # Bail out with error if a router fails to converge
386 ospfStatus = net['r%s' % i].cmd('vtysh -c "show ipv6 ospf neigh"')
387 fatal_error = "OSPFv6 did not converge"
388 assert False, "OSPFv6 did not converge:\n%s" % ospfStatus
389
390 print("OSPFv3 converged.")
391
392 if timeout < 60:
393 # Only wait if we actually went through a convergence
394 print("\nwaiting 15s for routes to populate")
395 sleep(15)
396
397 def test_ospfv3_routingTable():
398 global fatal_error
399 global net
400
401 # Skip if previous fatal error condition is raised
402 if (fatal_error != ""):
403 pytest.skip(fatal_error)
404
405 thisDir = os.path.dirname(os.path.realpath(__file__))
406
407 # Verify OSPFv3 Routing Table
408 print("\n\n** Verifing OSPFv3 Routing Table")
409 print("******************************************\n")
410 failures = 0
411 for i in range(1, 5):
412 refTableFile = '%s/r%s/show_ipv6_route.ref' % (thisDir, i)
413 if os.path.isfile(refTableFile):
414 # Read expected result from file
415 expected = open(refTableFile).read().rstrip()
416 # Fix newlines (make them all the same)
417 expected = ('\n'.join(expected.splitlines()) + '\n').splitlines(1)
418
419 # Actual output from router
420 actual = net['r%s' % i].cmd('vtysh -c "show ipv6 route" 2> /dev/null | grep "^O"').rstrip()
421 # Mask out Link-Local mac address portion. They are random...
422 actual = re.sub(r" fe80::[0-9a-f:]+", " fe80::XXXX:XXXX:XXXX:XXXX", actual)
423 # Drop timers on end of line (older Quagga Versions)
424 actual = re.sub(r", [0-2][0-9]:[0-5][0-9]:[0-5][0-9]", "", actual)
425 # Fix newlines (make them all the same)
426 actual = ('\n'.join(actual.splitlines()) + '\n').splitlines(1)
427
428 # Generate Diff
429 diff = ''.join(difflib.context_diff(actual, expected,
430 fromfile="actual OSPFv3 IPv6 routing table",
431 tofile="expected OSPFv3 IPv6 routing table"))
432
433 # Empty string if it matches, otherwise diff contains unified diff
434 if diff:
435 sys.stderr.write('r%s failed OSPFv3 (IPv6) Routing Table Check:\n%s\n' % (i, diff))
436 failures += 1
437 else:
438 print("r%s ok" % i)
439
440 assert failures == 0, "OSPFv3 (IPv6) Routing Table verification failed for router r%s:\n%s" % (i, diff)
441
442 # For debugging after starting Quagga daemons, uncomment the next line
443 # CLI(net)
444
445 def test_linux_ipv6_kernel_routingTable():
446 global fatal_error
447 global net
448
449 # Skip if previous fatal error condition is raised
450 if (fatal_error != ""):
451 pytest.skip(fatal_error)
452
453 thisDir = os.path.dirname(os.path.realpath(__file__))
454
455 # Verify Linux Kernel Routing Table
456 print("\n\n** Verifing Linux IPv6 Kernel Routing Table")
457 print("******************************************\n")
458 failures = 0
459
460 # Get a list of all current link-local addresses first as they change for
461 # each run and we need to translate them
462 linklocals = []
463 for i in range(1, 5):
464 linklocals += net['r%s' % i].get_ipv6_linklocal()
465
466 # Now compare the routing tables (after substituting link-local addresses)
467 for i in range(1, 5):
468 refTableFile = '%s/r%s/ip_6_address.ref' % (thisDir, i)
469 if os.path.isfile(refTableFile):
470
471 expected = open(refTableFile).read().rstrip()
472 # Fix newlines (make them all the same)
473 expected = ('\n'.join(expected.splitlines())).splitlines(1)
474
475 # Actual output from router
476 actual = net['r%s' % i].cmd('ip -6 route').rstrip()
477 # Mask out Link-Local mac addresses
478 for ll in linklocals:
479 actual = actual.replace(ll[1], "fe80::__(%s)__" % ll[0])
480
481 # Fix newlines (make them all the same)
482 actual = ('\n'.join(actual.splitlines())).splitlines(1)
483
484 # Print Actual table
485 # print("Router r%s table" % i)
486 # for line in actual:
487 # print(line.rstrip())
488
489 # Generate Diff
490 diff = ''.join(difflib.context_diff(actual, expected,
491 fromfile="actual IPv6 kernel routing table",
492 tofile="expected IPv6 kernel routing table"))
493
494 # Empty string if it matches, otherwise diff contains unified diff
495 if diff:
496 sys.stderr.write('r%s failed Linux IPv6 Kernel Routing Table Check:\n%s\n' % (i, diff))
497 failures += 1
498 else:
499 print("r%s ok" % i)
500
501 assert failures == 0, "Linux Kernel IPv6 Routing Table verification failed for router r%s:\n%s" % (i, diff)
502
503 # For debugging after starting Quagga daemons, uncomment the next line
504 # CLI(net)
505
506
507 if __name__ == '__main__':
508
509 setLogLevel('info')
510 # To suppress tracebacks, either use the following pytest call or add "--tb=no" to cli
511 # retval = pytest.main(["-s", "--tb=no"])
512 retval = pytest.main(["-s"])
513 sys.exit(retval)