carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1 | "Utility functions for Mininet." |
| 2 | |
| 3 | from mininet.log import output, info, error, warn |
| 4 | |
| 5 | from time import sleep |
| 6 | from resource import setrlimit, RLIMIT_NPROC, RLIMIT_NOFILE |
| 7 | from select import poll, POLLIN |
| 8 | from subprocess import call, check_call, Popen, PIPE, STDOUT |
| 9 | import re |
| 10 | from fcntl import fcntl, F_GETFL, F_SETFL |
| 11 | from os import O_NONBLOCK |
| 12 | import os |
| 13 | |
| 14 | # Command execution support |
| 15 | |
| 16 | def run( cmd ): |
| 17 | """Simple interface to subprocess.call() |
| 18 | cmd: list of command params""" |
| 19 | return call( cmd.split( ' ' ) ) |
| 20 | |
| 21 | def 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 | |
| 29 | def 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 | |
| 58 | def 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 | |
| 107 | def 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 | |
| 115 | def 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 | |
| 122 | def 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 | |
| 131 | isShellBuiltin.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 | |
| 148 | def 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 | |
| 160 | def 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 | |
| 174 | def 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 | |
| 189 | def 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 | |
| 198 | def 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 | |
| 217 | def 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 | |
| 224 | def _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 | |
| 236 | def 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 | |
| 242 | def 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 | |
| 253 | def 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 | |
| 258 | def nextCCNnet(curCCNnet): |
| 259 | netNum = ipParse(curCCNnet) |
| 260 | return ipStr(netNum+4) |
| 261 | |
| 262 | def 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 | |
| 274 | def 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 | |
| 279 | def 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 | |
| 288 | def 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 | |
| 296 | def 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 | |
| 304 | def 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 | |
| 315 | def 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 | |
| 359 | def fixLimits(): |
| 360 | "Fix ridiculously small resource limits." |
| 361 | setrlimit( RLIMIT_NPROC, ( 8192, 8192 ) ) |
| 362 | setrlimit( RLIMIT_NOFILE, ( 16384, 16384 ) ) |
| 363 | |
| 364 | def 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 | |
| 376 | def 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 | |
| 383 | def naturalSeq( t ): |
| 384 | "Natural sort key function for sequences" |
| 385 | return [ natural( x ) for x in t ] |
| 386 | |
| 387 | def 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 | |
| 397 | def 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 | |
| 402 | def 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 | |
| 414 | def 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 | |
| 430 | def 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 | |
| 456 | def 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 | |
| 465 | def 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 |