First commit
diff --git a/mininet/util.py b/mininet/util.py
new file mode 100644
index 0000000..6a3dc14
--- /dev/null
+++ b/mininet/util.py
@@ -0,0 +1,473 @@
+"Utility functions for Mininet."
+
+from mininet.log import output, info, error, warn
+
+from time import sleep
+from resource import setrlimit, RLIMIT_NPROC, RLIMIT_NOFILE
+from select import poll, POLLIN
+from subprocess import call, check_call, Popen, PIPE, STDOUT
+import re
+from fcntl import fcntl, F_GETFL, F_SETFL
+from os import O_NONBLOCK
+import os
+
+# Command execution support
+
+def run( cmd ):
+ """Simple interface to subprocess.call()
+ cmd: list of command params"""
+ return call( cmd.split( ' ' ) )
+
+def checkRun( cmd ):
+ """Simple interface to subprocess.check_call()
+ cmd: list of command params"""
+ return check_call( cmd.split( ' ' ) )
+
+# pylint doesn't understand explicit type checking
+# pylint: disable-msg=E1103
+
+def oldQuietRun( *cmd ):
+ """Run a command, routing stderr to stdout, and return the output.
+ cmd: list of command params"""
+ if len( cmd ) == 1:
+ cmd = cmd[ 0 ]
+ if isinstance( cmd, str ):
+ cmd = cmd.split( ' ' )
+ popen = Popen( cmd, stdout=PIPE, stderr=STDOUT )
+ # We can't use Popen.communicate() because it uses
+ # select(), which can't handle
+ # high file descriptor numbers! poll() can, however.
+ out = ''
+ readable = poll()
+ readable.register( popen.stdout )
+ while True:
+ while readable.poll():
+ data = popen.stdout.read( 1024 )
+ if len( data ) == 0:
+ break
+ out += data
+ popen.poll()
+ if popen.returncode is not None:
+ break
+ return out
+
+
+# This is a bit complicated, but it enables us to
+# monitor command output as it is happening
+
+def errRun( *cmd, **kwargs ):
+ """Run a command and return stdout, stderr and return code
+ cmd: string or list of command and args
+ stderr: STDOUT to merge stderr with stdout
+ shell: run command using shell
+ echo: monitor output to console"""
+ # Allow passing in a list or a string
+ if len( cmd ) == 1:
+ cmd = cmd[ 0 ]
+ if isinstance( cmd, str ):
+ cmd = cmd.split( ' ' )
+ cmd = [ str( arg ) for arg in cmd ]
+ # By default we separate stderr, don't run in a shell, and don't echo
+ stderr = kwargs.get( 'stderr', PIPE )
+ shell = kwargs.get( 'shell', False )
+ echo = kwargs.get( 'echo', False )
+ if echo:
+ # cmd goes to stderr, output goes to stdout
+ info( cmd, '\n' )
+ popen = Popen( cmd, stdout=PIPE, stderr=stderr, shell=shell )
+ # We use poll() because select() doesn't work with large fd numbers,
+ # and thus communicate() doesn't work either
+ out, err = '', ''
+ poller = poll()
+ poller.register( popen.stdout, POLLIN )
+ fdtofile = { popen.stdout.fileno(): popen.stdout }
+ outDone, errDone = False, True
+ if popen.stderr:
+ fdtofile[ popen.stderr.fileno() ] = popen.stderr
+ poller.register( popen.stderr, POLLIN )
+ errDone = False
+ while not outDone or not errDone:
+ readable = poller.poll()
+ for fd, _event in readable:
+ f = fdtofile[ fd ]
+ data = f.read( 1024 )
+ if echo:
+ output( data )
+ if f == popen.stdout:
+ out += data
+ if data == '':
+ outDone = True
+ elif f == popen.stderr:
+ err += data
+ if data == '':
+ errDone = True
+ returncode = popen.wait()
+ return out, err, returncode
+
+def errFail( *cmd, **kwargs ):
+ "Run a command using errRun and raise exception on nonzero exit"
+ out, err, ret = errRun( *cmd, **kwargs )
+ if ret:
+ raise Exception( "errFail: %s failed with return code %s: %s"
+ % ( cmd, ret, err ) )
+ return out, err, ret
+
+def quietRun( cmd, **kwargs ):
+ "Run a command and return merged stdout and stderr"
+ return errRun( cmd, stderr=STDOUT, **kwargs )[ 0 ]
+
+# pylint: enable-msg=E1103
+# pylint: disable-msg=E1101
+
+def isShellBuiltin( cmd ):
+ "Return True if cmd is a bash builtin."
+ if isShellBuiltin.builtIns is None:
+ isShellBuiltin.builtIns = quietRun( 'bash -c enable' )
+ space = cmd.find( ' ' )
+ if space > 0:
+ cmd = cmd[ :space]
+ return cmd in isShellBuiltin.builtIns
+
+isShellBuiltin.builtIns = None
+
+# pylint: enable-msg=E1101
+
+# Interface management
+#
+# Interfaces are managed as strings which are simply the
+# interface names, of the form 'nodeN-ethM'.
+#
+# To connect nodes, we create a pair of veth interfaces, and then place them
+# in the pair of nodes that we want to communicate. We then update the node's
+# list of interfaces and connectivity map.
+#
+# For the kernel datapath, switch interfaces
+# live in the root namespace and thus do not have to be
+# explicitly moved.
+
+def makeIntfPair( intf1, intf2 ):
+ """Make a veth pair connecting intf1 and intf2.
+ intf1: string, interface
+ intf2: string, interface
+ returns: success boolean"""
+ # Delete any old interfaces with the same names
+ quietRun( 'ip link del ' + intf1 )
+ quietRun( 'ip link del ' + intf2 )
+ # Create new pair
+ cmd = 'ip link add name ' + intf1 + ' type veth peer name ' + intf2
+ return quietRun( cmd )
+
+def retry( retries, delaySecs, fn, *args, **keywords ):
+ """Try something several times before giving up.
+ n: number of times to retry
+ delaySecs: wait this long between tries
+ fn: function to call
+ args: args to apply to function call"""
+ tries = 0
+ while not fn( *args, **keywords ) and tries < retries:
+ sleep( delaySecs )
+ tries += 1
+ if tries >= retries:
+ error( "*** gave up after %i retries\n" % tries )
+ exit( 1 )
+
+def moveIntfNoRetry( intf, node, printError=False ):
+ """Move interface to node, without retrying.
+ intf: string, interface
+ node: Node object
+ printError: if true, print error"""
+ cmd = 'ip link set ' + intf + ' netns ' + repr( node.pid )
+ quietRun( cmd )
+ links = node.cmd( 'ip link show' )
+ if not ( ' %s:' % intf ) in links:
+ if printError:
+ error( '*** Error: moveIntf: ' + intf +
+ ' not successfully moved to ' + node.name + '\n' )
+ return False
+ return True
+
+def moveIntf( intf, node, printError=False, retries=3, delaySecs=0.001 ):
+ """Move interface to node, retrying on failure.
+ intf: string, interface
+ node: Node object
+ printError: if true, print error"""
+ retry( retries, delaySecs, moveIntfNoRetry, intf, node, printError )
+
+# Support for dumping network
+
+def dumpNodeConnections( nodes ):
+ "Dump connections to/from nodes."
+
+ def dumpConnections( node ):
+ "Helper function: dump connections to node"
+ for intf in node.intfList():
+ output( ' %s:' % intf )
+ if intf.link:
+ intfs = [ intf.link.intf1, intf.link.intf2 ]
+ intfs.remove( intf )
+ output( intfs[ 0 ] )
+ else:
+ output( ' ' )
+
+ for node in nodes:
+ output( node.name )
+ dumpConnections( node )
+ output( '\n' )
+
+def dumpNetConnections( net ):
+ "Dump connections in network"
+ nodes = net.controllers + net.switches + net.hosts
+ dumpNodeConnections( nodes )
+
+# IP and Mac address formatting and parsing
+
+def _colonHex( val, bytecount ):
+ """Generate colon-hex string.
+ val: input as unsigned int
+ bytecount: number of bytes to convert
+ returns: chStr colon-hex string"""
+ pieces = []
+ for i in range( bytecount - 1, -1, -1 ):
+ piece = ( ( 0xff << ( i * 8 ) ) & val ) >> ( i * 8 )
+ pieces.append( '%02x' % piece )
+ chStr = ':'.join( pieces )
+ return chStr
+
+def macColonHex( mac ):
+ """Generate MAC colon-hex string from unsigned int.
+ mac: MAC address as unsigned int
+ returns: macStr MAC colon-hex string"""
+ return _colonHex( mac, 6 )
+
+def ipStr( ip ):
+ """Generate IP address string from an unsigned int.
+ ip: unsigned int of form w << 24 | x << 16 | y << 8 | z
+ returns: ip address string w.x.y.z, or 10.x.y.z if w==0"""
+ w = ( ip >> 24 ) & 0xff
+ w = 10 if w == 0 else w
+ x = ( ip >> 16 ) & 0xff
+ y = ( ip >> 8 ) & 0xff
+ z = ip & 0xff
+ return "%i.%i.%i.%i" % ( w, x, y, z )
+
+def ipNum( w, x, y, z ):
+ """Generate unsigned int from components of IP address
+ returns: w << 24 | x << 16 | y << 8 | z"""
+ return ( w << 24 ) | ( x << 16 ) | ( y << 8 ) | z
+
+def nextCCNnet(curCCNnet):
+ netNum = ipParse(curCCNnet)
+ return ipStr(netNum+4)
+
+def ipAdd( i, prefixLen=8, ipBaseNum=0x0a000000 ):
+ """Return IP address string from ints
+ i: int to be added to ipbase
+ prefixLen: optional IP prefix length
+ ipBaseNum: option base IP address as int
+ returns IP address as string"""
+ # Ugly but functional
+ assert i < ( 1 << ( 32 - prefixLen ) )
+ mask = 0xffffffff ^ ( ( 1 << prefixLen ) - 1 )
+ ipnum = i + ( ipBaseNum & mask )
+ return ipStr( ipnum )
+
+def ipParse( ip ):
+ "Parse an IP address and return an unsigned int."
+ args = [ int( arg ) for arg in ip.split( '.' ) ]
+ return ipNum( *args )
+
+def netParse( ipstr ):
+ """Parse an IP network specification, returning
+ address and prefix len as unsigned ints"""
+ prefixLen = 0
+ if '/' in ipstr:
+ ip, pf = ipstr.split( '/' )
+ prefixLen = int( pf )
+ return ipParse( ip ), prefixLen
+
+def checkInt( s ):
+ "Check if input string is an int"
+ try:
+ int( s )
+ return True
+ except ValueError:
+ return False
+
+def checkFloat( s ):
+ "Check if input string is a float"
+ try:
+ float( s )
+ return True
+ except ValueError:
+ return False
+
+def makeNumeric( s ):
+ "Convert string to int or float if numeric."
+ if checkInt( s ):
+ return int( s )
+ elif checkFloat( s ):
+ return float( s )
+ else:
+ return s
+
+# Popen support
+
+def pmonitor(popens, timeoutms=500, readline=True,
+ readmax=1024 ):
+ """Monitor dict of hosts to popen objects
+ a line at a time
+ timeoutms: timeout for poll()
+ readline: return single line of output
+ yields: host, line/output (if any)
+ terminates: when all EOFs received"""
+ poller = poll()
+ fdToHost = {}
+ for host, popen in popens.iteritems():
+ fd = popen.stdout.fileno()
+ fdToHost[ fd ] = host
+ poller.register( fd, POLLIN )
+ if not readline:
+ # Use non-blocking reads
+ flags = fcntl( fd, F_GETFL )
+ fcntl( fd, F_SETFL, flags | O_NONBLOCK )
+ while True:
+ fds = poller.poll( timeoutms )
+ if fds:
+ for fd, _event in fds:
+ host = fdToHost[ fd ]
+ popen = popens[ host ]
+ if readline:
+ # Attempt to read a line of output
+ # This blocks until we receive a newline!
+ line = popen.stdout.readline()
+ else:
+ line = popen.stdout.read( readmax )
+ yield host, line
+ # Check for EOF
+ if not line:
+ popen.poll()
+ if popen.returncode is not None:
+ poller.unregister( fd )
+ del popens[ host ]
+ if not popens:
+ return
+ else:
+ yield None, ''
+
+# Other stuff we use
+
+def fixLimits():
+ "Fix ridiculously small resource limits."
+ setrlimit( RLIMIT_NPROC, ( 8192, 8192 ) )
+ setrlimit( RLIMIT_NOFILE, ( 16384, 16384 ) )
+
+def mountCgroups():
+ "Make sure cgroups file system is mounted"
+ mounts = quietRun( 'mount' )
+ cgdir = '/sys/fs/cgroup'
+ csdir = cgdir + '/cpuset'
+ if ('cgroup on %s' % cgdir not in mounts and
+ 'cgroups on %s' % cgdir not in mounts):
+ raise Exception( "cgroups not mounted on " + cgdir )
+ if 'cpuset on %s' % csdir not in mounts:
+ errRun( 'mkdir -p ' + csdir )
+ errRun( 'mount -t cgroup -ocpuset cpuset ' + csdir )
+
+def natural( text ):
+ "To sort sanely/alphabetically: sorted( l, key=natural )"
+ def num( s ):
+ "Convert text segment to int if necessary"
+ return int( s ) if s.isdigit() else s
+ return [ num( s ) for s in re.split( r'(\d+)', text ) ]
+
+def naturalSeq( t ):
+ "Natural sort key function for sequences"
+ return [ natural( x ) for x in t ]
+
+def numCores():
+ "Returns number of CPU cores based on /proc/cpuinfo"
+ if hasattr( numCores, 'ncores' ):
+ return numCores.ncores
+ try:
+ numCores.ncores = int( quietRun('grep -c processor /proc/cpuinfo') )
+ except ValueError:
+ return 0
+ return numCores.ncores
+
+def irange(start, end):
+ """Inclusive range from start to end (vs. Python insanity.)
+ irange(1,5) -> 1, 2, 3, 4, 5"""
+ return range( start, end + 1 )
+
+def custom( cls, **params ):
+ "Returns customized constructor for class cls."
+ # Note: we may wish to see if we can use functools.partial() here
+ # and in customConstructor
+ def customized( *args, **kwargs):
+ "Customized constructor"
+ kwargs = kwargs.copy()
+ kwargs.update( params )
+ return cls( *args, **kwargs )
+ customized.__name__ = 'custom(%s,%s)' % ( cls, params )
+ return customized
+
+def splitArgs( argstr ):
+ """Split argument string into usable python arguments
+ argstr: argument string with format fn,arg2,kw1=arg3...
+ returns: fn, args, kwargs"""
+ split = argstr.split( ',' )
+ fn = split[ 0 ]
+ params = split[ 1: ]
+ # Convert int and float args; removes the need for function
+ # to be flexible with input arg formats.
+ args = [ makeNumeric( s ) for s in params if '=' not in s ]
+ kwargs = {}
+ for s in [ p for p in params if '=' in p ]:
+ key, val = s.split( '=' )
+ kwargs[ key ] = makeNumeric( val )
+ return fn, args, kwargs
+
+def customConstructor( constructors, argStr ):
+ """Return custom constructor based on argStr
+ The args and key/val pairs in argsStr will be automatically applied
+ when the generated constructor is later used.
+ """
+ cname, newargs, kwargs = splitArgs( argStr )
+ constructor = constructors.get( cname, None )
+
+ if not constructor:
+ raise Exception( "error: %s is unknown - please specify one of %s" %
+ ( cname, constructors.keys() ) )
+
+ def customized( name, *args, **params ):
+ "Customized constructor, useful for Node, Link, and other classes"
+ params = params.copy()
+ params.update( kwargs )
+ if not newargs:
+ return constructor( name, *args, **params )
+ if args:
+ warn( 'warning: %s replacing %s with %s\n' % (
+ constructor, args, newargs ) )
+ return constructor( name, *newargs, **params )
+
+ customized.__name__ = 'customConstructor(%s)' % argStr
+ return customized
+
+def buildTopo( topos, topoStr ):
+ """Create topology from string with format (object, arg1, arg2,...).
+ input topos is a dict of topo names to constructors, possibly w/args.
+ """
+ topo, args, kwargs = splitArgs( topoStr )
+ if topo not in topos:
+ raise Exception( 'Invalid topo name %s' % topo )
+ return topos[ topo ]( *args, **kwargs )
+
+def ensureRoot():
+ """Ensure that we are running as root.
+
+ Probably we should only sudo when needed as per Big Switch's patch.
+ """
+ if os.getuid() != 0:
+ print "*** Mininet must run as root."
+ exit( 1 )
+ return