Statistics
| Branch: | Tag: | Revision:

mininet / mininet / util.py @ 149a1f56

History | View | Annotate | Download (11.1 KB)

1
"Utility functions for Mininet."
2

    
3
from time import sleep
4
from resource import setrlimit, RLIMIT_NPROC, RLIMIT_NOFILE
5
from select import poll, POLLIN
6
from subprocess import call, check_call, Popen, PIPE, STDOUT
7
from mininet.log import output, info, error
8
import re
9

    
10
# Command execution support
11

    
12
def run( cmd ):
13
    """Simple interface to subprocess.call()
14
       cmd: list of command params"""
15
    return call( cmd.split( ' ' ) )
16

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

    
22
# pylint doesn't understand explicit type checking
23
# pylint: disable-msg=E1103
24

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

    
50

    
51
# This is a bit complicated, but it enables us to
52
# monitor commount output as it is happening
53

    
54
def errRun( *cmd, **kwargs ):
55
    """Run a command and return stdout, stderr and return code
56
       cmd: string or list of command and args
57
       stderr: STDOUT to merge stderr with stdout
58
       shell: run command using shell
59
       echo: monitor output to console"""
60
    # Allow passing in a list or a string
61
    if len( cmd ) == 1:
62
        cmd = cmd[ 0 ]
63
        if isinstance( cmd, str ):
64
            cmd = cmd.split( ' ' )
65
    cmd = [ str( arg ) for arg in cmd ]
66
    # By default we separate stderr, don't run in a shell, and don't echo
67
    stderr = kwargs.get( 'stderr', PIPE )
68
    shell = kwargs.get( 'shell', False )
69
    echo = kwargs.get( 'echo', False )
70
    if echo:
71
        # cmd goes to stderr, output goes to stdout
72
        info( cmd, '\n' )
73
    popen = Popen( cmd, stdout=PIPE, stderr=stderr, shell=shell )
74
    # We use poll() because select() doesn't work with large fd numbers
75
    out, err = '', ''
76
    poller = poll()
77
    poller.register( popen.stdout, POLLIN )
78
    fdtofile = { popen.stdout.fileno(): popen.stdout }
79
    if popen.stderr:
80
        fdtofile[ popen.stderr.fileno() ] = popen.stderr
81
        poller.register( popen.stderr, POLLIN )
82
    while True:
83
        readable = poller.poll()
84
        # Tell pylint to ignore unused variable event
85
        # pylint: disable-msg=W0612
86
        for fd, event in readable:
87
            # pylint: enable-msg=W0612
88
            f = fdtofile[ fd ]
89
            data = f.read( 1024 )
90
            if echo:
91
                output( data )
92
            if f == popen.stdout:
93
                out += data
94
            elif f == popen.stderr:
95
                err += data
96
        returncode = popen.poll()
97
        if returncode is not None:
98
            break
99
    return out, err, returncode
100

    
101
def errFail( *cmd, **kwargs ):
102
    "Run a command using errRun and raise exception on nonzero exit"
103
    out, err, ret = errRun( *cmd, **kwargs )
104
    if ret:
105
        raise Exception( "errFail: %s failed with return code %s: %s"
106
                         % ( cmd, ret, err ) )
107
    return out, err, ret
108

    
109
def quietRun( cmd, **kwargs ):
110
    "Run a command and return merged stdout and stderr"
111
    return errRun( cmd, stderr=STDOUT, **kwargs )[ 0 ]
112

    
113
# pylint: enable-msg=E1103
114
# pylint: disable-msg=E1101,W0612
115

    
116
def isShellBuiltin( cmd ):
117
    "Return True if cmd is a bash builtin."
118
    if isShellBuiltin.builtIns is None:
119
        isShellBuiltin.builtIns = quietRun( 'bash -c enable' )
120
    space = cmd.find( ' ' )
121
    if space > 0:
122
        cmd = cmd[ :space]
123
    return cmd in isShellBuiltin.builtIns
