Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 5ac3cde2

History | View | Annotate | Download (50.3 KB)

1
"""
2
Node objects for Mininet.
3

4
Nodes provide a simple abstraction for interacting with hosts, switches
5
and controllers. Local nodes are simply one or more processes on the local
6
machine.
7

8
Node: superclass for all (primarily local) network nodes.
9

10
Host: a virtual host. By default, a host is simply a shell; commands
11
    may be sent using Cmd (which waits for output), or using sendCmd(),
12
    which returns immediately, allowing subsequent monitoring using
13
    monitor(). Examples of how to run experiments using this
14
    functionality are provided in the examples/ directory.
15

16
CPULimitedHost: a virtual host whose CPU bandwidth is limited by
17
    RT or CFS bandwidth limiting.
18

19
HostWithPrivateDirs: a virtual host that has user-specified private
20
    directories. These may be temporary directories stored as a tmpfs,
21
    or persistent directories that are mounted from another directory in
22
    the root filesystem.
23

24
Switch: superclass for switch nodes.
25

26
UserSwitch: a switch using the user-space switch from the OpenFlow
27
    reference implementation.
28

29
KernelSwitch: a switch using the kernel switch from the OpenFlow reference
30
    implementation.
31

32
OVSSwitch: a switch using the OpenVSwitch OpenFlow-compatible switch
33
    implementation (openvswitch.org).
34

35
Controller: superclass for OpenFlow controllers. The default controller
36
    is controller(8) from the reference implementation.
37

38
NOXController: a controller node using NOX (noxrepo.org).
39

40
RemoteController: a remote controller node, which may use any
41
    arbitrary OpenFlow-compatible controller, and which is not
42
    created or managed by mininet.
43

44
Future enhancements:
45

46
- Possibly make Node, Switch and Controller more abstract so that
47
  they can be used for both local and remote nodes
48

49
- Create proxy objects for remote nodes (Mininet: Cluster Edition)
50
"""
51

    
52
import os
53
import re
54
import signal
55
import select
56
from subprocess import Popen, PIPE, STDOUT
57
from operator import or_
58
from time import sleep
59

    
60
from mininet.log import info, error, warn, debug
61
from mininet.util import ( quietRun, errRun, errFail, moveIntf, isShellBuiltin,
62
                           numCores, retry, mountCgroups )
63
from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN
64
from mininet.link import Link, Intf, TCIntf
65
from re import findall
66
from distutils.version import StrictVersion
67

    
68
class Node( object ):
69
    """A virtual network node is simply a shell in a network namespace.
70
       We communicate with it using pipes."""
71

    
72
    portBase = 0  # Nodes always start with eth0/port0, even in OF 1.0
73

    
74
    def __init__( self, name, inNamespace=True, **params ):
75
        """name: name of node
76
           inNamespace: in network namespace?
77
           params: Node parameters (see config() for details)"""
78

    
79
        # Make sure class actually works
80
        self.checkSetup()
81

    
82
        self.name = name
83
        self.inNamespace = inNamespace
84

    
85
        # Stash configuration parameters for future reference
86
        self.params = params
87

    
88
        self.intfs = {}  # dict of port numbers to interfaces
89
        self.ports = {}  # dict of interfaces to port numbers
90
                         # replace with Port objects, eventually ?
91
        self.nameToIntf = {}  # dict of interface names to Intfs
92

    
93
        # Make pylint happy
94
        ( self.shell, self.execed, self.pid, self.stdin, self.stdout,
95
            self.lastPid, self.lastCmd, self.pollOut ) = (
96
                None, None, None, None, None, None, None, None )
97
        self.waiting = False
98
        self.readbuf = ''
99

    
100
        # Start command interpreter shell
101
        self.startShell()
102

    
103
    # File descriptor to node mapping support
104
    # Class variables and methods
105

    
106
    inToNode = {}  # mapping of input fds to nodes
107
    outToNode = {}  # mapping of output fds to nodes
108

    
109
    @classmethod
110
    def fdToNode( cls, fd ):
111
        """Return node corresponding to given file descriptor.
112
           fd: file descriptor
113
           returns: node"""
114
        node = cls.outToNode.get( fd )
115
        return node or cls.inToNode.get( fd )
116

    
117
    # Command support via shell process in namespace
118

    
119
    def startShell( self ):
120
        "Start a shell process for running commands"
121
        if self.shell:
122
            error( "%s: shell is already running" )
123
            return
124
        # mnexec: (c)lose descriptors, (d)etach from tty,
125
        # (p)rint pid, and run in (n)amespace
126
        opts = '-cdp'
127
        if self.inNamespace:
128
            opts += 'n'
129
        # bash -m: enable job control
130
        # -s: pass $* to shell, and make process easy to find in ps
131
        cmd = [ 'mnexec', opts, 'bash', '-ms', 'mininet:' + self.name ]
132
        self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT,
133
                            close_fds=True )
134
        self.stdin = self.shell.stdin
135
        self.stdout = self.shell.stdout
136
        self.pid = self.shell.pid
137
        self.pollOut = select.poll()
138
        self.pollOut.register( self.stdout )
139
        # Maintain mapping between file descriptors and nodes
140
        # This is useful for monitoring multiple nodes
141
        # using select.poll()
142
        self.outToNode[ self.stdout.fileno() ] = self
143
        self.inToNode[ self.stdin.fileno() ] = self
144
        self.execed = False
145
        self.lastCmd = None
146
        self.lastPid = None
147
        self.readbuf = ''
148
        self.waiting = False
149

    
150
    def cleanup( self ):
151
        "Help python collect its garbage."
152
        # Intfs may end up in root NS
153
        for intfName in self.intfNames():
154
            if self.name in intfName:
155
                quietRun( 'ip link del ' + intfName )
156
        self.shell = None
157

    
158
    # Subshell I/O, commands and control
159

    
160
    def read( self, maxbytes=1024 ):
161
        """Buffered read from node, non-blocking.
162
           maxbytes: maximum number of bytes to return"""
163
        count = len( self.readbuf )
164
        if count < maxbytes:
165
            data = os.read( self.stdout.fileno(), maxbytes - count )
166
            self.readbuf += data
167
        if maxbytes >= len( self.readbuf ):
168
            result = self.readbuf
169
            self.readbuf = ''
170
        else:
171
            result = self.readbuf[ :maxbytes ]
172
            self.readbuf = self.readbuf[ maxbytes: ]
173
        return result
174

    
175
    def readline( self ):
176
        """Buffered readline from node, non-blocking.
177
           returns: line (minus newline) or None"""
178
        self.readbuf += self.read( 1024 )
179
        if '\n' not in self.readbuf:
180
            return None
181
        pos = self.readbuf.find( '\n' )
182
        line = self.readbuf[ 0: pos ]
183
        self.readbuf = self.readbuf[ pos + 1: ]
184
        return line
185

    
186
    def write( self, data ):
187
        """Write data to node.
188
           data: string"""
189
        os.write( self.stdin.fileno(), data )
