Statistics
| Branch: | Tag: | Revision:

mininet / mininet / util.py @ 41a54f05

History | View | Annotate | Download (17.8 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

    
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, addr1=None, addr2=None ):
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
    if addr1 is None and addr2 is None:
158
        cmd = 'ip link add name ' + intf1 + ' type veth peer name ' + intf2
159
    else:
160
        cmd = ( 'ip link add name ' + intf1 + ' address ' + addr1 +
161
                ' type veth peer name ' + intf2 + ' address ' + addr2 )
162
    cmdOutput = quietRun( cmd )
163
    if cmdOutput == '':
164
        return True
165
    else:
166
        error( "Error creating interface pair: %s " % cmdOutput )
167
        return False
168

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

    
183
def moveIntfNoRetry( intf, dstNode, srcNode=None, printError=False ):
184
    """Move interface to node, without retrying.
185
       intf: string, interface
186
        dstNode: destination Node
187
        srcNode: source Node or None (default) for root ns
188
        printError: if true, print error"""
189
    intf = str( intf )
190
    cmd = 'ip link set %s netns %s' % ( intf, dstNode.pid )
191
    if srcNode:
192
        cmdOutput = srcNode.cmd( cmd )
193
    else:
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
        return False
202
    return True
203

    
204
def moveIntf( intf, dstNode, srcNode=None, printError=False,
205
             retries=3, delaySecs=0.001 ):
206
    """Move interface to node, retrying on failure.
207
       intf: string, interface
208
       dstNode: destination Node
209
       srcNode: source Node or None (default) for root ns
210
       printError: if true, print error"""
