Statistics
| Branch: | Tag: | Revision:

mininet / mininet / util.py @ b1ec912d

History | View | Annotate | Download (19 KB)

1
"Utility functions for Mininet."
2

    
3
from mininet.log import output, info, error, warn, debug
4

    
5
from time import sleep
6
from resource import getrlimit, setrlimit, RLIMIT_NPROC, RLIMIT_NOFILE
7
from select import poll, POLLIN, POLLHUP
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
from functools import partial
14

    
15
# Command execution support
16

    
17
def run( cmd ):
18
    """Simple interface to subprocess.call()
19
       cmd: list of command params"""
20
    return call( cmd.split( ' ' ) )
21

    
22
def checkRun( cmd ):
23
    """Simple interface to subprocess.check_call()
24
       cmd: list of command params"""
25
    return check_call( cmd.split( ' ' ) )
26

    
27
# pylint doesn't understand explicit type checking
28
# pylint: disable=E1103
29

    
30
def oldQuietRun( *cmd ):
31
    """Run a command, routing stderr to stdout, and return the output.
32
       cmd: list of command params"""
33
    if len( cmd ) == 1:
34
        cmd = cmd[ 0 ]
35
        if isinstance( cmd, str ):
36
            cmd = cmd.split( ' ' )
37
    popen = Popen( cmd, stdout=PIPE, stderr=STDOUT )
38
    # We can't use Popen.communicate() because it uses
39
    # select(), which can't handle
40
    # high file descriptor numbers! poll() can, however.
41
    out = ''
42
    readable = poll()
43
    readable.register( popen.stdout )
44
    while True:
45
        while readable.poll():
46
            data = popen.stdout.read( 1024 )
47
            if len( data ) == 0:
48
                break
49
            out += data
50
        popen.poll()
51
        if popen.returncode is not None:
52
            break
53
    return out
54

    
55

    
56
# This is a bit complicated, but it enables us to
57
# monitor command output as it is happening
58

    
59
def errRun( *cmd, **kwargs ):
60
    """Run a command and return stdout, stderr and return code
61
       cmd: string or list of command and args
62
       stderr: STDOUT to merge stderr with stdout
63
       shell: run command using shell
64
       echo: monitor output to console"""
65
    # By default we separate stderr, don't run in a shell, and don't echo
66
    stderr = kwargs.get( 'stderr', PIPE )
67
    shell = kwargs.get( 'shell', False )
68
    echo = kwargs.get( 'echo', False )
69
    if echo:
70
        # cmd goes to stderr, output goes to stdout
71
        info( cmd, '\n' )
72
    if len( cmd ) == 1:
73
        cmd = cmd[ 0 ]
74
    # Allow passing in a list or a string
75
    if isinstance( cmd, str ) and not shell:
76
        cmd = cmd.split( ' ' )
77
        cmd = [ str( arg ) for arg in cmd ]
78
    elif isinstance( cmd, list ) and shell:
79
        cmd = " ".join( arg for arg in cmd )
80
    popen = Popen( cmd, stdout=PIPE, stderr=stderr, shell=shell )
81
    # We use poll() because select() doesn't work with large fd numbers,
82
    # and thus communicate() doesn't work either
83
    out, err = '', ''
84
    poller = poll()
85
    poller.register( popen.stdout, POLLIN )
86
    fdtofile = { popen.stdout.fileno(): popen.stdout }
87
    outDone, errDone = False, True
88
    if popen.stderr:
89
        fdtofile[ popen.stderr.fileno() ] = popen.stderr
90
        poller.register( popen.stderr, POLLIN )
91
        errDone = False
92
    while not outDone or not errDone:
93
        readable = poller.poll()
94
        for fd, _event in readable:
95
            f = fdtofile[ fd ]
96
            data = f.read( 1024 )
97
            if echo:
98
                output( data )
99
            if f == popen.stdout:
100
                out += data
101
                if data == '':
102
                    outDone = True
103
            elif f == popen.stderr:
104
                err += data
105
                if data == '':
106
                    errDone = True
107
    returncode = popen.wait()
108
    return out, err, returncode
109

    
110
def errFail( *cmd, **kwargs ):
111
    "Run a command using errRun and raise exception on nonzero exit"
112
    out, err, ret = errRun( *cmd, **kwargs )
113
    if ret:
114
        raise Exception( "errFail: %s failed with return code %s: %s"
115
                         % ( cmd, ret, err ) )
116
    return out, err, ret
117

    
118
def quietRun( cmd, **kwargs ):
119
    "Run a command and return merged stdout and stderr"