190

    
191
    def terminate( self ):
192
        "Send kill signal to Node and clean up after it."
193
        if self.shell:
194
            os.killpg( self.pid, signal.SIGKILL )
195
        self.cleanup()
196

    
197
    def stop( self ):
198
        "Stop node."
199
        self.terminate()
200

    
201
    def waitReadable( self, timeoutms=None ):
202
        """Wait until node's output is readable.
203
           timeoutms: timeout in ms or None to wait indefinitely."""
204
        if len( self.readbuf ) == 0:
205
            self.pollOut.poll( timeoutms )
206

    
207
    def sendCmd( self, *args, **kwargs ):
208
        """Send a command, followed by a command to echo a sentinel,
209
           and return without waiting for the command to complete.
210
           args: command and arguments, or string
211
           printPid: print command's PID?"""
212
        assert not self.waiting
213
        printPid = kwargs.get( 'printPid', True )
214
        # Allow sendCmd( [ list ] )
215
        if len( args ) == 1 and type( args[ 0 ] ) is list:
216
            cmd = args[ 0 ]
217
        # Allow sendCmd( cmd, arg1, arg2... )
218
        elif len( args ) > 0:
219
            cmd = args
220
        # Convert to string
221
        if not isinstance( cmd, str ):
222
            cmd = ' '.join( [ str( c ) for c in cmd ] )
223
        if not re.search( r'\w', cmd ):
224
            # Replace empty commands with something harmless
225
            cmd = 'echo -n'
226
        self.lastCmd = cmd
227
        printPid = printPid and not isShellBuiltin( cmd )
228
        if len( cmd ) > 0 and cmd[ -1 ] == '&':
229
            # print ^A{pid}\n{sentinel}
230
            cmd += ' printf "\\001%d\n\\177" $! \n'
231
        else:
232
            # print sentinel
233
            cmd += '; printf "\\177"'
234
            if printPid and not isShellBuiltin( cmd ):
235
                cmd = 'mnexec -p ' + cmd
236
        self.write( cmd + '\n' )
237
        self.lastPid = None
238
        self.waiting = True
239

    
240
    def sendInt( self, sig=signal.SIGINT ):
241
        "Interrupt running command."
242
        if self.lastPid:
243
            try:
244
                os.kill( self.lastPid, sig )
245
            except OSError:
246
                pass
247

    
248
    def monitor( self, timeoutms=None ):
249
        """Monitor and return the output of a command.
250
           Set self.waiting to False if command has completed.
251
           timeoutms: timeout in ms or None to wait indefinitely."""
252
        self.waitReadable( timeoutms )
253
        data = self.read( 1024 )
254
        # Look for PID
255
        marker = chr( 1 ) + r'\d+\n'
256
        if chr( 1 ) in data:
257
            markers = re.findall( marker, data )
258
            if markers:
259
                self.lastPid = int( markers[ 0 ][ 1: ] )
260
                data = re.sub( marker, '', data )
261
        # Look for sentinel/EOF
262
        if len( data ) > 0 and data[ -1 ] == chr( 127 ):
263
            self.waiting = False
264
            data = data[ :-1 ]
265
        elif chr( 127 ) in data:
266
            self.waiting = False
267
            data = data.replace( chr( 127 ), '' )
268
        return data
269

    
270
    def waitOutput( self, verbose=False ):
271
        """Wait for a command to complete.
272
           Completion is signaled by a sentinel character, ASCII(127)
273
           appearing in the output stream.  Wait for the sentinel and return
274
           the output, including trailing newline.
275
           verbose: print output interactively"""
276
        log = info if verbose else debug
277
        output = ''
278
        while self.waiting:
279
            data = self.monitor()
280
            output += data
281
            log( data )
282
        return output
283

    
284
    def cmd( self, *args, **kwargs ):
285
        """Send a command, wait for output, and return it.
286
           cmd: string"""
287
        verbose = kwargs.get( 'verbose', False )
288
        log = info if verbose else debug
289
        log( '*** %s : %s\n' % ( self.name, args ) )
290
        self.sendCmd( *args, **kwargs )
291
        return self.waitOutput( verbose )
292

    
293
    def cmdPrint( self, *args):
294
        """Call cmd and printing its output
295
           cmd: string"""
296
        return self.cmd( *args, **{ 'verbose': True } )
297

    
298
    def popen( self, *args, **kwargs ):
299
        """Return a Popen() object in our namespace
300
           args: Popen() args, single list, or string
301
           kwargs: Popen() keyword args"""
302
        defaults = { 'stdout': PIPE, 'stderr': PIPE,
303
                     'mncmd':
304
                     [ 'mnexec', '-da', str( self.pid ) ] }
305
        defaults.update( kwargs )
306
        if len( args ) == 1:
307
            if type( args[ 0 ] ) is list:
308
                # popen([cmd, arg1, arg2...])
309
                cmd = args[ 0 ]
310
            elif type( args[ 0 ] ) is str:
311
                # popen("cmd arg1 arg2...")
312
                cmd = args[ 0 ].split()
313
            else:
314
                raise Exception( 'popen() requires a string or list' )
315
        elif len( args ) > 0:
316
            # popen( cmd, arg1, arg2... )
317
            cmd = list( args )
318
        # Attach to our namespace  using mnexec -a
319
        mncmd = defaults[ 'mncmd' ]
320
        del defaults[ 'mncmd' ]
321
        cmd = mncmd + cmd
322
        # Shell requires a string, not a list!
323
        if defaults.get( 'shell', False ):
324
            cmd = ' '.join( cmd )
325
        return Popen( cmd, **defaults )
326

    
327
    def pexec( self, *args, **kwargs ):
328
        """Execute a command using popen
329
           returns: out, err, exitcode"""
330
        popen = self.popen( *args, **kwargs)
331
        out, err = popen.communicate()
332
        exitcode = popen.wait()
333
        return out, err, exitcode
334

    
335
    # Interface management, configuration, and routing
336

    
337
    # BL notes: This might be a bit redundant or over-complicated.
338
    # However, it does allow a bit of specialization, including
339
    # changing the canonical interface names. It's also tricky since
340
    # the real interfaces are created as veth pairs, so we can't
341
    # make a single interface at a time.
342

    
343
    def newPort( self ):
344
        "Return the next port number to allocate."
345
        if len( self.ports ) > 0:
346
            return max( self.ports.values() ) + 1
347
        return self.portBase
348

    
349
    def addIntf( self, intf, port=None ):
350
        """Add an interface.
351
           intf: interface
352
           port: port number (optional, typically OpenFlow port number)"""
353
        if port is None:
354
            port = self.newPort()
355
        self.intfs[ port ] = intf
356
        self.ports[ intf ] = port
357
        self.nameToIntf[ intf.name ] = intf
358
        debug( '\n' )
359
        debug( 'added intf %s:%d to node %s\n' % ( intf, port, self.name ) )
360
        if self.inNamespace:
361
            debug( 'moving', intf, 'into namespace for', self.name, '\n' )
