]> git.proxmox.com Git - mirror_frr.git/blame - tests/topotests/bgp_multiview_topo1/test_bgp_multiview_topo1.py
bgp_multiview_topo1: Fix limit to create coredumps on quagga crashes
[mirror_frr.git] / tests / topotests / bgp_multiview_topo1 / test_bgp_multiview_topo1.py
CommitLineData
586e15c4
MW
1#!/usr/bin/env python
2
3#
4# test_bgp_multiview_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"""
26test_bgp_multiview_topo1.py: Simple Quagga Route-Server Test
27
28See Topology Diagram bgp-routeserver-1.pdf
29"""
30
31import os
32import re
33import sys
34import difflib
35import StringIO
2cda38e4
MW
36import glob
37import subprocess
586e15c4
MW
38
39from mininet.topo import Topo
40from mininet.net import Mininet
41from mininet.node import Node, OVSSwitch, Host
42from mininet.log import setLogLevel, info
43from mininet.cli import CLI
44from mininet.link import Intf
45
46from functools import partial
47from time import sleep
48
49import pytest
50
2cda38e4
MW
51fatal_error = ""
52
586e15c4
MW
53def int2dpid(dpid):
54 "Converting Integer to DPID"
55
56 try:
57 dpid = hex(dpid)[2:]
58 dpid = '0'*(16-len(dpid))+dpid
59 return dpid
60 except IndexError:
61 raise Exception('Unable to derive default datapath ID - '
62 'please either specify a dpid or use a '
63 'canonical switch name such as s23.')
64
65class LinuxRouter(Node):
66 "A Node with IPv4/IPv6 forwarding enabled."
67
68 def config(self, **params):
69 super(LinuxRouter, self).config(**params)
70 # Enable forwarding on the router
71 self.cmd('sysctl net.ipv4.ip_forward=1')
72 self.cmd('sysctl net.ipv6.conf.all.forwarding=1')
73 def terminate(self):
74 """
75 Terminate generic LinuxRouter Mininet instance
76 """
77 self.cmd('sysctl net.ipv4.ip_forward=0')
78 self.cmd('sysctl net.ipv6.conf.all.forwarding=0')
79 super(LinuxRouter, self).terminate()
80
81class QuaggaRouter(Node):
82 "A Node with IPv4/IPv6 forwarding enabled and Quagga as Routing Engine"
83
84 def config(self, **params):
85 super(QuaggaRouter, self).config(**params)
86 # Enable forwarding on the router
87 self.cmd('sysctl net.ipv4.ip_forward=1')
88 self.cmd('sysctl net.ipv6.conf.all.forwarding=1')
2cda38e4
MW
89 # Enable coredumps
90 self.cmd('sysctl kernel.core_uses_pid=1')
91 self.cmd('sysctl fs.suid_dumpable=2')
92 self.cmd("sysctl kernel.core_pattern=/tmp/%s_%%e_core-sig_%%s-pid_%%p.dmp" % self.name)
5ead87b4 93 self.cmd('ulimit -c unlimited')
2cda38e4 94 # Set ownership of config files
586e15c4
MW
95 self.cmd('chown quagga:quaggavty /etc/quagga')
96 self.daemons = {'zebra': 0, 'ripd': 0, 'ripngd': 0, 'ospfd': 0,
97 'ospf6d': 0, 'isisd': 0, 'bgpd': 0, 'pimd': 0}
98 def terminate(self):
99 # Delete Running Quagga Daemons
100 rundaemons = self.cmd('ls -1 /var/run/quagga/*.pid')
101 for d in StringIO.StringIO(rundaemons):
102 self.cmd('kill -7 `cat %s`' % d.rstrip())
103 self.waitOutput()
104 # Disable forwarding
105 self.cmd('sysctl net.ipv4.ip_forward=0')
106 self.cmd('sysctl net.ipv6.conf.all.forwarding=0')
107 super(QuaggaRouter, self).terminate()
108 def removeIPs(self):
109 for interface in self.intfNames():
110 self.cmd('ip address flush', interface)
111 def loadConf(self, daemon, source=None):
112 # print "Daemons before:", self.daemons
113 if daemon in self.daemons.keys():
114 self.daemons[daemon] = 1
115 if source is None:
116 self.cmd('touch /etc/quagga/%s.conf' % daemon)
117 self.waitOutput()
118 else:
119 self.cmd('cp %s /etc/quagga/%s.conf' % (source, daemon))
120 self.waitOutput()
121 self.cmd('chmod 640 /etc/quagga/%s.conf' % daemon)
122 self.waitOutput()
123 self.cmd('chown quagga:quagga /etc/quagga/%s.conf' % daemon)
124 self.waitOutput()
125 else:
126 print("No daemon %s known" % daemon)
127 # print "Daemons after:", self.daemons
128 def startQuagga(self):
129 # Disable integrated-vtysh-config
130 self.cmd('echo "no service integrated-vtysh-config" > /etc/quagga/vtysh.conf')
131 with open("/etc/quagga/vtysh.conf", "w") as vtyshfile:
132 vtyshfile.write('no service integrated-vtysh-config')
133 self.cmd('chown quagga:quaggavty /etc/quagga/vtysh.conf')
2cda38e4
MW
134 # Try to find relevant old logfiles in /tmp and delete them
135 map(os.remove, glob.glob("/tmp/*%s*.log" % self.name))
136 # Remove old core files
137 map(os.remove, glob.glob("/tmp/%s*.dmp" % self.name))
586e15c4
MW
138 # Remove IP addresses from OS first - we have them in zebra.conf
139 self.removeIPs()
140 # Start Zebra first
141 if self.daemons['zebra'] == 1:
142 self.cmd('/usr/lib/quagga/zebra -d')
143 self.waitOutput()
144 print('%s: zebra started' % self)
145 sleep(1)
146 # Fix Link-Local Addresses
147 # Somehow (on Mininet only), Zebra removes the IPv6 Link-Local addresses on start. Fix this
148 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')
149 # Now start all the other daemons
150 for daemon in self.daemons:
151 if (self.daemons[daemon] == 1) and (daemon != 'zebra'):
152 self.cmd('/usr/lib/quagga/%s -d' % daemon)
153 self.waitOutput()
154 print('%s: %s started' % (self, daemon))
155 def checkQuaggaRunning(self):
2cda38e4
MW
156 global fatal_error
157
586e15c4
MW
158 daemonsRunning = self.cmd('vtysh -c "show log" | grep "Logging configuration for"')
159 for daemon in self.daemons:
2cda38e4
MW
160 if (self.daemons[daemon] == 1) and not (daemon in daemonsRunning):
161 sys.stderr.write("%s: Daemon %s not running\n" % (self.name, daemon))
162 # Look for core file
163 corefiles = glob.glob("/tmp/%s_%s_core*.dmp" % (self.name, daemon))
164 if (len(corefiles) > 0):
5ead87b4
MW
165 backtrace = subprocess.check_output(["gdb /usr/lib/quagga/%s %s --batch -ex bt 2> /dev/null" % (daemon, corefiles[0])], shell=True)
166 sys.stderr.write("\n%s: %s crashed. Core file found - Backtrace follows:\n" % (self.name, daemon))
2cda38e4
MW
167 sys.stderr.write("%s\n" % backtrace)
168 else:
169 # No core found - If we find matching logfile in /tmp, then print last 20 lines from it.
170 if os.path.isfile("/tmp/%s-%s.log" % (self.name, daemon)):
5ead87b4
MW
171 log_tail = subprocess.check_output(["tail -n20 /tmp/%s-%s.log 2> /dev/null" % (self.name, daemon)], shell=True)
172 sys.stderr.write("\nFrom quagga %s %s log file:\n" % (self.name, daemon))
2cda38e4
MW
173 sys.stderr.write("%s\n" % log_tail)
174
175 fatal_error = "%s: Daemon %s not running" % (self.name, daemon)
176 assert False, "%s: Daemon %s not running" % (self.name, daemon)
586e15c4
MW
177
178class LegacySwitch(OVSSwitch):
179 "A Legacy Switch without OpenFlow"
180
181 def __init__(self, name, **params):
182 OVSSwitch.__init__(self, name, failMode='standalone', **params)
183 self.switchIP = None
184
185
186#####################################################
187##
188## Network Topology Definition
189##
190#####################################################
191
192class NetworkTopo(Topo):
193 "A LinuxRouter connecting three IP subnets"
194
195 def build(self, **_opts):
196
197 quaggaPrivateDirs = ['/etc/quagga',
198 '/var/run/quagga',
199 '/var/log',
200 '/var/run/ssh']
201 exabgpPrivateDirs = ['/etc/exabgp',
202 '/var/run/exabgp',
203 '/var/log']
204
205 # Setup Routers
206 quagga = {}
207 for i in range(1, 2):
208 quagga[i] = self.addNode('r%s' % i, cls=QuaggaRouter,
209 privateDirs=quaggaPrivateDirs)
210
211 # Setup Provider BGP peers
212 peer = {}
213 for i in range(1, 9):
214 peer[i] = self.addHost('peer%s' % i, ip='172.16.1.%s/24' % i,
215 defaultRoute='via 172.16.1.254',
216 privateDirs=exabgpPrivateDirs)
217
218 # Setup Switches
219 switch = {}
220 # First switch is for a dummy interface (for local network)
221 switch[0] = self.addSwitch('sw0', cls=LegacySwitch)
222 self.addLink(switch[0], quagga[1], intfName2='r1-stub')
223 # Second switch is for connection to all peering routers
224 switch[1] = self.addSwitch('sw1', cls=LegacySwitch)
225 self.addLink(switch[1], quagga[1], intfName2='r1-eth0')
226 for j in range(1, 9):
227 self.addLink(switch[1], peer[j], intfName2='peer%s-eth0' % j)
228
229
230#####################################################
231##
232## Tests starting
233##
234#####################################################
235
236def setup_module(module):
237 global topo, net
238
239 print("\n\n** %s: Setup Topology" % module.__name__)
240 print("******************************************\n")
241
242 print("Cleanup old Mininet runs")
243 os.system('sudo mn -c > /dev/null 2>&1')
244
245 thisDir = os.path.dirname(os.path.realpath(__file__))
246 topo = NetworkTopo()
247
248 net = Mininet(controller=None, topo=topo)
249 net.start()
250
251 # Starting Routers
252 for i in range(1, 2):
253 net['r%s' % i].loadConf('zebra', '%s/r%s/zebra.conf' % (thisDir, i))
254 net['r%s' % i].loadConf('bgpd', '%s/r%s/bgpd.conf' % (thisDir, i))
255 net['r%s' % i].startQuagga()
256
257 # Starting PE Hosts and init ExaBGP on each of them
258 print('*** Starting BGP on all 8 Peers in 10s')
259 sleep(10)
260 for i in range(1, 9):
261 net['peer%s' % i].cmd('cp %s/exabgp.env /etc/exabgp/exabgp.env' % thisDir)
262 net['peer%s' % i].cmd('cp %s/peer%s/* /etc/exabgp/' % (thisDir, i))
263 net['peer%s' % i].cmd('chmod 644 /etc/exabgp/*')
264 net['peer%s' % i].cmd('chmod 755 /etc/exabgp/*.py')
265 net['peer%s' % i].cmd('chown -R exabgp:exabgp /etc/exabgp')
266 net['peer%s' % i].cmd('exabgp -e /etc/exabgp/exabgp.env /etc/exabgp/exabgp.cfg')
267 print('peer%s' % i),
268 print('')
269
270 # For debugging after starting Quagga daemons, uncomment the next line
271 # CLI(net)
272
273def teardown_module(module):
274 global net
275
276 print("\n\n** %s: Shutdown Topology" % module.__name__)
277 print("******************************************\n")
278
279 # Shutdown - clean up everything
280 print('*** Killing BGP on Peer routers')
281 # Killing ExaBGP
282 for i in range(1, 9):
283 net['peer%s' % i].cmd('kill `cat /var/run/exabgp/exabgp.pid`')
284
285 # End - Shutdown network
286 net.stop()
287
288def test_quagga_running():
2cda38e4 289 global fatal_error
586e15c4
MW
290 global net
291
2cda38e4
MW
292 # Skip if previous fatal error condition is raised
293 if (fatal_error != ""):
294 pytest.skip(fatal_error)
295
586e15c4
MW
296 print("\n\n** Check if Quagga is running on each Router node")
297 print("******************************************\n")
298 sleep(5)
299
300 # Starting Routers
301 for i in range(1, 2):
302 net['r%s' % i].checkQuaggaRunning()
303
304def test_bgp_converge():
305 "Check for BGP converged on all peers and BGP views"
306
2cda38e4 307 global fatal_error
586e15c4
MW
308 global net
309
2cda38e4
MW
310 # Skip if previous fatal error condition is raised
311 if (fatal_error != ""):
312 pytest.skip(fatal_error)
313
586e15c4
MW
314 # Wait for BGP to converge (All Neighbors in either Full or TwoWay State)
315 print("\n\n** Verify for BGP to converge")
316 print("******************************************\n")
317 timeout = 60
318 while timeout > 0:
319 print("Timeout in %s: " % timeout),
320 sys.stdout.flush()
321 # Look for any node not yet converged
322 for i in range(1, 2):
323 for view in range(1, 4):
324 notConverged = net['r%s' % i].cmd('vtysh -c "show ip bgp view %s summary" 2> /dev/null | grep ^[0-9] | grep -v " 11$"' % view)
325 if notConverged:
326 print('Waiting for r%s, view %s' % (i, view))
327 sys.stdout.flush()
328 break
329 if notConverged:
330 break
331 if notConverged:
332 sleep(5)
333 timeout -= 5
334 else:
335 print('Done')
336 break
337 else:
338 # Bail out with error if a router fails to converge
339 bgpStatus = net['r%s' % i].cmd('show ip bgp view %s summary"')
586e15c4
MW
340 assert False, "BGP did not converge:\n%s" % bgpStatus
341
342 print("BGP converged.")
343
344 # if timeout < 60:
345 # # Only wait if we actually went through a convergence
346 # print("\nwaiting 15s for routes to populate")
347 # sleep(15)
348
349 # For debugging after starting Quagga daemons, uncomment the next line
350 # CLI(net)
351
352def test_bgp_routingTable():
2cda38e4 353 global fatal_error
586e15c4
MW
354 global net
355
2cda38e4
MW
356 # Skip if previous fatal error condition is raised
357 if (fatal_error != ""):
358 pytest.skip(fatal_error)
359
586e15c4
MW
360 thisDir = os.path.dirname(os.path.realpath(__file__))
361
362 # Verify OSPFv3 Routing Table
363 print("\n\n** Verifing BGP Routing Tables")
364 print("******************************************\n")
365 failures = 0
366 for i in range(1, 2):
367 for view in range(1, 4):
368 refTableFile = '%s/r%s/show_ip_bgp_view_%s.ref' % (thisDir, i, view)
369 if os.path.isfile(refTableFile):
370 # Read expected result from file
371 expected = open(refTableFile).read().rstrip()
372 # Fix newlines (make them all the same)
373 expected = ('\n'.join(expected.splitlines()) + '\n').splitlines(1)
374
375 # Actual output from router
376 actual = net['r%s' % i].cmd('vtysh -c "show ip bgp view %s" 2> /dev/null' % view).rstrip()
377
378 # Fix inconsitent spaces between 0.99.24 and newer versions of Quagga...
379 actual = re.sub('0 0', '0 0', actual)
380 actual = re.sub(r'([0-9]) 32768', r'\1 32768', actual)
381 # Remove summary line (changed recently)
382 actual = re.sub(r'Total number.*', '', actual)
383 actual = re.sub(r'Displayed.*', '', actual)
384 actual = actual.rstrip()
385
386 # Fix newlines (make them all the same)
387 actual = ('\n'.join(actual.splitlines()) + '\n').splitlines(1)
388
389 # Generate Diff
390 diff = ''.join(difflib.unified_diff(actual, expected))
391 # Empty string if it matches, otherwise diff contains unified diff
392
393 if diff:
394 sys.stderr.write('r%s failed Routing Table Check for view %s:\n%s\n'
395 % (i, view, diff))
396 failures += 1
397 else:
398 print("r%s ok" % i)
399
400 assert failures == 0, "Routing Table verification failed for router r%s, view %s:\n%s" % (i, view, diff)
401
402 # For debugging after starting Quagga daemons, uncomment the next line
403 # CLI(net)
404
405
406if __name__ == '__main__':
407
408 setLogLevel('info')
2cda38e4
MW
409 # To suppress tracebacks, either use the following pytest call or add "--tb=no" to cli
410 # retval = pytest.main(["-s", "--tb=no"])
586e15c4
MW
411 retval = pytest.main(["-s"])
412 sys.exit(retval)