120
    return errRun( cmd, stderr=STDOUT, **kwargs )[ 0 ]
121

    
122
# pylint: enable=E1103
123
# pylint: disable=E1101
124

    
125
def isShellBuiltin( cmd ):
126
    "Return True if cmd is a bash builtin."
127
    if isShellBuiltin.builtIns is None:
128
        isShellBuiltin.builtIns = quietRun( 'bash -c enable' )
129
    space = cmd.find( ' ' )
130
    if space > 0:
131
        cmd = cmd[ :space]
132
    return cmd in isShellBuiltin.builtIns
133

    
134
isShellBuiltin.builtIns = None
135

    
136
# pylint: enable=E1101
137

    
138
# Interface management
139
#
140
# Interfaces are managed as strings which are simply the
141
# interface names, of the form 'nodeN-ethM'.
142
#
143
# To connect nodes, we create a pair of veth interfaces, and then place them
144
# in the pair of nodes that we want to communicate. We then update the node's
145
# list of interfaces and connectivity map.
146
#
147
# For the kernel datapath, switch interfaces
148
# live in the root namespace and thus do not have to be
149
# explicitly moved.
150

    
151
def makeIntfPair( intf1, intf2, addr1=None, addr2=None, runCmd=quietRun ):
152
    """Make a veth pair connecting intf1 and intf2.
153
       intf1: string, interface
154
       intf2: string, interface
155
       runCmd: function to run shell commands (quietRun)
156
       returns: ip link add result"""
157
    # Delete any old interfaces with the same names
158
    runCmd( 'ip link del ' + intf1 )
159
    runCmd( 'ip link del ' + intf2 )
160
    # Create new pair
161
    if addr1 is None and addr2 is None:
162
        cmd = 'ip link add name ' + intf1 + ' type veth peer name ' + intf2
163
    else:
164
        cmd = ( 'ip link add name ' + intf1 + ' address ' + addr1 +
165
                ' type veth peer name ' + intf2 + ' address ' + addr2 )
166
    cmdOutput = runCmd( cmd )
167
    if cmdOutput == '':
168
        return True
169
    else:
170
        error( "Error creating interface pair: %s " % cmdOutput )
171
        return False
172

    
173
def retry( retries, delaySecs, fn, *args, **keywords ):
174
    """Try something several times before giving up.
175
       n: number of times to retry
176
       delaySecs: wait this long between tries
177
       fn: function to call
178
       args: args to apply to function call"""
179
    tries = 0
180
    while not fn( *args, **keywords ) and tries < retries:
181
        sleep( delaySecs )
182
        tries += 1
183
    if tries >= retries:
184
        error( "*** gave up after %i retries\n" % tries )
185
        exit( 1 )
186

    
187
def moveIntfNoRetry( intf, dstNode, printError=False ):
188
    """Move interface to node, without retrying.
189
       intf: string, interface
190
        dstNode: destination Node
191
        printError: if true, print error"""
192
    intf = str( intf )
193
    cmd = 'ip link set %s netns %s' % ( intf, dstNode.pid )
194
    cmdOutput = quietRun( cmd )
195
    # If ip link set does not produce any output, then we can assume
196
    # that the link has been moved successfully.
197
    if cmdOutput:
198
        if printError:
199
            error( '*** Error: moveIntf: ' + intf +
200
                   ' not successfully moved to ' + dstNode.name + ':\n',
201
                   cmdOutput )
202
        return False
203
    return True
204

    
205
def moveIntf( intf, dstNode, printError=True,
206
             retries=3, delaySecs=0.001 ):
207
    """Move interface to node, retrying on failure.
208
       intf: string, interface
209
       dstNode: destination Node
210
       printError: if true, print error"""
211
    retry( retries, delaySecs, moveIntfNoRetry, intf, dstNode,
212
           printError=printError )
213

    
214
# Support for dumping network
215

    
216
def dumpNodeConnections( nodes ):
217
    "Dump connections to/from nodes."
218

    
219
    def dumpConnections( node ):
220
        "Helper function: dump connections to node"
221
        for intf in node.intfList():
222
            output( ' %s:' % intf )
223
            if intf.link:
224
                intfs = [ intf.link.intf1, intf.link.intf2 ]
225
                intfs.remove( intf )
226
                output( intfs[ 0 ] )
227
            else:
228
                output( ' ' )
229

    
230
    for node in nodes:
231
        output( node.name )
232
        dumpConnections( node )
233
        output( '\n' )
234

    
235
def dumpNetConnections( net ):
236
    "Dump connections in network"