362
            moveIntf( intf.name, self )
363

    
364
    def defaultIntf( self ):
365
        "Return interface for lowest port"
366
        ports = self.intfs.keys()
367
        if ports:
368
            return self.intfs[ min( ports ) ]
369
        else:
370
            warn( '*** defaultIntf: warning:', self.name,
371
                  'has no interfaces\n' )
372

    
373
    def intf( self, intf='' ):
374
        """Return our interface object with given string name,
375
           default intf if name is falsy (None, empty string, etc).
376
           or the input intf arg.
377

378
        Having this fcn return its arg for Intf objects makes it
379
        easier to construct functions with flexible input args for
380
        interfaces (those that accept both string names and Intf objects).
381
        """
382
        if not intf:
383
            return self.defaultIntf()
384
        elif type( intf ) is str:
385
            return self.nameToIntf[ intf ]
386
        else:
387
            return intf
388

    
389
    def connectionsTo( self, node):
390
        "Return [ intf1, intf2... ] for all intfs that connect self to node."
391
        # We could optimize this if it is important
392
        connections = []
393
        for intf in self.intfList():
394
            link = intf.link
395
            if link:
396
                node1, node2 = link.intf1.node, link.intf2.node
397
                if node1 == self and node2 == node:
398
                    connections += [ ( intf, link.intf2 ) ]
399
                elif node1 == node and node2 == self:
400
                    connections += [ ( intf, link.intf1 ) ]
401
        return connections
402

    
403
    def deleteIntfs( self, checkName=True ):
404
        """Delete all of our interfaces.
405
           checkName: only delete interfaces that contain our name"""
406
        # In theory the interfaces should go away after we shut down.
407
        # However, this takes time, so we're better off removing them
408
        # explicitly so that we won't get errors if we run before they
409
        # have been removed by the kernel. Unfortunately this is very slow,
410
        # at least with Linux kernels before 2.6.33
411
        for intf in self.intfs.values():
412
            # Protect against deleting hardware interfaces
413
            if ( self.name in intf.name ) or ( not checkName ):
414
                intf.delete()
415
                info( '.' )
416

    
417
    # Routing support
418

    
419
    def setARP( self, ip, mac ):
420
        """Add an ARP entry.
421
           ip: IP address as string
422
           mac: MAC address as string"""
423
        result = self.cmd( 'arp', '-s', ip, mac )
424
        return result
425

    
426
    def setHostRoute( self, ip, intf ):
427
        """Add route to host.
428
           ip: IP address as dotted decimal
429
           intf: string, interface name"""
430
        return self.cmd( 'route add -host', ip, 'dev', intf )
431

    
432
    def setDefaultRoute( self, intf=None ):
433
        """Set the default route to go through intf.
434
           intf: Intf or {dev <intfname> via <gw-ip> ...}"""
435
        # Note setParam won't call us if intf is none
436
        if type( intf ) is str and ' ' in intf:
437
            params = intf
438
        else:
439
            params = 'dev %s' % intf
440
        self.cmd( 'ip route del default' )
441
        return self.cmd( 'ip route add default', params )
442

    
443
    # Convenience and configuration methods
444

    
445
    def setMAC( self, mac, intf=None ):
446
        """Set the MAC address for an interface.
447
           intf: intf or intf name
448
           mac: MAC address as string"""
449
        return self.intf( intf ).setMAC( mac )
450

    
451
    def setIP( self, ip, prefixLen=8, intf=None ):
452
        """Set the IP address for an interface.
453
           intf: intf or intf name
454
           ip: IP address as a string
455
           prefixLen: prefix length, e.g. 8 for /8 or 16M addrs"""
456
        # This should probably be rethought
457
        if '/' not in ip:
458
            ip = '%s/%s' % ( ip, prefixLen )
459
        return self.intf( intf ).setIP( ip )
460

    
461
    def IP( self, intf=None ):
462
        "Return IP address of a node or specific interface."
463
        return self.intf( intf ).IP()
464

    
465
    def MAC( self, intf=None ):
466
        "Return MAC address of a node or specific interface."
467
        return self.intf( intf ).MAC()
468

    
469
    def intfIsUp( self, intf=None ):
470
        "Check if an interface is up."
471
        return self.intf( intf ).isUp()
472

    
473
    # The reason why we configure things in this way is so
474
    # That the parameters can be listed and documented in
475
    # the config method.
476
    # Dealing with subclasses and superclasses is slightly
477
    # annoying, but at least the information is there!
478

    
479
    def setParam( self, results, method, **param ):
480
        """Internal method: configure a *single* parameter
481
           results: dict of results to update
482
           method: config method name
483
           param: arg=value (ignore if value=None)
484
           value may also be list or dict"""
485
        name, value = param.items()[ 0 ]
486
        f = getattr( self, method, None )
487
        if not f or value is None:
488
            return
489
        if type( value ) is list:
490
            result = f( *value )
491
        elif type( value ) is dict:
492
            result = f( **value )
493
        else:
494
            result = f( value )
495
        results[ name ] = result
496
        return result
497

    
498
    def config( self, mac=None, ip=None,
499
                defaultRoute=None, lo='up', **_params ):
500
        """Configure Node according to (optional) parameters:
501
           mac: MAC address for default interface
502
           ip: IP address for default interface
503
           ifconfig: arbitrary interface configuration
504
           Subclasses should override this method and call
505
           the parent class's config(**params)"""
506
        # If we were overriding this method, we would call
507
        # the superclass config method here as follows:
508
        # r = Parent.config( **_params )
509
        r = {}
510
        self.setParam( r, 'setMAC', mac=mac )
511
        self.setParam( r, 'setIP', ip=ip )
512
        self.setParam( r, 'setDefaultRoute', defaultRoute=defaultRoute )
513
        # This should be examined
514
        self.cmd( 'ifconfig lo ' + lo )
515
        return r
516

    
517
    def configDefault( self, **moreParams ):
518
        "Configure with default parameters"
519
        self.params.update( moreParams )
520
        self.config( **self.params )
521

    
522
    # This is here for backward compatibility
523
    def linkTo( self, node, link=Link ):
524
        """(Deprecated) Link to another node
525
           replace with Link( node1, node2)"""
526
        return link( self, node )
527

    
528
    # Other methods
529

    
530
    def intfList( self ):
531
        "List of our interfaces sorted by port number"
532
        return [ self.intfs[ p ] for p in sorted( self.intfs.iterkeys() ) ]
533

    
534
    def intfNames( self ):
535
        "The names of our interfaces sorted by port number"
536
        return [ str( i ) for i in self.intfList() ]
537

    
538
    def __repr__( self ):
539
        "More informative string representation"
540
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
541
                              for i in self.intfList() ] ) )
542
        return '<%s %s: %s pid=%s> ' % (
543
            self.__class__.__name__, self.name, intfs, self.pid )
544

    
545
    def __str__( self ):
546
        "Abbreviated string representation"
547
        return self.name
548

    
549
    # Automatic class setup support
