Statistics
| Branch: | Tag: | Revision:

mininet / mininet / util.py @ 959586bc

History | View | Annotate | Download (20.2 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=maybe-no-member
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
    debug( '*** errRun:', cmd, '\n' )
81
    popen = Popen( cmd, stdout=PIPE, stderr=stderr, shell=shell )
82
    # We use poll() because select() doesn't work with large fd numbers,
83
    # and thus communicate() doesn't work either
84
    out, err = '', ''
85
    poller = poll()
86
    poller.register( popen.stdout, POLLIN )
87
    fdtofile = { popen.stdout.fileno(): popen.stdout }
88
    outDone, errDone = False, True
89
    if popen.stderr:
90
        fdtofile[ popen.stderr.fileno() ] = popen.stderr
91
        poller.register( popen.stderr, POLLIN )
92
        errDone = False
93
    while not outDone or not errDone:
94
        readable = poller.poll()
95
        for fd, event in readable:
96
            f = fdtofile[ fd ]
97
            if event & POLLIN:
98
                data = f.read( 1024 )
99
                if echo:
100
                    output( data )
101
                if f == popen.stdout:
102
                    out += data
103
                    if data == '':
104
                        outDone = True
105
                elif f == popen.stderr:
106
                    err += data
107
                    if data == '':
108
                        errDone = True
109
            else:  # POLLHUP or something unexpected
110
                if f == popen.stdout:
111
                    outDone = True
112
                elif f == popen.stderr:
113
                    errDone = True
114
                poller.unregister( fd )
115

    
116
    returncode = popen.wait()
117
    return out, err, returncode
118

    
119
def errFail( *cmd, **kwargs ):
120
    "Run a command using errRun and raise exception on nonzero exit"
121
    out, err, ret = errRun( *cmd, **kwargs )
122
    if ret:
123
        raise Exception( "errFail: %s failed with return code %s: %s"
124
                         % ( cmd, ret, err ) )
125
    return out, err, ret
126

    
127
def quietRun( cmd, **kwargs ):
128
    "Run a command and return merged stdout and stderr"
129
    return errRun( cmd, stderr=STDOUT, **kwargs )[ 0 ]
130

    
131
# pylint: enable=maybe-no-member
132

    
133
def isShellBuiltin( cmd ):
134
    "Return True if cmd is a bash builtin."
135
    if isShellBuiltin.builtIns is None:
136
        isShellBuiltin.builtIns = quietRun( 'bash -c enable' )
137
    space = cmd.find( ' ' )
138
    if space > 0:
139
        cmd = cmd[ :space]
140
    return cmd in isShellBuiltin.builtIns
141

    
142
isShellBuiltin.builtIns = None
143

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

    
157
def makeIntfPair( intf1, intf2, addr1=None, addr2=None, node1=None, node2=None,
158
                  deleteIntfs=True, runCmd=None ):
159
    """Make a veth pair connnecting new interfaces intf1 and intf2
160
       intf1: name for interface 1
161
       intf2: name for interface 2
162
       addr1: MAC address for interface 1 (optional)
163
       addr2: MAC address for interface 2 (optional)
164
       node1: home node for interface 1 (optional)
165
       node2: home node for interface 2 (optional)
166
       deleteIntfs: delete intfs before creating them
167
       runCmd: function to run shell commands (quietRun)
168
       returns: ip link add result"""
169
    if not runCmd:
170
        runCmd = quietRun if not node1 else node1.cmd
171
        runCmd2 = quietRun if not node2 else node2.cmd
172
    if deleteIntfs:
173
        # Delete any old interfaces with the same names
174
        runCmd( 'ip link del ' + intf1 )
175
        runCmd2( 'ip link del ' + intf2 )
176
    # Create new pair
177
    netns = 1 if not node2 else node2.pid
178
    if addr1 is None and addr2 is None:
179
        cmdOutput = runCmd( 'ip link add name %s '
180
                            'type veth peer name %s '
181
                            'netns %s' % ( intf1, intf2, netns ) )
182
    else:
183
        cmdOutput = runCmd( 'ip link add name %s '
184
                            'address %s '
185
                            'type veth peer name %s '
186
                            'address %s '
187
                            'netns %s' %
188
                            (  intf1, addr1, intf2, addr2, netns ) )
189
    if cmdOutput == '':
190
        return True
191
    else:
192
        error( "Error creating interface pair: %s " % cmdOutput )
193
        return False
194

    
195
def retry( retries, delaySecs, fn, *args, **keywords ):
196
    """Try something several times before giving up.
197
       n: number of times to retry
198
       delaySecs: wait this long between tries
199
       fn: function to call
200
       args: args to apply to function call"""
201
    tries = 0
202
    while not fn( *args, **keywords ) and tries < retries:
203
        sleep( delaySecs )
204
        tries += 1
205
    if tries >= retries:
206
        error( "*** gave up after %i retries\n" % tries )
207
        exit( 1 )
208

    
209
def moveIntfNoRetry( intf, dstNode, printError=False ):
210
    """Move interface to node, without retrying.
211
       intf: string, interface
212
        dstNode: destination Node
213
        printError: if true, print error"""
214
    intf = str( intf )
215
    cmd = 'ip link set %s netns %s' % ( intf, dstNode.pid )
216
    cmdOutput = quietRun( cmd )
217
    # If ip link set does not produce any output, then we can assume
218
    # that the link has been moved successfully.
219
    if cmdOutput:
220
        if printError:
221
            error( '*** Error: moveIntf: ' + intf +
222
                   ' not successfully moved to ' + dstNode.name + ':\n',
223
                   cmdOutput )
224
        return False
225
    return True
226

    
227
def moveIntf( intf, dstNode, printError=True,
228
              retries=3, delaySecs=0.001 ):
229
    """Move interface to node, retrying on failure.
230
       intf: string, interface
231
       dstNode: destination Node
232
       printError: if true, print error"""
233
    retry( retries, delaySecs, moveIntfNoRetry, intf, dstNode,
234
           printError=printError )
235

    
236
# Support for dumping network
237

    
238
def dumpNodeConnections( nodes ):
239
    "Dump connections to/from nodes."
240

    
241
    def dumpConnections( node ):
242
        "Helper function: dump connections to node"
243
        for intf in node.intfList():
244
            output( ' %s:' % intf )
245
            if intf.link:
246
                intfs = [ intf.link.intf1, intf.link.intf2 ]
247
                intfs.remove( intf )
248
                output( intfs[ 0 ] )
249
            else:
250
                output( ' ' )
251

    
252
    for node in nodes:
253
        output( node.name )
254
        dumpConnections( node )
255
        output( '\n' )
256

    
257
def dumpNetConnections( net ):
258
    "Dump connections in network"
259
    nodes = net.controllers + net.switches + net.hosts
260
    dumpNodeConnections( nodes )
261

    
262
def dumpPorts( switches ):
263
    "dump interface to openflow port mappings for each switch"
264
    for switch in switches:
265
        output( '%s ' % switch.name )
266
        for intf in switch.intfList():
267
            port = switch.ports[ intf ]
268
            output( '%s:%d ' % ( intf, port ) )
269
        output( '\n' )
270

    
271
# IP and Mac address formatting and parsing
272

    
273
def _colonHex( val, bytecount ):
274
    """Generate colon-hex string.
275
       val: input as unsigned int
276
       bytecount: number of bytes to convert
277
       returns: chStr colon-hex string"""
278
    pieces = []
279
    for i in range( bytecount - 1, -1, -1 ):
280
        piece = ( ( 0xff << ( i * 8 ) ) & val ) >> ( i * 8 )
281
        pieces.append( '%02x' % piece )
282
    chStr = ':'.join( pieces )
283
    return chStr
284

    
285
def macColonHex( mac ):
286
    """Generate MAC colon-hex string from unsigned int.
287
       mac: MAC address as unsigned int
288
       returns: macStr MAC colon-hex string"""
289
    return _colonHex( mac, 6 )
290

    
291
def ipStr( ip ):
292
    """Generate IP address string from an unsigned int.
293
       ip: unsigned int of form w << 24 | x << 16 | y << 8 | z
294
       returns: ip address string w.x.y.z"""
295
    w = ( ip >> 24 ) & 0xff
296
    x = ( ip >> 16 ) & 0xff
297
    y = ( ip >> 8 ) & 0xff
298
    z = ip & 0xff
299
    return "%i.%i.%i.%i" % ( w, x, y, z )
300

    
301
def ipNum( w, x, y, z ):
302
    """Generate unsigned int from components of IP address
303
       returns: w << 24 | x << 16 | y << 8 | z"""
304
    return ( w << 24 ) | ( x << 16 ) | ( y << 8 ) | z
305

    
306
def ipAdd( i, prefixLen=8, ipBaseNum=0x0a000000 ):
307
    """Return IP address string from ints
308
       i: int to be added to ipbase
309
       prefixLen: optional IP prefix length
310
       ipBaseNum: option base IP address as int
311
       returns IP address as string"""
312
    imax = 0xffffffff >> prefixLen
313
    assert i <= imax, 'Not enough IP addresses in the subnet'
314
    mask = 0xffffffff ^ imax
315
    ipnum = ( ipBaseNum & mask ) + i
316
    return ipStr( ipnum )
317

    
318
def ipParse( ip ):
319
    "Parse an IP address and return an unsigned int."
320
    args = [ int( arg ) for arg in ip.split( '.' ) ]
321
    while len(args) < 4:
322
        args.append( 0 )
323
    return ipNum( *args )
324

    
325
def netParse( ipstr ):
326
    """Parse an IP network specification, returning
327
       address and prefix len as unsigned ints"""
328
    prefixLen = 0
329
    if '/' in ipstr:
330
        ip, pf = ipstr.split( '/' )
331
        prefixLen = int( pf )
332
    #if no prefix is specified, set the prefix to 24
333
    else:
334
        ip = ipstr
335
        prefixLen = 24
336
    return ipParse( ip ), prefixLen
337

    
338
def checkInt( s ):
339
    "Check if input string is an int"
340
    try:
341
        int( s )
342
        return True
343
    except ValueError:
344
        return False
345

    
346
def checkFloat( s ):
347
    "Check if input string is a float"
348
    try:
349
        float( s )
350
        return True
351
    except ValueError:
352
        return False
353

    
354
def makeNumeric( s ):
355
    "Convert string to int or float if numeric."
356
    if checkInt( s ):
357
        return int( s )
358
    elif checkFloat( s ):
359
        return float( s )
360
    else:
361
        return s
362

    
363
# Popen support
364

    
365
def pmonitor(popens, timeoutms=500, readline=True,
366
             readmax=1024 ):
367
    """Monitor dict of hosts to popen objects
368
       a line at a time
369
       timeoutms: timeout for poll()
370
       readline: return single line of output
371
       yields: host, line/output (if any)
372
       terminates: when all EOFs received"""
373
    poller = poll()
374
    fdToHost = {}
375
    for host, popen in popens.iteritems():
376
        fd = popen.stdout.fileno()
377
        fdToHost[ fd ] = host
378
        poller.register( fd, POLLIN )
379
        if not readline:
380
            # Use non-blocking reads
381
            flags = fcntl( fd, F_GETFL )
382
            fcntl( fd, F_SETFL, flags | O_NONBLOCK )
383
    while popens:
384
        fds = poller.poll( timeoutms )
385
        if fds:
386
            for fd, event in fds:
387
                host = fdToHost[ fd ]
388
                popen = popens[ host ]
389
                if event & POLLIN:
390
                    if readline:
391
                        # Attempt to read a line of output
392
                        # This blocks until we receive a newline!
393
                        line = popen.stdout.readline()
394
                    else:
395
                        line = popen.stdout.read( readmax )
396
                    yield host, line
397
                # Check for EOF
398
                elif event & POLLHUP:
399
                    poller.unregister( fd )
400
                    del popens[ host ]
401
        else:
402
            yield None, ''
403

    
404
# Other stuff we use
405
def sysctlTestAndSet( name, limit ):
406
    "Helper function to set sysctl limits"
407
    #convert non-directory names into directory names
408
    if '/' not in name:
409
        name = '/proc/sys/' + name.replace( '.', '/' )
410
    #read limit
411
    with open( name, 'r' ) as readFile:
412
        oldLimit = readFile.readline()
413
        if isinstance( limit, int ):
414
            #compare integer limits before overriding
415
            if int( oldLimit ) < limit:
416
                with open( name, 'w' ) as writeFile:
417
                    writeFile.write( "%d" % limit )
418
        else:
419
            #overwrite non-integer limits
420
            with open( name, 'w' ) as writeFile:
421
                writeFile.write( limit )
422

    
423
def rlimitTestAndSet( name, limit ):
424
    "Helper function to set rlimits"
425
    soft, hard = getrlimit( name )
426
    if soft < limit:
427
        hardLimit = hard if limit < hard else limit
428
        setrlimit( name, ( limit, hardLimit ) )
429

    
430
def fixLimits():
431
    "Fix ridiculously small resource limits."
432
    debug( "*** Setting resource limits\n" )
433
    try:
434
        rlimitTestAndSet( RLIMIT_NPROC, 8192 )
435
        rlimitTestAndSet( RLIMIT_NOFILE, 16384 )
436
        #Increase open file limit
437
        sysctlTestAndSet( 'fs.file-max', 10000 )
438
        #Increase network buffer space
439
        sysctlTestAndSet( 'net.core.wmem_max', 16777216 )
440
        sysctlTestAndSet( 'net.core.rmem_max', 16777216 )
441
        sysctlTestAndSet( 'net.ipv4.tcp_rmem', '10240 87380 16777216' )
442
        sysctlTestAndSet( 'net.ipv4.tcp_wmem', '10240 87380 16777216' )
443
        sysctlTestAndSet( 'net.core.netdev_max_backlog', 5000 )
444
        #Increase arp cache size
445
        sysctlTestAndSet( 'net.ipv4.neigh.default.gc_thresh1', 4096 )
446
        sysctlTestAndSet( 'net.ipv4.neigh.default.gc_thresh2', 8192 )
447
        sysctlTestAndSet( 'net.ipv4.neigh.default.gc_thresh3', 16384 )
448
        #Increase routing table size
449
        sysctlTestAndSet( 'net.ipv4.route.max_size', 32768 )
450
        #Increase number of PTYs for nodes
451
        sysctlTestAndSet( 'kernel.pty.max', 20000 )
452
    # pylint: disable=broad-except
453
    except Exception:
454
        warn( "*** Error setting resource limits. "
455
              "Mininet's performance may be affected.\n" )
456
    # pylint: enable=broad-except
457

    
458

    
459
def mountCgroups():
460
    "Make sure cgroups file system is mounted"
461
    mounts = quietRun( 'cat /proc/mounts' )
462
    cgdir = '/sys/fs/cgroup'
463
    csdir = cgdir + '/cpuset'
464
    if ('cgroup %s' % cgdir not in mounts and
465
            'cgroups %s' % cgdir not in mounts):
466
        raise Exception( "cgroups not mounted on " + cgdir )
467
    if 'cpuset %s' % csdir not in mounts:
468
        errRun( 'mkdir -p ' + csdir )
469
        errRun( 'mount -t cgroup -ocpuset cpuset ' + csdir )
470

    
471
def natural( text ):
472
    "To sort sanely/alphabetically: sorted( l, key=natural )"
473
    def num( s ):
474
        "Convert text segment to int if necessary"
475
        return int( s ) if s.isdigit() else s
476
    return [  num( s ) for s in re.split( r'(\d+)', str( text ) ) ]
477

    
478
def naturalSeq( t ):
479
    "Natural sort key function for sequences"
480
    return [ natural( x ) for x in t ]
481

    
482
def numCores():
483
    "Returns number of CPU cores based on /proc/cpuinfo"
484
    if hasattr( numCores, 'ncores' ):
485
        return numCores.ncores
486
    try:
487
        numCores.ncores = int( quietRun('grep -c processor /proc/cpuinfo') )
488
    except ValueError:
489
        return 0
490
    return numCores.ncores
491

    
492
def irange(start, end):
493
    """Inclusive range from start to end (vs. Python insanity.)
494
       irange(1,5) -> 1, 2, 3, 4, 5"""
495
    return range( start, end + 1 )
496

    
497
def custom( cls, **params ):
498
    "Returns customized constructor for class cls."
499
    # Note: we may wish to see if we can use functools.partial() here
500
    # and in customConstructor
501
    def customized( *args, **kwargs):
502
        "Customized constructor"
503
        kwargs = kwargs.copy()
504
        kwargs.update( params )
505
        return cls( *args, **kwargs )
506
    customized.__name__ = 'custom(%s,%s)' % ( cls, params )
507
    return customized
508

    
509
def splitArgs( argstr ):
510
    """Split argument string into usable python arguments
511
       argstr: argument string with format fn,arg2,kw1=arg3...
512
       returns: fn, args, kwargs"""
513
    split = argstr.split( ',' )
514
    fn = split[ 0 ]
515
    params = split[ 1: ]
516
    # Convert int and float args; removes the need for function
517
    # to be flexible with input arg formats.
518
    args = [ makeNumeric( s ) for s in params if '=' not in s ]
519
    kwargs = {}
520
    for s in [ p for p in params if '=' in p ]:
521
        key, val = s.split( '=', 1 )
522
        kwargs[ key ] = makeNumeric( val )
523
    return fn, args, kwargs
524

    
525
def customConstructor( constructors, argStr ):
526
    """Return custom constructor based on argStr
527
    The args and key/val pairs in argsStr will be automatically applied
528
    when the generated constructor is later used.
529
    """
530
    cname, newargs, kwargs = splitArgs( argStr )
531
    constructor = constructors.get( cname, None )
532

    
533
    if not constructor:
534
        raise Exception( "error: %s is unknown - please specify one of %s" %
535
                         ( cname, constructors.keys() ) )
536

    
537
    def customized( name, *args, **params ):
538
        "Customized constructor, useful for Node, Link, and other classes"
539
        params = params.copy()
540
        params.update( kwargs )
541
        if not newargs:
542
            return constructor( name, *args, **params )
543
        if args:
544
            warn( 'warning: %s replacing %s with %s\n' % (
545
                  constructor, args, newargs ) )
546
        return constructor( name, *newargs, **params )
547

    
548
    customized.__name__ = 'customConstructor(%s)' % argStr
549
    return customized
550

    
551
def buildTopo( topos, topoStr ):
552
    """Create topology from string with format (object, arg1, arg2,...).
553
    input topos is a dict of topo names to constructors, possibly w/args.
554
    """
555
    topo, args, kwargs = splitArgs( topoStr )
556
    if topo not in topos:
557
        raise Exception( 'Invalid topo name %s' % topo )
558
    return topos[ topo ]( *args, **kwargs )
559

    
560
def ensureRoot():
561
    """Ensure that we are running as root.
562

563
    Probably we should only sudo when needed as per Big Switch's patch.
564
    """
565
    if os.getuid() != 0:
566
        print "*** Mininet must run as root."
567
        exit( 1 )
568
    return
569

    
570
def waitListening( client=None, server='127.0.0.1', port=80, timeout=None ):
571
    """Wait until server is listening on port.
572
       returns True if server is listening"""
573
    runCmd = ( client.cmd if client else
574
               partial( quietRun, shell=True ) )
575
    if not runCmd( 'which telnet' ):
576
        raise Exception('Could not find telnet' )
577
    # pylint: disable=maybe-no-member
578
    serverIP = server if isinstance( server, basestring ) else server.IP()
579
    cmd = ( 'sh -c "echo A | telnet -e A %s %s"' %
580
            ( serverIP, port ) )
581
    time = 0
582
    while 'Connected' not in runCmd( cmd ):
583
        if timeout:
584
            print time
585
            if time >= timeout:
586
                error( 'could not connect to %s on port %d\n'
587
                       % ( server, port ) )
588
                return False
589
        output('waiting for', server,
590
               'to listen on port', port, '\n')
591
        sleep( .5 )
592
        time += .5
593
    return True