]> git.proxmox.com Git - mirror_frr.git/blob - tests/topotests/lib/topotest.py
Added optional output for memleaks at the end of test. Enable them by setting the...
[mirror_frr.git] / tests / topotests / lib / topotest.py
1 #!/usr/bin/env python
2
3 #
4 # topotest.py
5 # Library of helper functions for 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 import os
26 import re
27 import sys
28 import glob
29 import StringIO
30 import subprocess
31 import platform
32
33 from mininet.topo import Topo
34 from mininet.net import Mininet
35 from mininet.node import Node, OVSSwitch, Host
36 from mininet.log import setLogLevel, info
37 from mininet.cli import CLI
38 from mininet.link import Intf
39
40 from time import sleep
41
42 def int2dpid(dpid):
43 "Converting Integer to DPID"
44
45 try:
46 dpid = hex(dpid)[2:]
47 dpid = '0'*(16-len(dpid))+dpid
48 return dpid
49 except IndexError:
50 raise Exception('Unable to derive default datapath ID - '
51 'please either specify a dpid or use a '
52 'canonical switch name such as s23.')
53
54 def addRouter(topo, name):
55 "Adding a FreeRangeRouter (or Quagga) to Topology"
56
57 MyPrivateDirs = ['/etc/frr',
58 '/etc/quagga',
59 '/var/run/frr',
60 '/var/run/quagga',
61 '/var/log']
62 return topo.addNode(name, cls=Router, privateDirs=MyPrivateDirs)
63
64 class LinuxRouter(Node):
65 "A Node with IPv4/IPv6 forwarding enabled."
66
67 def config(self, **params):
68 super(LinuxRouter, self).config(**params)
69 # Enable forwarding on the router
70 self.cmd('sysctl net.ipv4.ip_forward=1')
71 self.cmd('sysctl net.ipv6.conf.all.forwarding=1')
72 def terminate(self):
73 """
74 Terminate generic LinuxRouter Mininet instance
75 """
76 self.cmd('sysctl net.ipv4.ip_forward=0')
77 self.cmd('sysctl net.ipv6.conf.all.forwarding=0')
78 super(LinuxRouter, self).terminate()
79
80 class Router(Node):
81 "A Node with IPv4/IPv6 forwarding enabled and Quagga as Routing Engine"
82
83 def config(self, **params):
84 super(Router, self).config(**params)
85
86 # Check if Quagga or FRR is installed
87 if os.path.isfile('/usr/lib/frr/zebra'):
88 self.routertype = 'frr'
89 elif os.path.isfile('/usr/lib/quagga/zebra'):
90 self.routertype = 'quagga'
91 else:
92 raise Exception('No FRR or Quagga found in ususal location')
93 # Enable forwarding on the router
94 self.cmd('sysctl net.ipv4.ip_forward=1')
95 self.cmd('sysctl net.ipv6.conf.all.forwarding=1')
96 # Enable coredumps
97 self.cmd('sysctl kernel.core_uses_pid=1')
98 self.cmd('sysctl fs.suid_dumpable=2')
99 self.cmd("sysctl kernel.core_pattern=/tmp/%s_%%e_core-sig_%%s-pid_%%p.dmp" % self.name)
100 self.cmd('ulimit -c unlimited')
101 # Set ownership of config files
102 self.cmd('chown %s:%svty /etc/%s' % (self.routertype, self.routertype, self.routertype))
103 self.daemons = {'zebra': 0, 'ripd': 0, 'ripngd': 0, 'ospfd': 0,
104 'ospf6d': 0, 'isisd': 0, 'bgpd': 0, 'pimd': 0,
105 'ldpd': 0}
106 def terminate(self):
107 # Delete Running Quagga or FRR Daemons
108 self.stopRouter()
109 # rundaemons = self.cmd('ls -1 /var/run/%s/*.pid' % self.routertype)
110 # for d in StringIO.StringIO(rundaemons):
111 # self.cmd('kill -7 `cat %s`' % d.rstrip())
112 # self.waitOutput()
113 # Disable forwarding
114 self.cmd('sysctl net.ipv4.ip_forward=0')
115 self.cmd('sysctl net.ipv6.conf.all.forwarding=0')
116 super(Router, self).terminate()
117 def stopRouter(self):
118 # Stop Running Quagga or FRR Daemons
119 rundaemons = self.cmd('ls -1 /var/run/%s/*.pid' % self.routertype)
120 if rundaemons is not None:
121 for d in StringIO.StringIO(rundaemons):
122 self.cmd('kill -7 `cat %s`' % d.rstrip())
123 self.waitOutput()
124 def removeIPs(self):
125 for interface in self.intfNames():
126 self.cmd('ip address flush', interface)
127 def loadConf(self, daemon, source=None):
128 # print "Daemons before:", self.daemons
129 if daemon in self.daemons.keys():
130 self.daemons[daemon] = 1
131 if source is None:
132 self.cmd('touch /etc/%s/%s.conf' % (self.routertype, daemon))
133 self.waitOutput()
134 else:
135 self.cmd('cp %s /etc/%s/%s.conf' % (source, self.routertype, daemon))
136 self.waitOutput()
137 self.cmd('chmod 640 /etc/%s/%s.conf' % (self.routertype, daemon))
138 self.waitOutput()
139 self.cmd('chown %s:%s /etc/%s/%s.conf' % (self.routertype, self.routertype, self.routertype, daemon))
140 self.waitOutput()
141 else:
142 print("No daemon %s known" % daemon)
143 # print "Daemons after:", self.daemons
144 def startRouter(self):
145 # Disable integrated-vtysh-config
146 with open('/etc/%s/vtysh.conf' % self.routertype, "w") as vtyshfile:
147 vtyshfile.write('no service integrated-vtysh-config')
148 self.cmd('chown %s:%svty /etc/%s/vtysh.conf' % (self.routertype, self.routertype, self.routertype))
149 # Try to find relevant old logfiles in /tmp and delete them
150 map(os.remove, glob.glob("/tmp/*%s*.log" % self.name))
151 # Remove old core files
152 map(os.remove, glob.glob("/tmp/%s*.dmp" % self.name))
153 # Remove IP addresses from OS first - we have them in zebra.conf
154 self.removeIPs()
155 # If ldp is used, check for LDP to be compiled and Linux Kernel to be 4.5 or higher
156 # No error - but return message and skip all the tests
157 if self.daemons['ldpd'] == 1:
158 if not os.path.isfile('/usr/lib/%s/ldpd' % self.routertype):
159 print("LDP Test, but no ldpd compiled or installed")
160 return "LDP Test, but no ldpd compiled or installed"
161 kernel_version = re.search(r'([0-9]+\.[0-9]+).*', platform.release())
162 if kernel_version:
163 if float(kernel_version.group(1)) < 4.5:
164 print("LDP Test need Linux Kernel 4.5 minimum")
165 return "LDP Test need Linux Kernel 4.5 minimum"
166 # Add mpls modules to kernel if we use LDP
167 if self.daemons['ldpd'] == 1:
168 self.cmd('/sbin/modprobe mpls-router')
169 self.cmd('/sbin/modprobe mpls-iptunnel')
170 self.cmd('echo 100000 > /proc/sys/net/mpls/platform_labels')
171 # Init done - now restarting daemons
172 self.restartRouter()
173 return ""
174 def restartRouter(self):
175 # Starts actuall daemons without init (ie restart)
176 # Start Zebra first
177 if self.daemons['zebra'] == 1:
178 # self.cmd('/usr/lib/%s/zebra -d' % self.routertype)
179 self.cmd('/usr/lib/%s/zebra > /tmp/%s-zebra.out 2> /tmp/%s-zebra.err &' % (self.routertype, self.name, self.name))
180 self.waitOutput()
181 print('%s: %s zebra started' % (self, self.routertype))
182 sleep(1)
183 # Fix Link-Local Addresses
184 # Somehow (on Mininet only), Zebra removes the IPv6 Link-Local addresses on start. Fix this
185 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')
186 # Now start all the other daemons
187 for daemon in self.daemons:
188 if (self.daemons[daemon] == 1) and (daemon != 'zebra'):
189 # self.cmd('/usr/lib/%s/%s -d' % (self.routertype, daemon))
190 self.cmd('/usr/lib/%s/%s > /tmp/%s-%s.out 2> /tmp/%s-%s.err &' % (self.routertype, daemon, self.name, daemon, self.name, daemon))
191 self.waitOutput()
192 print('%s: %s %s started' % (self, self.routertype, daemon))
193 def getStdErr(self, daemon):
194 return self.getLog('err', daemon)
195 def getStdOut(self, daemon):
196 return self.getLog('out', daemon)
197 def getLog(self, log, daemon):
198 return self.cmd('cat /tmp/%s-%s.%s' % (self.name, daemon, log) )
199 def checkRouterRunning(self):
200 global fatal_error
201
202 daemonsRunning = self.cmd('vtysh -c "show log" | grep "Logging configuration for"')
203 for daemon in self.daemons:
204 if (self.daemons[daemon] == 1) and not (daemon in daemonsRunning):
205 sys.stderr.write("%s: Daemon %s not running\n" % (self.name, daemon))
206 # Look for core file
207 corefiles = glob.glob("/tmp/%s_%s_core*.dmp" % (self.name, daemon))
208 if (len(corefiles) > 0):
209 backtrace = subprocess.check_output(["gdb /usr/lib/%s/%s %s --batch -ex bt 2> /dev/null" % (self.routertype, daemon, corefiles[0])], shell=True)
210 sys.stderr.write("\n%s: %s crashed. Core file found - Backtrace follows:\n" % (self.name, daemon))
211 sys.stderr.write("%s\n" % backtrace)
212 else:
213 # No core found - If we find matching logfile in /tmp, then print last 20 lines from it.
214 if os.path.isfile("/tmp/%s-%s.log" % (self.name, daemon)):
215 log_tail = subprocess.check_output(["tail -n20 /tmp/%s-%s.log 2> /dev/null" % (self.name, daemon)], shell=True)
216 sys.stderr.write("\nFrom %s %s %s log file:\n" % (self.routertype, self.name, daemon))
217 sys.stderr.write("%s\n" % log_tail)
218
219 return "%s: Daemon %s not running" % (self.name, daemon)
220 return ""
221 def get_ipv6_linklocal(self):
222 "Get LinkLocal Addresses from interfaces"
223
224 linklocal = []
225
226 ifaces = self.cmd('ip -6 address')
227 # Fix newlines (make them all the same)
228 ifaces = ('\n'.join(ifaces.splitlines()) + '\n').splitlines()
229 interface=""
230 ll_per_if_count=0
231 for line in ifaces:
232 m = re.search('[0-9]+: ([^:@]+)[@if0-9:]+ <', line)
233 if m:
234 interface = m.group(1)
235 ll_per_if_count = 0
236 m = re.search('inet6 (fe80::[0-9a-f]+:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+)[/0-9]* scope link', line)
237 if m:
238 local = m.group(1)
239 ll_per_if_count += 1
240 if (ll_per_if_count > 1):
241 linklocal += [["%s-%s" % (interface, ll_per_if_count), local]]
242 else:
243 linklocal += [[interface, local]]
244 return linklocal
245
246 class LegacySwitch(OVSSwitch):
247 "A Legacy Switch without OpenFlow"
248
249 def __init__(self, name, **params):
250 OVSSwitch.__init__(self, name, failMode='standalone', **params)
251 self.switchIP = None
252