550

    
551
    isSetup = False
552

    
553
    @classmethod
554
    def checkSetup( cls ):
555
        "Make sure our class and superclasses are set up"
556
        while cls and not getattr( cls, 'isSetup', True ):
557
            cls.setup()
558
            cls.isSetup = True
559
            # Make pylint happy
560
            cls = getattr( type( cls ), '__base__', None )
561

    
562
    @classmethod
563
    def setup( cls ):
564
        "Make sure our class dependencies are available"
565
        pathCheck( 'mnexec', 'ifconfig', moduleName='Mininet')
566

    
567

    
568
class Host( Node ):
569
    "A host is simply a Node"
570
    pass
571

    
572

    
573
class CPULimitedHost( Host ):
574

    
575
    "CPU limited host"
576

    
577
    def __init__( self, name, sched='cfs', **kwargs ):
578
        Host.__init__( self, name, **kwargs )
579
        # Initialize class if necessary
580
        if not CPULimitedHost.inited:
581
            CPULimitedHost.init()
582
        # Create a cgroup and move shell into it
583
        self.cgroup = 'cpu,cpuacct,cpuset:/' + self.name
584
        errFail( 'cgcreate -g ' + self.cgroup )
585
        # We don't add ourselves to a cpuset because you must
586
        # specify the cpu and memory placement first
587
        errFail( 'cgclassify -g cpu,cpuacct:/%s %s' % ( self.name, self.pid ) )
588
        # BL: Setting the correct period/quota is tricky, particularly
589
        # for RT. RT allows very small quotas, but the overhead
590
        # seems to be high. CFS has a mininimum quota of 1 ms, but
591
        # still does better with larger period values.
592
        self.period_us = kwargs.get( 'period_us', 100000 )
593
        self.sched = sched
594
        self.rtprio = 20
595

    
596
    def cgroupSet( self, param, value, resource='cpu' ):
597
        "Set a cgroup parameter and return its value"
598
        cmd = 'cgset -r %s.%s=%s /%s' % (
599
            resource, param, value, self.name )
600
        quietRun( cmd )
601
        nvalue = int( self.cgroupGet( param, resource ) )
602
        if nvalue != value:
603
            error( '*** error: cgroupSet: %s set to %s instead of %s\n'
604
                   % ( param, nvalue, value ) )
605
        return nvalue
606

    
607
    def cgroupGet( self, param, resource='cpu' ):
608
        "Return value of cgroup parameter"
609
        cmd = 'cgget -r %s.%s /%s' % (
610
            resource, param, self.name )
611
        return int( quietRun( cmd ).split()[ -1 ] )
612

    
613
    def cgroupDel( self ):
614
        "Clean up our cgroup"
615
        # info( '*** deleting cgroup', self.cgroup, '\n' )
616
        _out, _err, exitcode = errRun( 'cgdelete -r ' + self.cgroup )
617
        return exitcode != 0
618

    
619
    def popen( self, *args, **kwargs ):
620
        """Return a Popen() object in node's namespace
621
           args: Popen() args, single list, or string
622
           kwargs: Popen() keyword args"""
623
        # Tell mnexec to execute command in our cgroup
624
        mncmd = [ 'mnexec', '-da', str( self.pid ),
625
                  '-g', self.name ]
626
        if self.sched == 'rt':
627
            mncmd += [ '-r', str( self.rtprio ) ]
628
        return Host.popen( self, *args, mncmd=mncmd, **kwargs )
629

    
630
    def cleanup( self ):
631
        "Clean up Node, then clean up our cgroup"
632
        super( CPULimitedHost, self ).cleanup()
633
        retry( retries=3, delaySecs=1, fn=self.cgroupDel )
634

    
635
    def chrt( self ):
636
        "Set RT scheduling priority"
637
        quietRun( 'chrt -p %s %s' % ( self.rtprio, self.pid ) )
638
        result = quietRun( 'chrt -p %s' % self.pid )
639
        firstline = result.split( '\n' )[ 0 ]
640
        lastword = firstline.split( ' ' )[ -1 ]
641
        if lastword != 'SCHED_RR':
642
            error( '*** error: could not assign SCHED_RR to %s\n' % self.name )
643
        return lastword
644

    
645
    def rtInfo( self, f ):
646
        "Internal method: return parameters for RT bandwidth"
647
        pstr, qstr = 'rt_period_us', 'rt_runtime_us'
648
        # RT uses wall clock time for period and quota
649
        quota = int( self.period_us * f * numCores() )
650
        return pstr, qstr, self.period_us, quota
651

    
652
    def cfsInfo( self, f):
653
        "Internal method: return parameters for CFS bandwidth"
654
        pstr, qstr = 'cfs_period_us', 'cfs_quota_us'
655
        # CFS uses wall clock time for period and CPU time for quota.
656
        quota = int( self.period_us * f * numCores() )
657
        period = self.period_us
658
        if f > 0 and quota < 1000:
659
            debug( '(cfsInfo: increasing default period) ' )
660
            quota = 1000
661
            period = int( quota / f / numCores() )
662
        return pstr, qstr, period, quota
663

    
664
    # BL comment:
665
    # This may not be the right API,
666
    # since it doesn't specify CPU bandwidth in "absolute"
667
    # units the way link bandwidth is specified.
668
    # We should use MIPS or SPECINT or something instead.
669
    # Alternatively, we should change from system fraction
670
    # to CPU seconds per second, essentially assuming that
671
    # all CPUs are the same.
672

    
673
    def setCPUFrac( self, f=-1, sched=None):
674
        """Set overall CPU fraction for this host
675
           f: CPU bandwidth limit (fraction)
676
           sched: 'rt' or 'cfs'
677
           Note 'cfs' requires CONFIG_CFS_BANDWIDTH"""
678
        if not f:
679
            return
680
        if not sched:
681
            sched = self.sched
682
        if sched == 'rt':
683
            pstr, qstr, period, quota = self.rtInfo( f )
684
        elif sched == 'cfs':
685
            pstr, qstr, period, quota = self.cfsInfo( f )
686
        else:
687
            return
688
        if quota < 0:
689
            # Reset to unlimited
690
            quota = -1
691
        # Set cgroup's period and quota
692
        self.cgroupSet( pstr, period )
693
        self.cgroupSet( qstr, quota )
694
        if sched == 'rt':
695
            # Set RT priority if necessary
696
            self.chrt()
697
        info( '(%s %d/%dus) ' % ( sched, quota, period ) )
698

    
699
    def setCPUs( self, cores, mems=0 ):
700
        "Specify (real) cores that our cgroup can run on"
701
        if type( cores ) is list:
702
            cores = ','.join( [ str( c ) for c in cores ] )
703
        self.cgroupSet( resource='cpuset', param='cpus',
704
                        value=cores )
705
        # Memory placement is probably not relevant, but we
706
        # must specify it anyway
707
        self.cgroupSet( resource='cpuset', param='mems',
708
                        value=mems)
709
        # We have to do this here after we've specified
710
        # cpus and mems
