]> git.proxmox.com Git - mirror_frr.git/blob - tests/topotests/lib/topotest.py
lib: Add function to check for daemon available (i.e. LDPd) and function to return...
[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 FRRouter (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 self.cmd('echo "no service integrated-vtysh-config" >> /etc/%s/vtysh.conf' % self.routertype)
147 self.cmd('chown %s:%svty /etc/%s/vtysh.conf' % (self.routertype, self.routertype, self.routertype))
148 # Try to find relevant old logfiles in /tmp and delete them
149 map(os.remove, glob.glob("/tmp/*%s*.log" % self.name))
150 # Remove old core files
151 map(os.remove, glob.glob("/tmp/%s*.dmp" % self.name))
152 # Remove IP addresses from OS first - we have them in zebra.conf
153 self.removeIPs()
154 # If ldp is used, check for LDP to be compiled and Linux Kernel to be 4.5 or higher
155 # No error - but return message and skip all the tests
156 if self.daemons['ldpd'] == 1:
157 if not os.path.isfile('/usr/lib/%s/ldpd' % self.routertype):
158 print("LDP Test, but no ldpd compiled or installed")
159 return "LDP Test, but no ldpd compiled or installed"
160 kernel_version = re.search(r'([0-9]+\.[0-9]+).*', platform.release())
161 if kernel_version:
162 if float(kernel_version.group(1)) < 4.5:
163 print("LDP Test need Linux Kernel 4.5 minimum")
164 return "LDP Test need Linux Kernel 4.5 minimum"
165 # Add mpls modules to kernel if we use LDP
166 if self.daemons['ldpd'] == 1:
167 self.cmd('/sbin/modprobe mpls-router')
168 self.cmd('/sbin/modprobe mpls-iptunnel')
169 self.cmd('echo 100000 > /proc/sys/net/mpls/platform_labels')
170 # Init done - now restarting daemons
171 self.restartRouter()
172 return ""
173 def restartRouter(self):
174 # Starts actuall daemons without init (ie restart)
175 # Start Zebra first
176 if self.daemons['zebra'] == 1:
177 # self.cmd('/usr/lib/%s/zebra -d' % self.routertype)
178 self.cmd('/usr/lib/%s/zebra > /tmp/%s-zebra.out 2> /tmp/%s-zebra.err &' % (self.routertype, self.name, self.name))
179 self.waitOutput()
180 print('%s: %s zebra started' % (self, self.routertype))
181 sleep(1)
182 # Fix Link-Local Addresses
183 # Somehow (on Mininet only), Zebra removes the IPv6 Link-Local addresses on start. Fix this
184 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')
185 # Now start all the other daemons
186 for daemon in self.daemons:
187 if (self.daemons[daemon] == 1) and (daemon != 'zebra'):
188 # self.cmd('/usr/lib/%s/%s -d' % (self.routertype, daemon))
189 self.cmd('/usr/lib/%s/%s > /tmp/%s-%s.out 2> /tmp/%s-%s.err &' % (self.routertype, daemon, self.name, daemon, self.name, daemon))
190 self.waitOutput()
191 print('%s: %s %s started' % (self, self.routertype, daemon))
192 def getStdErr(self, daemon):
193 return self.getLog('err', daemon)
194 def getStdOut(self, daemon):
195 return self.getLog('out', daemon)
196 def getLog(self, log, daemon):
197 return self.cmd('cat /tmp/%s-%s.%s' % (self.name, daemon, log) )
198 def checkRouterRunning(self):
199 global fatal_error
200
201 daemonsRunning = self.cmd('vtysh -c "show log" | grep "Logging configuration for"')
202 for daemon in self.daemons:
203 if (self.daemons[daemon] == 1) and not (daemon in daemonsRunning):
204 sys.stderr.write("%s: Daemon %s not running\n" % (self.name, daemon))
205 # Look for core file
206 corefiles = glob.glob("/tmp/%s_%s_core*.dmp" % (self.name, daemon))
207 if (len(corefiles) > 0):
208 backtrace = subprocess.check_output(["gdb /usr/lib/%s/%s %s --batch -ex bt 2> /dev/null" % (self.routertype, daemon, corefiles[0])], shell=True)
209 sys.stderr.write("\n%s: %s crashed. Core file found - Backtrace follows:\n" % (self.name, daemon))
210 sys.stderr.write("%s\n" % backtrace)
211 else:
212 # No core found - If we find matching logfile in /tmp, then print last 20 lines from it.
213 if os.path.isfile("/tmp/%s-%s.log" % (self.name, daemon)):
214 log_tail = subprocess.check_output(["tail -n20 /tmp/%s-%s.log 2> /dev/null" % (self.name, daemon)], shell=True)
215 sys.stderr.write("\nFrom %s %s %s log file:\n" % (self.routertype, self.name, daemon))
216 sys.stderr.write("%s\n" % log_tail)
217
218 return "%s: Daemon %s not running" % (self.name, daemon)
219 return ""
220 def get_ipv6_linklocal(self):
221 "Get LinkLocal Addresses from interfaces"
222
223 linklocal = []
224
225 ifaces = self.cmd('ip -6 address')
226 # Fix newlines (make them all the same)
227 ifaces = ('\n'.join(ifaces.splitlines()) + '\n').splitlines()
228 interface=""
229 ll_per_if_count=0
230 for line in ifaces:
231 m = re.search('[0-9]+: ([^:@]+)[@if0-9:]+ <', line)
232 if m:
233 interface = m.group(1)
234 ll_per_if_count = 0
235 m = re.search('inet6 (fe80::[0-9a-f]+:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+)[/0-9]* scope link', line)
236 if m:
237 local = m.group(1)
238 ll_per_if_count += 1
239 if (ll_per_if_count > 1):
240 linklocal += [["%s-%s" % (interface, ll_per_if_count), local]]
241 else:
242 linklocal += [[interface, local]]
243 return linklocal
244 def daemon_available(self, daemon):
245 "Check if specified daemon is installed (and for ldp if kernel supports MPLS)"
246
247 if not os.path.isfile('/usr/lib/%s/%s' % (self.routertype, daemon)):
248 return False
249 if (daemon == 'ldpd'):
250 kernel_version = re.search(r'([0-9]+\.[0-9]+).*', platform.release())
251 if kernel_version:
252 if float(kernel_version.group(1)) < 4.5:
253 return False
254 else:
255 return False
256 return True
257 def get_routertype(self):
258 "Return the type of Router (frr or quagga)"
259
260 return self.routertype
261
262
263 class LegacySwitch(OVSSwitch):
264 "A Legacy Switch without OpenFlow"
265
266 def __init__(self, name, **params):
267 OVSSwitch.__init__(self, name, failMode='standalone', **params)
268 self.switchIP = None
269