124

    
125
isShellBuiltin.builtIns = None
126

    
127
# pylint: enable-msg=E1101,W0612
128

    
129
# Interface management
130
#
131
# Interfaces are managed as strings which are simply the
132
# interface names, of the form 'nodeN-ethM'.
133
#
134
# To connect nodes, we create a pair of veth interfaces, and then place them
135
# in the pair of nodes that we want to communicate. We then update the node's
136
# list of interfaces and connectivity map.
137
#
138
# For the kernel datapath, switch interfaces
139
# live in the root namespace and thus do not have to be
140
# explicitly moved.
141

    
142
def makeIntfPair( intf1, intf2 ):
143
    """Make a veth pair connecting intf1 and intf2.
144
       intf1: string, interface
145
       intf2: string, interface
146
       returns: success boolean"""
147
    # Delete any old interfaces with the same names
148
    quietRun( 'ip link del ' + intf1 )
149
    quietRun( 'ip link del ' + intf2 )
150
    # Create new pair
151
    cmd = 'ip link add name ' + intf1 + ' type veth peer name ' + intf2
152
    return quietRun( cmd )
153

    
154
def retry( retries, delaySecs, fn, *args, **keywords ):
155
    """Try something several times before giving up.
156
       n: number of times to retry
157
       delaySecs: wait this long between tries
158
       fn: function to call
159
       args: args to apply to function call"""
160
    tries = 0
161
    while not fn( *args, **keywords ) and tries < retries:
162
        sleep( delaySecs )
163
        tries += 1
164
    if tries >= retries:
165
        error( "*** gave up after %i retries\n" % tries )
166
        exit( 1 )
167

    
168
def moveIntfNoRetry( intf, node, printError=False ):
169
    """Move interface to node, without retrying.
170
       intf: string, interface
171
       node: Node object
172
       printError: if true, print error"""
173
    cmd = 'ip link set ' + intf + ' netns ' + repr( node.pid )
174
    quietRun( cmd )
175
    links = node.cmd( 'ip link show' )
176
    if not ( ' %s:' % intf ) in links:
177
        if printError:
178
            error( '*** Error: moveIntf: ' + intf +
179
                ' not successfully moved to ' + node.name + '\n' )
180
        return False
181
    return True
182

    
183
def moveIntf( intf, node, printError=False, retries=3, delaySecs=0.001 ):
184
    """Move interface to node, retrying on failure.
185
       intf: string, interface
186
       node: Node object
187
       printError: if true, print error"""
188
    retry( retries, delaySecs, moveIntfNoRetry, intf, node, printError )
189

    
190
# Support for dumping network
191

    
192
def dumpNodeConnections( nodes ):
193
    "Dump connections to/from nodes."
194

    
195
    def dumpConnections( node ):
196
        "Helper function: dump connections to node"
197
        for intf in node.intfList():
198
            output( ' %s:' % intf )
199
            if intf.link:
200
                intfs = [ intf.link.intf1, intf.link.intf2 ]
201
                intfs.remove( intf )
202
                output( intfs[ 0 ] )
203
            else:
204
                output( ' ' )
205

    
206
    for node in nodes:
207
        output( node.name )
208
        dumpConnections( node )
209
        output( '\n' )
210

    
211
def dumpNetConnections( net ):
212
    "Dump connections in network"
213
    nodes = net.controllers + net.switches + net.hosts
214
    dumpNodeConnections( nodes )
215

    
216
# IP and Mac address formatting and parsing
217

    
218
def _colonHex( val, bytecount ):
219
    """Generate colon-hex string.
220
       val: input as unsigned int
221
       bytescount: number of bytes to convert
222
       returns: chStr colon-hex string"""
223
    pieces = []
224
    for i in range( bytecount - 1, -1, -1 ):
225
        piece = ( ( 0xff << ( i * 8 ) ) & val ) >> ( i * 8 )
226
        pieces.append( '%02x' % piece )
227
    chStr = ':'.join( pieces )
228
    return chStr
229

    
230
def macColonHex( mac ):
231
    """Generate MAC colon-hex string from unsigned int.
232
       mac: MAC address as unsigned int
233
       returns: macStr MAC colon-hex string"""
234
    return _colonHex( mac, 6 )
235

    
236
def ipStr( ip ):
237
    """Generate IP address string from an unsigned int.
238
       ip: unsigned int of form w << 24 | x << 16 | y << 8 | z
239
       returns: ip address string w.x.y.z, or 10.x.y.z if w==0"""
240
    w = ( ip >> 24 ) & 0xff
241
    w = 10 if w == 0 else w
242
    x = ( ip >> 16 ) & 0xff