711
        errFail( 'cgclassify -g cpuset:/%s %s' % (
712
                 self.name, self.pid ) )
713

    
714
    def config( self, cpu=None, cores=None, **params ):
715
        """cpu: desired overall system CPU fraction
716
           cores: (real) core(s) this host can run on
717
           params: parameters for Node.config()"""
718
        r = Node.config( self, **params )
719
        # Was considering cpu={'cpu': cpu , 'sched': sched}, but
720
        # that seems redundant
721
        self.setParam( r, 'setCPUFrac', cpu=cpu )
722
        self.setParam( r, 'setCPUs', cores=cores )
723
        return r
724

    
725
    inited = False
726

    
727
    @classmethod
728
    def init( cls ):
729
        "Initialization for CPULimitedHost class"
730
        mountCgroups()
731
        cls.inited = True
732

    
733
class HostWithPrivateDirs( Host ):
734
    "Host with private directories"
735

    
736
    def __init__( self, name, *args, **kwargs ):
737
        "privateDirs: list of private directory strings or tuples"
738
        self.name = name
739
        self.privateDirs = kwargs.pop( 'privateDirs', [] )
740
        Host.__init__( self, name, *args, **kwargs )
741
        self.mountPrivateDirs()
742

    
743
    def mountPrivateDirs( self ):
744
        "mount private directories"
745
        for directory in self.privateDirs:
746
            if isinstance( directory, tuple ):
747
                # mount given private directory
748
                privateDir = directory[ 1 ] % self.__dict__ 
749
                mountPoint = directory[ 0 ]
750
                self.cmd( 'mkdir -p %s' % privateDir )
751
                self.cmd( 'mkdir -p %s' % mountPoint )
752
                self.cmd( 'mount --bind %s %s' %
753
                               ( privateDir, mountPoint ) )
754
            else:
755
                # mount temporary filesystem on directory
756
                self.cmd( 'mkdir -p %s' % directory ) 
757
                self.cmd( 'mount -n -t tmpfs tmpfs %s' % directory )
758

    
759

    
760

    
761
# Some important things to note:
762
#
763
# The "IP" address which setIP() assigns to the switch is not
764
# an "IP address for the switch" in the sense of IP routing.
765
# Rather, it is the IP address for the control interface,
766
# on the control network, and it is only relevant to the
767
# controller. If you are running in the root namespace
768
# (which is the only way to run OVS at the moment), the
769
# control interface is the loopback interface, and you
770
# normally never want to change its IP address!
771
#
772
# In general, you NEVER want to attempt to use Linux's
773
# network stack (i.e. ifconfig) to "assign" an IP address or
774
# MAC address to a switch data port. Instead, you "assign"
775
# the IP and MAC addresses in the controller by specifying
776
# packets that you want to receive or send. The "MAC" address
777
# reported by ifconfig for a switch data port is essentially
778
# meaningless. It is important to understand this if you
779
# want to create a functional router using OpenFlow.
780

    
781
class Switch( Node ):
782
    """A Switch is a Node that is running (or has execed?)
783
       an OpenFlow switch."""
784

    
785
    portBase = 1  # Switches start with port 1 in OpenFlow
786
    dpidLen = 16  # digits in dpid passed to switch
787

    
788
    def __init__( self, name, dpid=None, opts='', listenPort=None, **params):
789
        """dpid: dpid hex string (or None to derive from name, e.g. s1 -> 1)
790
           opts: additional switch options
791
           listenPort: port to listen on for dpctl connections"""
792
        Node.__init__( self, name, **params )
793
        self.dpid = self.defaultDpid( dpid )
794
        self.opts = opts
795
        self.listenPort = listenPort
796
        if not self.inNamespace:
797
            self.controlIntf = Intf( 'lo', self, port=0 )
798

    
799
    def defaultDpid( self, dpid=None ):
800
        "Return correctly formatted dpid from dpid or switch name (s1 -> 1)"
801
        if dpid:
802
            # Remove any colons and make sure it's a good hex number
803
            dpid = dpid.translate( None, ':' )
804
            assert len( dpid ) <= self.dpidLen and int( dpid, 16 ) >= 0
805
        else:
806
            # Use hex of the first number in the switch name
807
            nums = re.findall( r'\d+', self.name )
808
            if nums:
809
                dpid = hex( int( nums[ 0 ] ) )[ 2: ]
810
            else:
811
                raise Exception( 'Unable to derive default datapath ID - '
812
                                 'please either specify a dpid or use a '
813
                                 'canonical switch name such as s23.' )
814
        return '0' * ( self.dpidLen - len( dpid ) ) + dpid
815

    
816
    def defaultIntf( self ):
817
        "Return control interface"
818
        if self.controlIntf:
819
            return self.controlIntf
820
        else:
821
            return Node.defaultIntf( self )
822

    
823
    def sendCmd( self, *cmd, **kwargs ):
824
        """Send command to Node.
825
           cmd: string"""
826
        kwargs.setdefault( 'printPid', False )
827
        if not self.execed:
828
            return Node.sendCmd( self, *cmd, **kwargs )
829
        else:
830
            error( '*** Error: %s has execed and cannot accept commands' %
831
                   self.name )
832

    
833
    def connected( self ):
834
        "Is the switch connected to a controller? (override this method)"
835
        return False and self  # satisfy pylint
836

    
837
    def __repr__( self ):
838
        "More informative string representation"
839
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
840
                              for i in self.intfList() ] ) )
841
        return '<%s %s: %s pid=%s> ' % (
842
            self.__class__.__name__, self.name, intfs, self.pid )
843

    
844
class UserSwitch( Switch ):
845
    "User-space switch."
846

    
847
    dpidLen = 12
848

    
849
    def __init__( self, name, dpopts='--no-slicing', **kwargs ):
850
        """Init.
851
           name: name for the switch
852
           dpopts: additional arguments to ofdatapath (--no-slicing)"""
853
        Switch.__init__( self, name, **kwargs )
854
        pathCheck( 'ofdatapath', 'ofprotocol',
855
                   moduleName='the OpenFlow reference user switch' +
856
                              '(openflow.org)' )
857
        if self.listenPort:
858
            self.opts += ' --listen=ptcp:%i ' % self.listenPort
859
        else:
860
            self.opts += ' --listen=punix:/tmp/%s.listen' % self.name
861
        self.dpopts = dpopts
862

    
863
    @classmethod
864
    def setup( cls ):
865
        "Ensure any dependencies are loaded; if not, try to load them."
866
        if not os.path.exists( '/dev/net/tun' ):
867
            moduleDeps( add=TUN )
868

    
869
    def dpctl( self, *args ):
870
        "Run dpctl command"
871
        listenAddr = None
872
        if not self.listenPort:
873
            listenAddr = 'unix:/tmp/%s.listen' % self.name
874
        else:
875
            listenAddr = 'tcp:127.0.0.1:%i' % self.listenPort
876
        return self.cmd( 'dpctl ' + ' '.join( args ) +
877
                         ' ' + listenAddr )