237
    nodes = net.controllers + net.switches + net.hosts
238
    dumpNodeConnections( nodes )
239

    
240
def dumpPorts( switches ):
241
    "dump interface to openflow port mappings for each switch"
242
    for switch in switches:
243
        output( '%s ' % switch.name )
244
        for intf in switch.intfList():
245
            port = switch.ports[ intf ]
246
            output( '%s:%d ' % ( intf, port ) )
247
        output( '\n' )
248

    
249
# IP and Mac address formatting and parsing
250

    
251
def _colonHex( val, bytecount ):
252
    """Generate colon-hex string.
253
       val: input as unsigned int
254
       bytecount: number of bytes to convert
255
       returns: chStr colon-hex string"""
256
    pieces = []
257
    for i in range( bytecount - 1, -1, -1 ):
258
        piece = ( ( 0xff << ( i * 8 ) ) & val ) >> ( i * 8 )
259
        pieces.append( '%02x' % piece )
260
    chStr = ':'.join( pieces )
261
    return chStr
262

    
263
def macColonHex( mac ):
264
    """Generate MAC colon-hex string from unsigned int.
265
       mac: MAC address as unsigned int
266
       returns: macStr MAC colon-hex string"""
267
    return _colonHex( mac, 6 )
268

    
269
def ipStr( ip ):
270
    """Generate IP address string from an unsigned int.
271
       ip: unsigned int of form w << 24 | x << 16 | y << 8 | z
272
       returns: ip address string w.x.y.z"""
273
    w = ( ip >> 24 ) & 0xff
274
    x = ( ip >> 16 ) & 0xff
275
    y = ( ip >> 8 ) & 0xff
276
    z = ip & 0xff
277
    return "%i.%i.%i.%i" % ( w, x, y, z )
278

    
279
def ipNum( w, x, y, z ):
280
    """Generate unsigned int from components of IP address
281
       returns: w << 24 | x << 16 | y << 8 | z"""
282
    return ( w << 24 ) | ( x << 16 ) | ( y << 8 ) | z
283

    
284
def ipAdd( i, prefixLen=8, ipBaseNum=0x0a000000 ):
285
    """Return IP address string from ints
286
       i: int to be added to ipbase
287
       prefixLen: optional IP prefix length
288
       ipBaseNum: option base IP address as int
289
       returns IP address as string"""
290
    imax = 0xffffffff >> prefixLen
291
    assert i <= imax, 'Not enough IP addresses in the subnet'
292
    mask = 0xffffffff ^ imax
293
    ipnum = ( ipBaseNum & mask ) + i
294
    return ipStr( ipnum )
295

    
296
def ipParse( ip ):
297
    "Parse an IP address and return an unsigned int."
298
    args = [ int( arg ) for arg in ip.split( '.' ) ]
299
    while len(args) < 4:
300
        args.append( 0 )
301
    return ipNum( *args )
302

    
303
def netParse( ipstr ):
304
    """Parse an IP network specification, returning
305
       address and prefix len as unsigned ints"""
306
    prefixLen = 0
307
    if '/' in ipstr:
308
        ip, pf = ipstr.split( '/' )
309
        prefixLen = int( pf )
310
    #if no prefix is specified, set the prefix to 24
311
    else:
312
        ip = ipstr
313
        prefixLen = 24
314
    return ipParse( ip ), prefixLen
315

    
316
def checkInt( s ):
317
    "Check if input string is an int"
318
    try:
319
        int( s )
320
        return True
321
    except ValueError:
322
        return False
323

    
324
def checkFloat( s ):
325
    "Check if input string is a float"
326
    try:
327
        float( s )
328
        return True
329
    except ValueError:
330
        return False
331

    
332
def makeNumeric( s ):
333
    "Convert string to int or float if numeric."
334
    if checkInt( s ):
335
        return int( s )
336
    elif checkFloat( s ):
337
        return float( s )
338
    else:
339
        return s
340

    
341
# Popen support
342

    
343
def pmonitor(popens, timeoutms=500, readline=True,
344
             readmax=1024 ):
345
    """Monitor dict of hosts to popen objects
346
       a line at a time
347
       timeoutms: timeout for poll()
348
       readline: return single line of output
349
       yields: host, line/output (if any)
350
       terminates: when all EOFs received"""
351
    poller = poll()
352
    fdToHost = {}
353
    for host, popen in popens.iteritems():
354
        fd = popen.stdout.fileno()
355
        fdToHost[ fd ] = host
356
        poller.register( fd, POLLIN )