243
    y = ( ip >> 8 ) & 0xff
244
    z = ip & 0xff
245
    return "%i.%i.%i.%i" % ( w, x, y, z )
246

    
247
def ipNum( w, x, y, z ):
248
    """Generate unsigned int from components of IP address
249
       returns: w << 24 | x << 16 | y << 8 | z"""
250
    return  ( w << 24 ) | ( x << 16 ) | ( y << 8 ) | z
251

    
252
def ipAdd( i, prefixLen=8, ipBaseNum=0x0a000000 ):
253
    """Return IP address string from ints
254
       i: int to be added to ipbase
255
       prefixLen: optional IP prefix length
256
       ipBaseNum: option base IP address as int
257
       returns IP address as string"""
258
    # Ugly but functional
259
    assert i < ( 1 << ( 32 - prefixLen ) )
260
    mask = 0xffffffff ^ ( ( 1 << prefixLen ) - 1 )
261
    ipnum = i + ( ipBaseNum & mask )
262
    return ipStr( ipnum )
263

    
264
def ipParse( ip ):
265
    "Parse an IP address and return an unsigned int."
266
    args = [ int( arg ) for arg in ip.split( '.' ) ]
267
    return ipNum( *args )
268

    
269
def netParse( ipstr ):
270
    """Parse an IP network specification, returning
271
       address and prefix len as unsigned ints"""
272
    prefixLen = 0
273
    if '/' in ipstr:
274
        ip, pf = ipstr.split( '/' )
275
        prefixLen = int( pf )
276
    return ipParse( ip ), prefixLen
277

    
278
def checkInt( s ):
279
    "Check if input string is an int"
280
    try:
281
        int( s )
282
        return True
283
    except ValueError:
284
        return False
285

    
286
def checkFloat( s ):
287
    "Check if input string is a float"
288
    try:
289
        float( s )
290
        return True
291
    except ValueError:
292
        return False
293

    
294
def makeNumeric( s ):
295
    "Convert string to int or float if numeric."
296
    if checkInt( s ):
297
        return int( s )
298
    elif checkFloat( s ):
299
        return float( s )
300
    else:
301
        return s
302

    
303

    
304
# Other stuff we use
305

    
306
def fixLimits():
307
    "Fix ridiculously small resource limits."
308
    setrlimit( RLIMIT_NPROC, ( 8192, 8192 ) )
309
    setrlimit( RLIMIT_NOFILE, ( 16384, 16384 ) )
310

    
311
def mountCgroups():
312
    "Make sure cgroups file system is mounted"
313
    mounts = quietRun( 'mount' )
314
    cgdir = '/sys/fs/cgroup'
315
    csdir = cgdir + '/cpuset'
316
    if 'cgroups on %s' % cgdir not in mounts:
317
        raise Exception( "cgroups not mounted on " + cgdir )
318
    if 'cpuset on %s' % csdir not in mounts:
319
        errRun( 'mkdir -p ' + csdir )
320
        errRun( 'mount -t cgroup -ocpuset cpuset ' + csdir )
321

    
322
def natural( text ):
323
    "To sort sanely/alphabetically: sorted( l, key=natural )"
324
    def num( s ):
325
        "Convert text segment to int if necessary"
326
        return int( s ) if s.isdigit() else s
327
    return [  num( s ) for s in re.split( r'(\d+)', text ) ]
328

    
329
def naturalSeq( t ):
330
    "Natural sort key function for sequences"
331
    return [ natural( x ) for x in t ]
332

    
333
def numCores():
334
    "Returns number of CPU cores based on /proc/cpuinfo"
335
    if hasattr( numCores, 'ncores' ):
336
        return numCores.ncores
337
    try:
338
        numCores.ncores = int( quietRun('grep -c processor /proc/cpuinfo') )
339
    except ValueError:
340
        return 0
341
    return numCores.ncores
342

    
343
def irange(start, end):
344
    """Inclusive range from start to end (vs. Python insanity.)
345
       irange(1,5) -> 1, 2, 3, 4, 5"""
346
    return range( start, end + 1 )
347

    
348
def custom( cls, **params ):
349
    "Returns customized constructor for class cls."
350
    def customized( *args, **kwargs):
351
        "Customized constructor"
352
        kwargs.update( params )
353
        return cls( *args, **kwargs )
354
    return customized