878

    
879
    def connected( self ):
880
        "Is the switch connected to a controller?"
881
        return 'remote.is-connected=true' in self.dpctl( 'status' )
882

    
883
    @staticmethod
884
    def TCReapply( intf ):
885
        """Unfortunately user switch and Mininet are fighting
886
           over tc queuing disciplines. To resolve the conflict,
887
           we re-create the user switch's configuration, but as a
888
           leaf of the TCIntf-created configuration."""
889
        if type( intf ) is TCIntf:
890
            ifspeed = 10000000000 # 10 Gbps
891
            minspeed = ifspeed * 0.001
892

    
893
            res = intf.config( **intf.params )
894

    
895
            if res is None: # link may not have TC parameters
896
                return
897

    
898
            # Re-add qdisc, root, and default classes user switch created, but
899
            # with new parent, as setup by Mininet's TCIntf
900
            parent = res['parent']
901
            intf.tc( "%s qdisc add dev %s " + parent +
902
                     " handle 1: htb default 0xfffe" )
903
            intf.tc( "%s class add dev %s classid 1:0xffff parent 1: htb rate "
904
                     + str(ifspeed) )
905
            intf.tc( "%s class add dev %s classid 1:0xfffe parent 1:0xffff " +
906
                     "htb rate " + str(minspeed) + " ceil " + str(ifspeed) )
907

    
908
    def start( self, controllers ):
909
        """Start OpenFlow reference user datapath.
910
           Log to /tmp/sN-{ofd,ofp}.log.
911
           controllers: list of controller objects"""
912
        # Add controllers
913
        clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
914
                            for c in controllers ] )
915
        ofdlog = '/tmp/' + self.name + '-ofd.log'
916
        ofplog = '/tmp/' + self.name + '-ofp.log'
917
        self.cmd( 'ifconfig lo up' )
918
        intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
919
        self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
920
                  ' punix:/tmp/' + self.name + ' -d %s ' % self.dpid +
921
                  self.dpopts +
922
                  ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
923
        self.cmd( 'ofprotocol unix:/tmp/' + self.name +
924
                  ' ' + clist +
925
                  ' --fail=closed ' + self.opts +
926
                  ' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
927
        if "no-slicing" not in self.dpopts:
928
            # Only TCReapply if slicing is enable
929
            sleep(1) # Allow ofdatapath to start before re-arranging qdisc's
930
            for intf in self.intfList():
931
                if not intf.IP():
932
                    self.TCReapply( intf )
933

    
934
    def stop( self ):
935
        "Stop OpenFlow reference user datapath."
936
        self.cmd( 'kill %ofdatapath' )
937
        self.cmd( 'kill %ofprotocol' )
938
        self.deleteIntfs()
939

    
940

    
941
class OVSLegacyKernelSwitch( Switch ):
942
    """Open VSwitch legacy kernel-space switch using ovs-openflowd.
943
       Currently only works in the root namespace."""
944

    
945
    def __init__( self, name, dp=None, **kwargs ):
946
        """Init.
947
           name: name for switch
948
           dp: netlink id (0, 1, 2, ...)
949
           defaultMAC: default MAC as unsigned int; random value if None"""
950
        Switch.__init__( self, name, **kwargs )
951
        self.dp = dp if dp else self.name
952
        self.intf = self.dp
953
        if self.inNamespace:
954
            error( "OVSKernelSwitch currently only works"
955
                   " in the root namespace.\n" )
956
            exit( 1 )
957

    
958
    @classmethod
959
    def setup( cls ):
960
        "Ensure any dependencies are loaded; if not, try to load them."
961
        pathCheck( 'ovs-dpctl', 'ovs-openflowd',
962
                   moduleName='Open vSwitch (openvswitch.org)')
963
        moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
964

    
965
    def start( self, controllers ):
966
        "Start up kernel datapath."
967
        ofplog = '/tmp/' + self.name + '-ofp.log'
968
        quietRun( 'ifconfig lo up' )
969
        # Delete local datapath if it exists;
970
        # then create a new one monitoring the given interfaces
971
        self.cmd( 'ovs-dpctl del-dp ' + self.dp )
972
        self.cmd( 'ovs-dpctl add-dp ' + self.dp )
973
        intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
974
        self.cmd( 'ovs-dpctl', 'add-if', self.dp, ' '.join( intfs ) )
975
        # Run protocol daemon
976
        clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
977
                            for c in controllers ] )
978
        self.cmd( 'ovs-openflowd ' + self.dp +
979
                  ' ' + clist +
980
                  ' --fail=secure ' + self.opts +
981
                  ' --datapath-id=' + self.dpid +
982
                  ' 1>' + ofplog + ' 2>' + ofplog + '&' )
983
        self.execed = False
984

    
985
    def stop( self ):
986
        "Terminate kernel datapath."
987
        quietRun( 'ovs-dpctl del-dp ' + self.dp )
988
        self.cmd( 'kill %ovs-openflowd' )
989
        self.deleteIntfs()
990

    
991

    
992
class OVSSwitch( Switch ):
993
    "Open vSwitch switch. Depends on ovs-vsctl."
994

    
995
    def __init__( self, name, failMode='secure', datapath='kernel',
996
                 inband=False, **params ):
997
        """Init.
998
           name: name for switch
999
           failMode: controller loss behavior (secure|open)
1000
           datapath: userspace or kernel mode (kernel|user)
1001
           inband: use in-band control (False)"""
1002
        Switch.__init__( self, name, **params )
1003
        self.failMode = failMode
1004
        self.datapath = datapath
1005
        self.inband = inband
1006

    
1007
    @classmethod
1008
    def setup( cls ):
1009
        "Make sure Open vSwitch is installed and working"
1010
        pathCheck( 'ovs-vsctl',
1011
                   moduleName='Open vSwitch (openvswitch.org)')
1012
        # This should no longer be needed, and it breaks
1013
        # with OVS 1.7 which has renamed the kernel module:
1014
        #  moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
1015
        out, err, exitcode = errRun( 'ovs-vsctl -t 1 show' )
1016
        if exitcode:
1017
            error( out + err +
1018
                   'ovs-vsctl exited with code %d\n' % exitcode +
1019
                   '*** Error connecting to ovs-db with ovs-vsctl\n'
1020
                   'Make sure that Open vSwitch is installed, '
1021
                   'that ovsdb-server is running, and that\n'
1022
                   '"ovs-vsctl show" works correctly.\n'
1023
                   'You may wish to try '
1024
                   '"service openvswitch-switch start".\n' )
1025
            exit( 1 )
1026
        info = quietRun( 'ovs-vsctl --version' )
1027
        cls.OVSVersion =  findall( '\d+\.\d+', info )[ 0 ]
1028

    
1029
    @classmethod
1030
    def isOldOVS( cls ):
1031
        return ( StrictVersion( cls.OVSVersion ) <
1032
             StrictVersion( '1.10' ) )
1033

    
1034
    @classmethod
