blob: 6a3dc14561ac5a201584bda3bfdc7d432ec82746 [file] [log] [blame]
carlosmscabralf40ecd12013-02-01 18:15:58 -02001"Utility functions for Mininet."
2
3from mininet.log import output, info, error, warn
4
5from time import sleep
6from resource import setrlimit, RLIMIT_NPROC, RLIMIT_NOFILE
7from select import poll, POLLIN
8from subprocess import call, check_call, Popen, PIPE, STDOUT
9import re
10from fcntl import fcntl, F_GETFL, F_SETFL
11from os import O_NONBLOCK
12import os
13
14# Command execution support
15
16def run( cmd ):
17 """Simple interface to subprocess.call()
18 cmd: list of command params"""
19 return call( cmd.split( ' ' ) )
20
21def checkRun( cmd ):
22 """Simple interface to subprocess.check_call()
23 cmd: list of command params"""
24 return check_call( cmd.split( ' ' ) )
25
26# pylint doesn't understand explicit type checking
27# pylint: disable-msg=E1103
28
29def oldQuietRun( *cmd ):
30 """Run a command, routing stderr to stdout, and return the output.
31 cmd: list of command params"""
32 if len( cmd ) == 1:
33 cmd = cmd[ 0 ]
34 if isinstance( cmd, str ):
35 cmd = cmd.split( ' ' )
36 popen = Popen( cmd, stdout=PIPE, stderr=STDOUT )
37 # We can't use Popen.communicate() because it uses
38 # select(), which can't handle
39 # high file descriptor numbers! poll() can, however.
40 out = ''
41 readable = poll()
42 readable.register( popen.stdout )
43 while True:
44 while readable.poll():
45 data = popen.stdout.read( 1024 )
46 if len( data ) == 0:
47 break
48 out += data
49 popen.poll()
50 if popen.returncode is not None:
51 break
52 return out
53
54
55# This is a bit complicated, but it enables us to
56# monitor command output as it is happening
57
58def errRun( *cmd, **kwargs ):
59 """Run a command and return stdout, stderr and return code
60 cmd: string or list of command and args
61 stderr: STDOUT to merge stderr with stdout
62 shell: run command using shell
63 echo: monitor output to console"""
64 # Allow passing in a list or a string
65 if len( cmd ) == 1:
66 cmd = cmd[ 0 ]
67 if isinstance( cmd, str ):
68 cmd = cmd.split( ' ' )
69 cmd = [ str( arg ) for arg in cmd ]
70 # By default we separate stderr, don't run in a shell, and don't echo
71 stderr = kwargs.get( 'stderr', PIPE )
72 shell = kwargs.get( 'shell', False )
73 echo = kwargs.get( 'echo', False )
74 if echo:
75 # cmd goes to stderr, output goes to stdout
76 info( cmd, '\n' )
77 popen = Popen( cmd, stdout=PIPE, stderr=stderr, shell=shell )
78 # We use poll() because select() doesn't work with large fd numbers,
79 # and thus communicate() doesn't work either
80 out, err = '', ''
81 poller = poll()
82 poller.register( popen.stdout, POLLIN )
83 fdtofile = { popen.stdout.fileno(): popen.stdout }
84 outDone, errDone = False, True
85 if popen.stderr:
86 fdtofile[ popen.stderr.fileno() ] = popen.stderr
87 poller.register( popen.stderr, POLLIN )
88 errDone = False
89 while not outDone or not errDone:
90 readable = poller.poll()
91 for fd, _event in readable:
92 f = fdtofile[ fd ]
93 data = f.read( 1024 )
94 if echo:
95 output( data )
96 if f == popen.stdout:
97 out += data
98 if data == '':
99 outDone = True
100 elif f == popen.stderr:
101 err += data
102 if data == '':
103 errDone = True
104 returncode = popen.wait()
105 return out, err, returncode
106
107def errFail( *cmd, **kwargs ):
108 "Run a command using errRun and raise exception on nonzero exit"
109 out, err, ret = errRun( *cmd, **kwargs )
110 if ret:
111 raise Exception( "errFail: %s failed with return code %s: %s"
112 % ( cmd, ret, err ) )
113 return out, err, ret
114
115def quietRun( cmd, **kwargs ):
116 "Run a command and return merged stdout and stderr"
117 return errRun( cmd, stderr=STDOUT, **kwargs )[ 0 ]
118
119# pylint: enable-msg=E1103
120# pylint: disable-msg=E1101
121
122def isShellBuiltin( cmd ):
123 "Return True if cmd is a bash builtin."
124 if isShellBuiltin.builtIns is None:
125 isShellBuiltin.builtIns = quietRun( 'bash -c enable' )
126 space = cmd.find( ' ' )
127 if space > 0:
128 cmd = cmd[ :space]
129 return cmd in isShellBuiltin.builtIns
130
131isShellBuiltin.builtIns = None
132
133# pylint: enable-msg=E1101
134
135# Interface management
136#
137# Interfaces are managed as strings which are simply the
138# interface names, of the form 'nodeN-ethM'.
139#
140# To connect nodes, we create a pair of veth interfaces, and then place them
141# in the pair of nodes that we want to communicate. We then update the node's
142# list of interfaces and connectivity map.
143#
144# For the kernel datapath, switch interfaces
145# live in the root namespace and thus do not have to be
146# explicitly moved.
147
148def makeIntfPair( intf1, intf2 ):
149 """Make a veth pair connecting intf1 and intf2.
150 intf1: string, interface
151 intf2: string, interface
152 returns: success boolean"""
153 # Delete any old interfaces with the same names
154 quietRun( 'ip link del ' + intf1 )
155 quietRun( 'ip link del ' + intf2 )
156 # Create new pair
157 cmd = 'ip link add name ' + intf1 + ' type veth peer name ' + intf2
158 return quietRun( cmd )
159
160def retry( retries, delaySecs, fn, *args, **keywords ):
161 """Try something several times before giving up.
162 n: number of times to retry
163 delaySecs: wait this long between tries
164 fn: function to call
165 args: args to apply to function call"""
166 tries = 0
167 while not fn( *args, **keywords ) and tries < retries:
168 sleep( delaySecs )
169 tries += 1
170 if tries >= retries:
171 error( "*** gave up after %i retries\n" % tries )
172 exit( 1 )
173
174def moveIntfNoRetry( intf, node, printError=False ):
175 """Move interface to node, without retrying.
176 intf: string, interface
177 node: Node object
178 printError: if true, print error"""
179 cmd = 'ip link set ' + intf + ' netns ' + repr( node.pid )
180 quietRun( cmd )
181 links = node.cmd( 'ip link show' )
182 if not ( ' %s:' % intf ) in links:
183 if printError:
184 error( '*** Error: moveIntf: ' + intf +
185 ' not successfully moved to ' + node.name + '\n' )
186 return False
187 return True
188
189def moveIntf( intf, node, printError=False, retries=3, delaySecs=0.001 ):
190 """Move interface to node, retrying on failure.
191 intf: string, interface
192 node: Node object
193 printError: if true, print error"""
194 retry( retries, delaySecs, moveIntfNoRetry, intf, node, printError )
195
196# Support for dumping network
197
198def dumpNodeConnections( nodes ):
199 "Dump connections to/from nodes."
200
201 def dumpConnections( node ):
202 "Helper function: dump connections to node"
203 for intf in node.intfList():
204 output( ' %s:' % intf )
205 if intf.link:
206 intfs = [ intf.link.intf1, intf.link.intf2 ]
207 intfs.remove( intf )
208 output( intfs[ 0 ] )
209 else:
210 output( ' ' )
211
212 for node in nodes:
213 output( node.name )
214 dumpConnections( node )
215 output( '\n' )
216
217def dumpNetConnections( net ):
218 "Dump connections in network"
219 nodes = net.controllers + net.switches + net.hosts
220 dumpNodeConnections( nodes )
221
222# IP and Mac address formatting and parsing
223
224def _colonHex( val, bytecount ):
225 """Generate colon-hex string.
226 val: input as unsigned int
227 bytecount: number of bytes to convert
228 returns: chStr colon-hex string"""
229 pieces = []
230 for i in range( bytecount - 1, -1, -1 ):
231 piece = ( ( 0xff << ( i * 8 ) ) & val ) >> ( i * 8 )
232 pieces.append( '%02x' % piece )
233 chStr = ':'.join( pieces )
234 return chStr
235
236def macColonHex( mac ):
237 """Generate MAC colon-hex string from unsigned int.
238 mac: MAC address as unsigned int
239 returns: macStr MAC colon-hex string"""
240 return _colonHex( mac, 6 )
241
242def ipStr( ip ):
243 """Generate IP address string from an unsigned int.
244 ip: unsigned int of form w << 24 | x << 16 | y << 8 | z
245 returns: ip address string w.x.y.z, or 10.x.y.z if w==0"""
246 w = ( ip >> 24 ) & 0xff
247 w = 10 if w == 0 else w
248 x = ( ip >> 16 ) & 0xff
249 y = ( ip >> 8 ) & 0xff
250 z = ip & 0xff
251 return "%i.%i.%i.%i" % ( w, x, y, z )
252
253def ipNum( w, x, y, z ):
254 """Generate unsigned int from components of IP address
255 returns: w << 24 | x << 16 | y << 8 | z"""
256 return ( w << 24 ) | ( x << 16 ) | ( y << 8 ) | z
257
258def nextCCNnet(curCCNnet):
259 netNum = ipParse(curCCNnet)
260 return ipStr(netNum+4)
261
262def ipAdd( i, prefixLen=8, ipBaseNum=0x0a000000 ):
263 """Return IP address string from ints
264 i: int to be added to ipbase
265 prefixLen: optional IP prefix length
266 ipBaseNum: option base IP address as int
267 returns IP address as string"""
268 # Ugly but functional
269 assert i < ( 1 << ( 32 - prefixLen ) )
270 mask = 0xffffffff ^ ( ( 1 << prefixLen ) - 1 )
271 ipnum = i + ( ipBaseNum & mask )
272 return ipStr( ipnum )
273
274def ipParse( ip ):
275 "Parse an IP address and return an unsigned int."
276 args = [ int( arg ) for arg in ip.split( '.' ) ]
277 return ipNum( *args )
278
279def netParse( ipstr ):
280 """Parse an IP network specification, returning
281 address and prefix len as unsigned ints"""
282 prefixLen = 0
283 if '/' in ipstr:
284 ip, pf = ipstr.split( '/' )
285 prefixLen = int( pf )
286 return ipParse( ip ), prefixLen
287
288def checkInt( s ):
289 "Check if input string is an int"
290 try:
291 int( s )
292 return True
293 except ValueError:
294 return False
295
296def checkFloat( s ):
297 "Check if input string is a float"
298 try:
299 float( s )
300 return True
301 except ValueError:
302 return False
303
304def makeNumeric( s ):
305 "Convert string to int or float if numeric."
306 if checkInt( s ):
307 return int( s )
308 elif checkFloat( s ):
309 return float( s )
310 else:
311 return s
312
313# Popen support
314
315def pmonitor(popens, timeoutms=500, readline=True,
316 readmax=1024 ):
317 """Monitor dict of hosts to popen objects
318 a line at a time
319 timeoutms: timeout for poll()
320 readline: return single line of output
321 yields: host, line/output (if any)
322 terminates: when all EOFs received"""
323 poller = poll()
324 fdToHost = {}
325 for host, popen in popens.iteritems():
326 fd = popen.stdout.fileno()
327 fdToHost[ fd ] = host
328 poller.register( fd, POLLIN )
329 if not readline:
330 # Use non-blocking reads
331 flags = fcntl( fd, F_GETFL )
332 fcntl( fd, F_SETFL, flags | O_NONBLOCK )
333 while True:
334 fds = poller.poll( timeoutms )
335 if fds:
336 for fd, _event in fds:
337 host = fdToHost[ fd ]
338 popen = popens[ host ]
339 if readline:
340 # Attempt to read a line of output
341 # This blocks until we receive a newline!
342 line = popen.stdout.readline()
343 else:
344 line = popen.stdout.read( readmax )
345 yield host, line
346 # Check for EOF
347 if not line:
348 popen.poll()
349 if popen.returncode is not None:
350 poller.unregister( fd )
351 del popens[ host ]
352 if not popens:
353 return
354 else:
355 yield None, ''
356
357# Other stuff we use
358
359def fixLimits():
360 "Fix ridiculously small resource limits."
361 setrlimit( RLIMIT_NPROC, ( 8192, 8192 ) )
362 setrlimit( RLIMIT_NOFILE, ( 16384, 16384 ) )
363
364def mountCgroups():
365 "Make sure cgroups file system is mounted"
366 mounts = quietRun( 'mount' )
367 cgdir = '/sys/fs/cgroup'
368 csdir = cgdir + '/cpuset'
369 if ('cgroup on %s' % cgdir not in mounts and
370 'cgroups on %s' % cgdir not in mounts):
371 raise Exception( "cgroups not mounted on " + cgdir )
372 if 'cpuset on %s' % csdir not in mounts:
373 errRun( 'mkdir -p ' + csdir )
374 errRun( 'mount -t cgroup -ocpuset cpuset ' + csdir )
375
376def natural( text ):
377 "To sort sanely/alphabetically: sorted( l, key=natural )"
378 def num( s ):
379 "Convert text segment to int if necessary"
380 return int( s ) if s.isdigit() else s
381 return [ num( s ) for s in re.split( r'(\d+)', text ) ]
382
383def naturalSeq( t ):
384 "Natural sort key function for sequences"
385 return [ natural( x ) for x in t ]
386
387def numCores():
388 "Returns number of CPU cores based on /proc/cpuinfo"
389 if hasattr( numCores, 'ncores' ):
390 return numCores.ncores
391 try:
392 numCores.ncores = int( quietRun('grep -c processor /proc/cpuinfo') )
393 except ValueError:
394 return 0
395 return numCores.ncores
396
397def irange(start, end):
398 """Inclusive range from start to end (vs. Python insanity.)
399 irange(1,5) -> 1, 2, 3, 4, 5"""
400 return range( start, end + 1 )
401
402def custom( cls, **params ):
403 "Returns customized constructor for class cls."
404 # Note: we may wish to see if we can use functools.partial() here
405 # and in customConstructor
406 def customized( *args, **kwargs):
407 "Customized constructor"
408 kwargs = kwargs.copy()
409 kwargs.update( params )
410 return cls( *args, **kwargs )
411 customized.__name__ = 'custom(%s,%s)' % ( cls, params )
412 return customized
413
414def splitArgs( argstr ):
415 """Split argument string into usable python arguments
416 argstr: argument string with format fn,arg2,kw1=arg3...
417 returns: fn, args, kwargs"""
418 split = argstr.split( ',' )
419 fn = split[ 0 ]
420 params = split[ 1: ]
421 # Convert int and float args; removes the need for function
422 # to be flexible with input arg formats.
423 args = [ makeNumeric( s ) for s in params if '=' not in s ]
424 kwargs = {}
425 for s in [ p for p in params if '=' in p ]:
426 key, val = s.split( '=' )
427 kwargs[ key ] = makeNumeric( val )
428 return fn, args, kwargs
429
430def customConstructor( constructors, argStr ):
431 """Return custom constructor based on argStr
432 The args and key/val pairs in argsStr will be automatically applied
433 when the generated constructor is later used.
434 """
435 cname, newargs, kwargs = splitArgs( argStr )
436 constructor = constructors.get( cname, None )
437
438 if not constructor:
439 raise Exception( "error: %s is unknown - please specify one of %s" %
440 ( cname, constructors.keys() ) )
441
442 def customized( name, *args, **params ):
443 "Customized constructor, useful for Node, Link, and other classes"
444 params = params.copy()
445 params.update( kwargs )
446 if not newargs:
447 return constructor( name, *args, **params )
448 if args:
449 warn( 'warning: %s replacing %s with %s\n' % (
450 constructor, args, newargs ) )
451 return constructor( name, *newargs, **params )
452
453 customized.__name__ = 'customConstructor(%s)' % argStr
454 return customized
455
456def buildTopo( topos, topoStr ):
457 """Create topology from string with format (object, arg1, arg2,...).
458 input topos is a dict of topo names to constructors, possibly w/args.
459 """
460 topo, args, kwargs = splitArgs( topoStr )
461 if topo not in topos:
462 raise Exception( 'Invalid topo name %s' % topo )
463 return topos[ topo ]( *args, **kwargs )
464
465def ensureRoot():
466 """Ensure that we are running as root.
467
468 Probably we should only sudo when needed as per Big Switch's patch.
469 """
470 if os.getuid() != 0:
471 print "*** Mininet must run as root."
472 exit( 1 )
473 return