357
        if not readline:
358
            # Use non-blocking reads
359
            flags = fcntl( fd, F_GETFL )
360
            fcntl( fd, F_SETFL, flags | O_NONBLOCK )
361
    while popens:
362
        fds = poller.poll( timeoutms )
363
        if fds:
364
            for fd, event in fds:
365
                host = fdToHost[ fd ]
366
                popen = popens[ host ]
367
                if event & POLLIN:
368
                    if readline:
369
                        # Attempt to read a line of output
370
                        # This blocks until we receive a newline!
371
                        line = popen.stdout.readline()
372
                    else:
373
                        line = popen.stdout.read( readmax )
374
                    yield host, line
375
                # Check for EOF
376
                elif event & POLLHUP:
377
                    poller.unregister( fd )
378
                    del popens[ host ]
379
        else:
380
            yield None, ''
381

    
382
# Other stuff we use
383
def sysctlTestAndSet( name, limit ):
384
    "Helper function to set sysctl limits"
385
    #convert non-directory names into directory names
386
    if '/' not in name:
387
        name = '/proc/sys/' + name.replace( '.', '/' )
388
    #read limit
389
    with open( name, 'r' ) as readFile:
390
        oldLimit = readFile.readline()
391
        if isinstance( limit, int ):
392
            #compare integer limits before overriding
393
            if int( oldLimit ) < limit:
394
                with open( name, 'w' ) as writeFile:
395
                    writeFile.write( "%d" % limit )
396
        else:
397
            #overwrite non-integer limits
398
            with open( name, 'w' ) as writeFile:
399
                writeFile.write( limit )
400

    
401
def rlimitTestAndSet( name, limit ):
402
    "Helper function to set rlimits"
403
    soft, hard = getrlimit( name )
404
    if soft < limit:
405
        hardLimit = hard if limit < hard else limit
406
        setrlimit( name, ( limit, hardLimit ) )
407

    
408
def fixLimits():
409
    "Fix ridiculously small resource limits."
410
    debug( "*** Setting resource limits\n" )
411
    try:
412
        rlimitTestAndSet( RLIMIT_NPROC, 8192 )
413
        rlimitTestAndSet( RLIMIT_NOFILE, 16384 )
414
        #Increase open file limit
415
        sysctlTestAndSet( 'fs.file-max', 10000 )
416
        #Increase network buffer space
417
        sysctlTestAndSet( 'net.core.wmem_max', 16777216 )
418
        sysctlTestAndSet( 'net.core.rmem_max', 16777216 )
419
        sysctlTestAndSet( 'net.ipv4.tcp_rmem', '10240 87380 16777216' )
420
        sysctlTestAndSet( 'net.ipv4.tcp_wmem', '10240 87380 16777216' )
421
        sysctlTestAndSet( 'net.core.netdev_max_backlog', 5000 )
422
        #Increase arp cache size
423
        sysctlTestAndSet( 'net.ipv4.neigh.default.gc_thresh1', 4096 )
424
        sysctlTestAndSet( 'net.ipv4.neigh.default.gc_thresh2', 8192 )
425
        sysctlTestAndSet( 'net.ipv4.neigh.default.gc_thresh3', 16384 )
426
        #Increase routing table size
427
        sysctlTestAndSet( 'net.ipv4.route.max_size', 32768 )
428
        #Increase number of PTYs for nodes
429
        sysctlTestAndSet( 'kernel.pty.max', 20000 )
430
    except Exception:
431
        warn( "*** Error setting resource limits. "
432
              "Mininet's performance may be affected.\n" )
433

    
434
def mountCgroups():
435
    "Make sure cgroups file system is mounted"
436
    mounts = quietRun( 'cat /proc/mounts' )
437
    cgdir = '/sys/fs/cgroup'
438
    csdir = cgdir + '/cpuset'
439
    if ('cgroup %s' % cgdir not in mounts and
440
            'cgroups %s' % cgdir not in mounts):
441
        raise Exception( "cgroups not mounted on " + cgdir )
442
    if 'cpuset %s' % csdir not in mounts:
443
        errRun( 'mkdir -p ' + csdir )
444
        errRun( 'mount -t cgroup -ocpuset cpuset ' + csdir )
445

    
446
def natural( text ):
447
    "To sort sanely/alphabetically: sorted( l, key=natural )"
448
    def num( s ):
449
        "Convert text segment to int if necessary"
450
        return int( s ) if s.isdigit() else s
451
    return [  num( s ) for s in re.split( r'(\d+)', str( text ) ) ]