1035
    def batchShutdown( cls, switches ):
1036
        "Call ovs-vsctl del-br on all OVSSwitches in a list"
1037
        quietRun( 'ovs-vsctl ' +
1038
                  ' -- '.join( '--if-exists del-br %s' % s
1039
                               for s in switches ) )
1040

    
1041
    def dpctl( self, *args ):
1042
        "Run ovs-ofctl command"
1043
        return self.cmd( 'ovs-ofctl', args[ 0 ], self, *args[ 1: ] )
1044

    
1045
    @staticmethod
1046
    def TCReapply( intf ):
1047
        """Unfortunately OVS and Mininet are fighting
1048
           over tc queuing disciplines. As a quick hack/
1049
           workaround, we clear OVS's and reapply our own."""
1050
        if type( intf ) is TCIntf:
1051
            intf.config( **intf.params )
1052

    
1053
    def attach( self, intf ):
1054
        "Connect a data port"
1055
        self.cmd( 'ovs-vsctl add-port', self, intf )
1056
        self.cmd( 'ifconfig', intf, 'up' )
1057
        self.TCReapply( intf )
1058

    
1059
    def detach( self, intf ):
1060
        "Disconnect a data port"
1061
        self.cmd( 'ovs-vsctl del-port', self, intf )
1062

    
1063
    def controllerUUIDs( self ):
1064
        "Return ovsdb UUIDs for our controllers"
1065
        uuids = []
1066
        controllers = self.cmd( 'ovs-vsctl -- get Bridge', self,
1067
                               'Controller' ).strip()
1068
        if controllers.startswith( '[' ) and controllers.endswith( ']' ):
1069
            controllers = controllers[ 1 : -1 ]
1070
            uuids = [ c.strip() for c in controllers.split( ',' ) ]
1071
        return uuids
1072

    
1073
    def connected( self ):
1074
        "Are we connected to at least one of our controllers?"
1075
        results = [ 'true' in self.cmd( 'ovs-vsctl -- get Controller',
1076
                                         uuid, 'is_connected' )
1077
                    for uuid in self.controllerUUIDs() ]
1078
        return reduce( or_, results, False )
1079

    
1080
    def start( self, controllers ):
1081
        "Start up a new OVS OpenFlow switch using ovs-vsctl"
1082
        if self.inNamespace:
1083
            raise Exception(
1084
                'OVS kernel switch does not work in a namespace' )
1085
        # We should probably call config instead, but this
1086
        # requires some rethinking...
1087
        self.cmd( 'ifconfig lo up' )
1088
        # Annoyingly, --if-exists option seems not to work
1089
        self.cmd( 'ovs-vsctl del-br', self )
1090
        int( self.dpid, 16 ) # DPID must be a hex string
1091
        # Interfaces and controllers
1092
        intfs = ' '.join( '-- add-port %s %s ' % ( self, intf ) +
1093
                          '-- set Interface %s ' % intf +
1094
                          'ofport_request=%s ' % self.ports[ intf ]
1095
                         for intf in self.intfList() if not intf.IP() )
1096
        clist = ' '.join( '%s:%s:%d' % ( c.protocol, c.IP(), c.port )
1097
                         for c in controllers )
1098
        if self.listenPort:
1099
            clist += ' ptcp:%s' % self.listenPort
1100
        # Construct big ovs-vsctl command for new versions of OVS
1101
        if not self.isOldOVS():
1102
            cmd = ( 'ovs-vsctl add-br %s ' % self +
1103
                    '-- set Bridge %s ' % self +
1104
                    'other_config:datapath-id=%s ' % self.dpid +
1105
                    '-- set-fail-mode %s %s ' % ( self, self.failMode ) +
1106
                    intfs +
1107
                    '-- set-controller %s %s ' % ( self, clist ) )
1108
        # Construct ovs-vsctl commands for old versions of OVS
1109
        else:
1110
            self.cmd( 'ovs-vsctl add-br', self )
1111
            for intf in self.intfList():
1112
                if not intf.IP():
1113
                    self.cmd( 'ovs-vsctl add-port', self, intf )
1114
            cmd = ( 'ovs-vsctl set Bridge %s ' % self +
1115
                    'other_config:datapath-id=%s ' % self.dpid +
1116
                    '-- set-fail-mode %s %s ' % ( self, self.failMode ) +
1117
                    '-- set-controller %s %s ' % ( self, clist ) )
1118
        if not self.inband:
1119
            cmd += ( '-- set bridge %s '
1120
                     'other-config:disable-in-band=true ' % self )
1121
        if self.datapath == 'user':
1122
            cmd += '-- set bridge %s datapath_type=netdev ' % self
1123
        # Reconnect quickly to controllers (1s vs. 15s max_backoff)
1124
        for uuid in self.controllerUUIDs():
1125
            if uuid.count( '-' ) != 4:
1126
                # Doesn't look like a UUID
1127
                continue
1128
            uuid = uuid.strip()
1129
            cmd += '-- set Controller %smax_backoff=1000 ' % uuid
1130
        # Do it!!
1131
        self.cmd( cmd )
1132
        for intf in self.intfList():
1133
            self.TCReapply( intf )
1134

    
1135

    
1136
    def stop( self ):
1137
        "Terminate OVS switch."
1138
        self.cmd( 'ovs-vsctl del-br', self )
1139
        if self.datapath == 'user':
1140
            self.cmd( 'ip link del', self )
1141
        self.deleteIntfs()
1142

    
1143
OVSKernelSwitch = OVSSwitch
1144

    
1145

    
1146
class IVSSwitch(Switch):
1147
    """IVS virtual switch"""
1148

    
1149
    def __init__( self, name, verbose=True, **kwargs ):
1150
        Switch.__init__( self, name, **kwargs )
1151
        self.verbose = verbose
1152

    
1153
    @classmethod
1154
    def setup( cls ):
1155
        "Make sure IVS is installed"
1156
        pathCheck( 'ivs-ctl', 'ivs',
1157
                   moduleName="Indigo Virtual Switch (projectfloodlight.org)" )
1158
        out, err, exitcode = errRun( 'ivs-ctl show' )
1159
        if exitcode:
1160
            error( out + err +
1161
                   'ivs-ctl exited with code %d\n' % exitcode +
1162
                   '*** The openvswitch kernel module might '
1163
                   'not be loaded. Try modprobe openvswitch.\n' )
1164
            exit( 1 )
1165

    
1166
    @classmethod
1167
    def batchShutdown( cls, switches ):
1168
        "Kill each IVS switch, to be waited on later in stop()"
1169
        for switch in switches:
1170
            switch.cmd( 'kill %ivs' )
1171

    
1172
    def start( self, controllers ):
1173
        "Start up a new IVS switch"
1174
        args = ['ivs']
1175
        args.extend( ['--name', self.name] )
1176
        args.extend( ['--dpid', self.dpid] )
1177
        if self.verbose:
1178
            args.extend( ['--verbose'] )
1179
        for intf in self.intfs.values():
1180
            if not intf.IP():