211
    retry( retries, delaySecs, moveIntfNoRetry, intf, dstNode,
212
          srcNode=srcNode, 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
# IP and Mac address formatting and parsing
241

    
242
def _colonHex( val, bytecount ):
243
    """Generate colon-hex string.
244
       val: input as unsigned int
245
       bytecount: number of bytes to convert
246
       returns: chStr colon-hex string"""
247
    pieces = []
248
    for i in range( bytecount - 1, -1, -1 ):
249
        piece = ( ( 0xff << ( i * 8 ) ) & val ) >> ( i * 8 )
250
        pieces.append( '%02x' % piece )
251
    chStr = ':'.join( pieces )
252
    return chStr
253

    
254
def macColonHex( mac ):
255
    """Generate MAC colon-hex string from unsigned int.
256
       mac: MAC address as unsigned int
257
       returns: macStr MAC colon-hex string"""
258
    return _colonHex( mac, 6 )
259

    
260
def ipStr( ip ):
261
    """Generate IP address string from an unsigned int.
262
       ip: unsigned int of form w << 24 | x << 16 | y << 8 | z
263
       returns: ip address string w.x.y.z"""
264
    w = ( ip >> 24 ) & 0xff
265
    x = ( ip >> 16 ) & 0xff
266
    y = ( ip >> 8 ) & 0xff
267
    z = ip & 0xff
268
    return "%i.%i.%i.%i" % ( w, x, y, z )
269

    
270
def ipNum( w, x, y, z ):
271
    """Generate unsigned int from components of IP address
272
       returns: w << 24 | x << 16 | y << 8 | z"""
273
    return ( w << 24 ) | ( x << 16 ) | ( y << 8 ) | z
274

    
275
def ipAdd( i, prefixLen=8, ipBaseNum=0x0a000000 ):
276
    """Return IP address string from ints
277
       i: int to be added to ipbase
278
       prefixLen: optional IP prefix length
279
       ipBaseNum: option base IP address as int
280
       returns IP address as string"""
281
    imax = 0xffffffff >> prefixLen
282
    assert i <= imax, 'Not enough IP addresses in the subnet'
283
    mask = 0xffffffff ^ imax
284
    ipnum = ( ipBaseNum & mask ) + i
285
    return ipStr( ipnum )
286

    
287
def ipParse( ip ):
288
    "Parse an IP address and return an unsigned int."
289
    args = [ int( arg ) for arg in ip.split( '.' ) ]
290
    while ( len(args) < 4 ):
291
        args.append( 0 )
292
    return ipNum( *args )
293

    
294
def netParse( ipstr ):
295
    """Parse an IP network specification, returning
296
       address and prefix len as unsigned ints"""
297
    prefixLen = 0
298
    if '/' in ipstr:
299
        ip, pf = ipstr.split( '/' )
300
        prefixLen = int( pf )
301
    #if no prefix is specified, set the prefix to 24
302
    else:
303
        ip = ipstr
304
        prefixLen = 24
305
    return ipParse( ip ), prefixLen
306

    
307
def checkInt( s ):
308
    "Check if input string is an int"
309
    try:
310
        int( s )
311
        return True
312
    except ValueError:
313
        return False
314

    
315
def checkFloat( s ):
316
    "Check if input string is a float"
317
    try:
318
        float( s )
319
        return True
320
    except ValueError:
321
        return False
322

    
323
def makeNumeric( s ):
324
    "Convert string to int or float if numeric."
325
    if checkInt( s ):
326
        return int( s )
327
    elif checkFloat( s ):
328
        return float( s )
329
    else:
330
        return s
331

    
332
# Popen support
333

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

    
373
# Other stuff we use
374
def sysctlTestAndSet( name, limit ):
375
    "Helper function to set sysctl limits"
376
    #convert non-directory names into directory names
377
    if '/' not in name:
378
        name = '/proc/sys/' + name.replace( '.', '/' )
379
    #read limit
380
    with open( name, 'r' ) as readFile:
381
        oldLimit = readFile.readline()
382
        if type( limit ) is int:
383
            #compare integer limits before overriding
384
            if int( oldLimit ) < limit:
385
                with open( name, 'w' ) as writeFile:
386
                    writeFile.write( "%d" % limit )
387
        else:
388
            #overwrite non-integer limits
389
            with open( name, 'w' ) as writeFile:
390
                writeFile.write( limit )
391

    
392
def rlimitTestAndSet( name, limit ):
393
    "Helper function to set rlimits"
394
    soft, hard = getrlimit( name )
395
    if soft < limit:
396
        hardLimit = hard if limit < hard else limit
397
        setrlimit( name, ( limit, hardLimit ) )
398

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

    
425
def mountCgroups():
426
    "Make sure cgroups file system is mounted"
427
    mounts = quietRun( 'cat /proc/mounts' )
428
    cgdir = '/sys/fs/cgroup'
429
    csdir = cgdir + '/cpuset'
430
    if ('cgroup %s' % cgdir not in mounts and
431
            'cgroups %s' % cgdir not in mounts):
432
        raise Exception( "cgroups not mounted on " + cgdir )
433
    if 'cpuset %s' % csdir not in mounts:
434
        errRun( 'mkdir -p ' + csdir )
435
        errRun( 'mount -t cgroup -ocpuset cpuset ' + csdir )
436

    
437
def natural( text ):
438
    "To sort sanely/alphabetically: sorted( l, key=natural )"
439
    def num( s ):
440
        "Convert text segment to int if necessary"
441
        return int( s ) if s.isdigit() else s
442
    return [  num( s ) for s in re.split( r'(\d+)', text ) ]
443

    
444
def naturalSeq( t ):
445
    "Natural sort key function for sequences"
446
    return [ natural( x ) for x in t ]
447

    
448
def numCores():
449
    "Returns number of CPU cores based on /proc/cpuinfo"
450
    if hasattr( numCores, 'ncores' ):
451
        return numCores.ncores
452
    try:
453
        numCores.ncores = int( quietRun('grep -c processor /proc/cpuinfo') )
454
    except ValueError:
455
        return 0
456
    return numCores.ncores
457

    
458
def irange(start, end):
459
    """Inclusive range from start to end (vs. Python insanity.)
460
       irange(1,5) -> 1, 2, 3, 4, 5"""
461
    return range( start, end + 1 )
462

    
463
def custom( cls, **params ):
464
    "Returns customized constructor for class cls."
465
    # Note: we may wish to see if we can use functools.partial() here
466
    # and in customConstructor
467
    def customized( *args, **kwargs):
468
        "Customized constructor"
469
        kwargs = kwargs.copy()
470
        kwargs.update( params )
471
        return cls( *args, **kwargs )
472
    customized.__name__ = 'custom(%s,%s)' % ( cls, params )
473
    return customized
474

    
475
def splitArgs( argstr ):
476
    """Split argument string into usable python arguments
477
       argstr: argument string with format fn,arg2,kw1=arg3...
478
       returns: fn, args, kwargs"""
479
    split = argstr.split( ',' )
480
    fn = split[ 0 ]
481
    params = split[ 1: ]
482
    # Convert int and float args; removes the need for function
483
    # to be flexible with input arg formats.
484
    args = [ makeNumeric( s ) for s in params if '=' not in s ]
485
    kwargs = {}
486
    for s in [ p for p in params if '=' in p ]:
487
        key, val = s.split( '=', 1 )
488
        kwargs[ key ] = makeNumeric( val )
489
    return fn, args, kwargs
490

    
491
def customConstructor( constructors, argStr ):
492
    """Return custom constructor based on argStr
493
    The args and key/val pairs in argsStr will be automatically applied
494
    when the generated constructor is later used.
495
    """
496
    cname, newargs, kwargs = splitArgs( argStr )
497
    constructor = constructors.get( cname, None )
498

    
499
    if not constructor:
500
        raise Exception( "error: %s is unknown - please specify one of %s" %
501
                         ( cname, constructors.keys() ) )
502

    
503
    def customized( name, *args, **params ):
504
        "Customized constructor, useful for Node, Link, and other classes"
505
        params = params.copy()
506
        params.update( kwargs )
507
        if not newargs:
508
            return constructor( name, *args, **params )
509
        if args:
510
            warn( 'warning: %s replacing %s with %s\n' % (
511
                  constructor, args, newargs ) )
512
        return constructor( name, *newargs, **params )
513

    
514
    customized.__name__ = 'customConstructor(%s)' % argStr
515
    return customized
516

    
517
def buildTopo( topos, topoStr ):
518
    """Create topology from string with format (object, arg1, arg2,...).
519
    input topos is a dict of topo names to constructors, possibly w/args.
520
    """
521
    topo, args, kwargs = splitArgs( topoStr )
522
    if topo not in topos:
523
        raise Exception( 'Invalid topo name %s' % topo )
524
    return topos[ topo ]( *args, **kwargs )
525

    
526
def ensureRoot():
527
    """Ensure that we are running as root.
528

529
    Probably we should only sudo when needed as per Big Switch's patch.
530
    """
531
    if os.getuid() != 0:
532
        print "*** Mininet must run as root."
533
        exit( 1 )
534
    return