452

    
453
def naturalSeq( t ):
454
    "Natural sort key function for sequences"
455
    return [ natural( x ) for x in t ]
456

    
457
def numCores():
458
    "Returns number of CPU cores based on /proc/cpuinfo"
459
    if hasattr( numCores, 'ncores' ):
460
        return numCores.ncores
461
    try:
462
        numCores.ncores = int( quietRun('grep -c processor /proc/cpuinfo') )
463
    except ValueError:
464
        return 0
465
    return numCores.ncores
466

    
467
def irange(start, end):
468
    """Inclusive range from start to end (vs. Python insanity.)
469
       irange(1,5) -> 1, 2, 3, 4, 5"""
470
    return range( start, end + 1 )
471

    
472
def custom( cls, **params ):
473
    "Returns customized constructor for class cls."
474
    # Note: we may wish to see if we can use functools.partial() here
475
    # and in customConstructor
476
    def customized( *args, **kwargs):
477
        "Customized constructor"
478
        kwargs = kwargs.copy()
479
        kwargs.update( params )
480
        return cls( *args, **kwargs )
481
    customized.__name__ = 'custom(%s,%s)' % ( cls, params )
482
    return customized
483

    
484
def splitArgs( argstr ):
485
    """Split argument string into usable python arguments
486
       argstr: argument string with format fn,arg2,kw1=arg3...
487
       returns: fn, args, kwargs"""
488
    split = argstr.split( ',' )
489
    fn = split[ 0 ]
490
    params = split[ 1: ]
491
    # Convert int and float args; removes the need for function
492
    # to be flexible with input arg formats.
493
    args = [ makeNumeric( s ) for s in params if '=' not in s ]
494
    kwargs = {}
495
    for s in [ p for p in params if '=' in p ]:
496
        key, val = s.split( '=', 1 )
497
        kwargs[ key ] = makeNumeric( val )
498
    return fn, args, kwargs
499

    
500
def customConstructor( constructors, argStr ):
501
    """Return custom constructor based on argStr
502
    The args and key/val pairs in argsStr will be automatically applied
503
    when the generated constructor is later used.
504
    """
505
    cname, newargs, kwargs = splitArgs( argStr )
506
    constructor = constructors.get( cname, None )
507

    
508
    if not constructor:
509
        raise Exception( "error: %s is unknown - please specify one of %s" %
510
                         ( cname, constructors.keys() ) )
511

    
512
    def customized( name, *args, **params ):
513
        "Customized constructor, useful for Node, Link, and other classes"
514
        params = params.copy()
515
        params.update( kwargs )
516
        if not newargs:
517
            return constructor( name, *args, **params )
518
        if args:
519
            warn( 'warning: %s replacing %s with %s\n' % (
520
                  constructor, args, newargs ) )
521
        return constructor( name, *newargs, **params )
522

    
523
    customized.__name__ = 'customConstructor(%s)' % argStr
524
    return customized
525

    
526
def buildTopo( topos, topoStr ):
527
    """Create topology from string with format (object, arg1, arg2,...).
528
    input topos is a dict of topo names to constructors, possibly w/args.
529
    """
530
    topo, args, kwargs = splitArgs( topoStr )
531
    if topo not in topos:
532
        raise Exception( 'Invalid topo name %s' % topo )
533
    return topos[ topo ]( *args, **kwargs )
534

    
535
def ensureRoot():
536
    """Ensure that we are running as root.
537

538
    Probably we should only sudo when needed as per Big Switch's patch.
539
    """
540
    if os.getuid() != 0:
541
        print "*** Mininet must run as root."
542
        exit( 1 )
543
    return
544

    
545
def waitListening( client=None, server='127.0.0.1', port=80, timeout=None ):
546
    """Wait until server is listening on port.
547
       returns True if server is listening"""
548
    runCmd = ( client.cmd if client else
549
                partial( quietRun, shell=True ) )
550
    if not runCmd( 'which telnet' ):
551
        raise Exception('Could not find telnet' )
552
    serverIP = server if isinstance( server, basestring ) else server.IP()
553
    cmd = ( 'sh -c "echo A | telnet -e A %s %s"' %
554
           ( serverIP, port ) )
555
    time = 0
556
    while 'Connected' not in run( cmd ):
557
        if timeout:
558
            print time
559
            if time >= timeout:
560
                error( 'could not connect to %s on port %d\n'
561
                       % ( server, port ) )
562
                return False
563
        output('waiting for', server,
564
               'to listen on port', port, '\n')
565
        sleep( .5 )
566
        time += .5
567
    return True