Statistics
| Branch: | Tag: | Revision:

mininet / mininet / util.py @ 98a8231c

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
    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
            if event & POLLIN:
97
                data = f.read( 1024 )
98
                if echo:
99
                    output( data )
100
                if f == popen.stdout:
101
                    out += data
102
                    if data == '':
103
                        outDone = True
104
                elif f == popen.stderr:
105
                    err += data
106
                    if data == '':
107
                        errDone = True
108
            else:  # POLLHUP or something unexpected
109
                if f == popen.stdout:
110
                    outDone = True
111
                elif f == popen.stderr:
112
                    errDone = True
113
                poller.unregister( fd )
114

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

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

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

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

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

    
141
isShellBuiltin.builtIns = None
142

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

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

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

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

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

    
235
# Support for dumping network
236

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

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

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

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

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

    
270
# IP and Mac address formatting and parsing
271

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

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

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

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

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

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

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

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

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

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

    
362
# Popen support
363

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

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

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

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

    
457

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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