1181
                args.extend( ['-i', intf.name] )
1182
        for c in controllers:
1183
            args.extend( ['-c', '%s:%d' % (c.IP(), c.port)] )
1184
        if self.listenPort:
1185
            args.extend( ['--listen', '127.0.0.1:%i' % self.listenPort] )
1186
        args.append( self.opts )
1187

    
1188
        logfile = '/tmp/ivs.%s.log' % self.name
1189

    
1190
        self.cmd( 'ifconfig lo up' )
1191
        self.cmd( ' '.join(args) + ' >' + logfile + ' 2>&1 </dev/null &' )
1192

    
1193
    def stop( self ):
1194
        "Terminate IVS switch."
1195
        self.cmd( 'kill %ivs' )
1196
        self.cmd( 'wait' )
1197
        self.deleteIntfs()
1198

    
1199
    def attach( self, intf ):
1200
        "Connect a data port"
1201
        self.cmd( 'ivs-ctl', 'add-port', '--datapath', self.name, intf )
1202

    
1203
    def detach( self, intf ):
1204
        "Disconnect a data port"
1205
        self.cmd( 'ivs-ctl', 'del-port', '--datapath', self.name, intf )
1206

    
1207
    def dpctl( self, *args ):
1208
        "Run dpctl command"
1209
        if not self.listenPort:
1210
            return "can't run dpctl without passive listening port"
1211
        return self.cmd( 'ovs-ofctl ' + ' '.join( args ) +
1212
                         ' tcp:127.0.0.1:%i' % self.listenPort )
1213

    
1214

    
1215
class Controller( Node ):
1216
    """A Controller is a Node that is running (or has execed?) an
1217
       OpenFlow controller."""
1218

    
1219
    def __init__( self, name, inNamespace=False, command='controller',
1220
                  cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
1221
                  port=6633, protocol='tcp', **params ):
1222
        self.command = command
1223
        self.cargs = cargs
1224
        self.cdir = cdir
1225
        self.ip = ip
1226
        self.port = port
1227
        self.protocol = protocol
1228
        Node.__init__( self, name, inNamespace=inNamespace,
1229
                       ip=ip, **params  )
1230
        self.cmd( 'ifconfig lo up' )  # Shouldn't be necessary
1231
        self.checkListening()
1232

    
1233
    def checkListening( self ):
1234
        "Make sure no controllers are running on our port"
1235
        # Verify that Telnet is installed first:
1236
        out, _err, returnCode = errRun( "which telnet" )
1237
        if 'telnet' not in out or returnCode != 0:
1238
            raise Exception( "Error running telnet to check for listening "
1239
                             "controllers; please check that it is "
1240
                             "installed." )
1241
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1242
                              ( self.ip, self.port ) )
1243
        if 'Connected' in listening:
1244
            servers = self.cmd( 'netstat -natp' ).split( '\n' )
1245
            pstr = ':%d ' % self.port
1246
            clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ]
1247
            raise Exception( "Please shut down the controller which is"
1248
                             " running on port %d:\n" % self.port +
1249
                             '\n'.join( clist ) )
1250

    
1251
    def start( self ):
1252
        """Start <controller> <args> on controller.
1253
           Log to /tmp/cN.log"""
1254
        pathCheck( self.command )
1255
        cout = '/tmp/' + self.name + '.log'
1256
        if self.cdir is not None:
1257
            self.cmd( 'cd ' + self.cdir )
1258
        self.cmd( self.command + ' ' + self.cargs % self.port +
1259
                  ' 1>' + cout + ' 2>' + cout + '&' )
1260
        self.execed = False
1261

    
1262
    def stop( self ):
1263
        "Stop controller."
1264
        self.cmd( 'kill %' + self.command )
1265
        self.terminate()
1266

    
1267
    def IP( self, intf=None ):
1268
        "Return IP address of the Controller"
1269
        if self.intfs:
1270
            ip = Node.IP( self, intf )
1271
        else:
1272
            ip = self.ip
1273
        return ip
1274

    
1275
    def __repr__( self ):
1276
        "More informative string representation"
1277
        return '<%s %s: %s:%s pid=%s> ' % (
1278
            self.__class__.__name__, self.name,
1279
            self.IP(), self.port, self.pid )
1280
    @classmethod
1281
    def isAvailable( self ):
1282
        return quietRun( 'which controller' )
1283

    
1284
class OVSController( Controller ):
1285
    "Open vSwitch controller"
1286
    def __init__( self, name, command='ovs-controller', **kwargs ):
1287
        if quietRun( 'which test-controller' ):
1288
            command = 'test-controller'
1289
        Controller.__init__( self, name, command=command, **kwargs )
1290
    @classmethod
1291
    def isAvailable( self ):
1292
        return quietRun( 'which ovs-controller' ) or quietRun( 'which test-controller' )
1293

    
1294
class NOX( Controller ):
1295
    "Controller to run a NOX application."
1296

    
1297
    def __init__( self, name, *noxArgs, **kwargs ):
1298
        """Init.
1299
           name: name to give controller
1300
           noxArgs: arguments (strings) to pass to NOX"""
1301
        if not noxArgs:
1302
            warn( 'warning: no NOX modules specified; '
1303
                  'running packetdump only\n' )
1304
            noxArgs = [ 'packetdump' ]
1305
        elif type( noxArgs ) not in ( list, tuple ):
1306
            noxArgs = [ noxArgs ]
1307

    
1308
        if 'NOX_CORE_DIR' not in os.environ:
1309
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
1310
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
1311

    
1312
        Controller.__init__( self, name,
1313
                             command=noxCoreDir + '/nox_core',
1314
                             cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
1315
                             ' '.join( noxArgs ),
1316
                             cdir=noxCoreDir,
1317
                             **kwargs )
1318

    
1319

    
1320
class RemoteController( Controller ):
1321
    "Controller running outside of Mininet's control."
1322

    
1323
    def __init__( self, name, ip='127.0.0.1',
1324
                  port=6633, **kwargs):
1325
        """Init.
1326
           name: name to give controller
1327
           ip: the IP address where the remote controller is
1328
           listening
1329
           port: the port where the remote controller is listening"""
1330
        Controller.__init__( self, name, ip=ip, port=port, **kwargs )
1331

    
1332
    def start( self ):
1333
        "Overridden to do nothing."
1334
        return
1335

    
1336
    def stop( self ):
1337
        "Overridden to do nothing."
1338
        return
1339

    
1340
    def checkListening( self ):
1341
        "Warn if remote controller is not accessible"
1342
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1343
                              ( self.ip, self.port ) )
1344
        if 'Connected' not in listening:
1345
            warn( "Unable to contact the remote controller"
1346
                  " at %s:%d\n" % ( self.ip, self.port ) )
1347

    
1348

    
1349
def DefaultController( name, order=[ Controller, OVSController ], **kwargs ):
1350
    "find a default controller for mininet"
1351
    for controller in order:
1352
        if controller.isAvailable():
1353
            return controller( name, **kwargs )