Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 355696f3

History | View | Annotate | Download (48.7 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
Switch: superclass for switch nodes.
20

21
UserSwitch: a switch using the user-space switch from the OpenFlow
22
    reference implementation.
23

24
KernelSwitch: a switch using the kernel switch from the OpenFlow reference
25
    implementation.
26

27
OVSSwitch: a switch using the OpenVSwitch OpenFlow-compatible switch
28
    implementation (openvswitch.org).
29

30
Controller: superclass for OpenFlow controllers. The default controller
31
    is controller(8) from the reference implementation.
32

33
NOXController: a controller node using NOX (noxrepo.org).
34

35
RemoteController: a remote controller node, which may use any
36
    arbitrary OpenFlow-compatible controller, and which is not
37
    created or managed by mininet.
38

39
Future enhancements:
40

41
- Possibly make Node, Switch and Controller more abstract so that
42
  they can be used for both local and remote nodes
43

44
- Create proxy objects for remote nodes (Mininet: Cluster Edition)
45
"""
46

    
47
import os
48
import pty
49
import re
50
import signal
51
import select
52
from subprocess import Popen, PIPE, STDOUT
53
from operator import or_
54
from time import sleep
55

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

    
64
class Node( object ):
65
    """A virtual network node is simply a shell in a network namespace.
66
       We communicate with it using pipes."""
67

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

    
70
    def __init__( self, name, inNamespace=True, **params ):
71
        """name: name of node
72
           inNamespace: in network namespace?
73
           params: Node parameters (see config() for details)"""
74

    
75
        # Make sure class actually works
76
        self.checkSetup()
77

    
78
        self.name = name
79
        self.inNamespace = inNamespace
80

    
81
        # Stash configuration parameters for future reference
82
        self.params = params
83

    
84
        self.intfs = {}  # dict of port numbers to interfaces
85
        self.ports = {}  # dict of interfaces to port numbers
86
                         # replace with Port objects, eventually ?
87
        self.nameToIntf = {}  # dict of interface names to Intfs
88

    
89
        # Make pylint happy
90
        ( self.shell, self.execed, self.pid, self.stdin, self.stdout,
91
            self.lastPid, self.lastCmd, self.pollOut ) = (
92
                None, None, None, None, None, None, None, None )
93
        self.waiting = False
94
        self.readbuf = ''
95

    
96
        # Start command interpreter shell
97
        self.startShell()
98

    
99
    # File descriptor to node mapping support
100
    # Class variables and methods
101

    
102
    inToNode = {}  # mapping of input fds to nodes
103
    outToNode = {}  # mapping of output fds to nodes
104

    
105
    @classmethod
106
    def fdToNode( cls, fd ):
107
        """Return node corresponding to given file descriptor.
108
           fd: file descriptor
109
           returns: node"""
110
        node = cls.outToNode.get( fd )
111
        return node or cls.inToNode.get( fd )
112

    
113
    # Command support via shell process in namespace
114

    
115
    def startShell( self ):
116
        "Start a shell process for running commands"
117
        if self.shell:
118
            error( "%s: shell is already running" )
119
            return
120
        # mnexec: (c)lose descriptors, (d)etach from tty,
121
        # (p)rint pid, and run in (n)amespace
122
        opts = '-cd'
123
        if self.inNamespace:
124
            opts += 'n'
125
        # bash -m: enable job control, i: force interactive
126
        # -s: pass $* to shell, and make process easy to find in ps
127
        # prompt is set to sentinel chr( 127 )
128
        os.environ[ 'PS1' ] = chr( 127 )
129
        cmd = [ 'mnexec', opts, 'bash', '--norc', '-mis', 'mininet:' + self.name ]
130
        # Spawn a shell subprocess in a pseudo-tty, to disable buffering
131
        # in the subprocess and insulate it from signals (e.g. SIGINT)
132
        # received by the parent
133
        master, slave = pty.openpty()
134
        self.shell = Popen( cmd, stdin=slave, stdout=slave, stderr=slave,
135
                                  close_fds=False )
136
        self.stdin = os.fdopen( master )
137
        self.stdout = self.stdin
138
        self.pid = self.shell.pid
139
        self.pollOut = select.poll()
140
        self.pollOut.register( self.stdout )
141
        # Maintain mapping between file descriptors and nodes
142
        # This is useful for monitoring multiple nodes
143
        # using select.poll()
144
        self.outToNode[ self.stdout.fileno() ] = self
145
        self.inToNode[ self.stdin.fileno() ] = self
146
        self.execed = False
147
        self.lastCmd = None
148
        self.lastPid = None
149
        self.readbuf = ''
150
        # Wait for prompt
151
        while True:
152
            data = self.read( 1024 )
153
            if chr( 127 ) in data:
154
                break
155
            self.pollOut.poll()
156
        self.waiting = False
157
        self.cmd( 'stty -echo' )
158

    
159
    def cleanup( self ):
160
        "Help python collect its garbage."
161
        # Intfs may end up in root NS
162
        for intfName in self.intfNames():
163
            if self.name in intfName:
164
                quietRun( 'ip link del ' + intfName )
165
        self.shell = None
166

    
167
    # Subshell I/O, commands and control
168

    
169
    def read( self, maxbytes=1024 ):
170
        """Buffered read from node, non-blocking.
171
           maxbytes: maximum number of bytes to return"""
172
        count = len( self.readbuf )
173
        if count < maxbytes:
174
            data = os.read( self.stdout.fileno(), maxbytes - count )
175
            self.readbuf += data
176
        if maxbytes >= len( self.readbuf ):
177
            result = self.readbuf
178
            self.readbuf = ''
179
        else:
180
            result = self.readbuf[ :maxbytes ]
181
            self.readbuf = self.readbuf[ maxbytes: ]
182
        return result
183

    
184
    def readline( self ):
185
        """Buffered readline from node, non-blocking.
186
           returns: line (minus newline) or None"""
187
        self.readbuf += self.read( 1024 )
188
        if '\n' not in self.readbuf:
189
            return None
190
        pos = self.readbuf.find( '\n' )
191
        line = self.readbuf[ 0: pos ]
192
        self.readbuf = self.readbuf[ pos + 1: ]
193
        return line
194

    
195
    def write( self, data ):
196
        """Write data to node.
197
           data: string"""
198
        os.write( self.stdin.fileno(), data )
199

    
200
    def terminate( self ):
201
        "Send kill signal to Node and clean up after it."
202
        if self.shell:
203
            os.killpg( self.pid, signal.SIGKILL )
204
        self.cleanup()
205

    
206
    def stop( self ):
207
        "Stop node."
208
        self.terminate()
209

    
210
    def waitReadable( self, timeoutms=None ):
211
        """Wait until node's output is readable.
212
           timeoutms: timeout in ms or None to wait indefinitely."""
213
        if len( self.readbuf ) == 0:
214
            self.pollOut.poll( timeoutms )
215

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

    
242
    def sendInt( self, intr=chr( 3 ) ):
243
        "Interrupt running command."
244
        self.write( intr )
245

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

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

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

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

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

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

    
336
    # Interface management, configuration, and routing
337

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

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

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

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

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

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

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

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

    
418
    # Routing support
419

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

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

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

    
444
    # Convenience and configuration methods
445

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

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

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

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

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

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

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

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

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

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

    
529
    # Other methods
530

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

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

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

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

    
550
    # Automatic class setup support
551

    
552
    isSetup = False
553

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

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

    
568

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

    
573

    
574
class CPULimitedHost( Host ):
575

    
576
    "CPU limited host"
577

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

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

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

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

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

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

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

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

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

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

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

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

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

    
726
    inited = False
727

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

    
734

    
735
# Some important things to note:
736
#
737
# The "IP" address which setIP() assigns to the switch is not
738
# an "IP address for the switch" in the sense of IP routing.
739
# Rather, it is the IP address for the control interface,
740
# on the control network, and it is only relevant to the
741
# controller. If you are running in the root namespace
742
# (which is the only way to run OVS at the moment), the
743
# control interface is the loopback interface, and you
744
# normally never want to change its IP address!
745
#
746
# In general, you NEVER want to attempt to use Linux's
747
# network stack (i.e. ifconfig) to "assign" an IP address or
748
# MAC address to a switch data port. Instead, you "assign"
749
# the IP and MAC addresses in the controller by specifying
750
# packets that you want to receive or send. The "MAC" address
751
# reported by ifconfig for a switch data port is essentially
752
# meaningless. It is important to understand this if you
753
# want to create a functional router using OpenFlow.
754

    
755
class Switch( Node ):
756
    """A Switch is a Node that is running (or has execed?)
757
       an OpenFlow switch."""
758

    
759
    portBase = 1  # Switches start with port 1 in OpenFlow
760
    dpidLen = 16  # digits in dpid passed to switch
761

    
762
    def __init__( self, name, dpid=None, opts='', listenPort=None, **params):
763
        """dpid: dpid hex string (or None to derive from name, e.g. s1 -> 1)
764
           opts: additional switch options
765
           listenPort: port to listen on for dpctl connections"""
766
        Node.__init__( self, name, **params )
767
        self.dpid = self.defaultDpid( dpid )
768
        self.opts = opts
769
        self.listenPort = listenPort
770
        if not self.inNamespace:
771
            self.controlIntf = Intf( 'lo', self, port=0 )
772

    
773
    def defaultDpid( self, dpid=None ):
774
        "Return correctly formatted dpid from dpid or switch name (s1 -> 1)"
775
        if dpid:
776
            # Remove any colons and make sure it's a good hex number
777
            dpid = dpid.translate( None, ':' )
778
            assert len( dpid ) <= self.dpidLen and int( dpid, 16 ) >= 0
779
        else:
780
            # Use hex of the first number in the switch name
781
            nums = re.findall( r'\d+', self.name )
782
            if nums:
783
                dpid = hex( int( nums[ 0 ] ) )[ 2: ]
784
            else:
785
                raise Exception( 'Unable to derive default datapath ID - '
786
                                 'please either specify a dpid or use a '
787
                                 'canonical switch name such as s23.' )
788
        return '0' * ( self.dpidLen - len( dpid ) ) + dpid
789

    
790
    def defaultIntf( self ):
791
        "Return control interface"
792
        if self.controlIntf:
793
            return self.controlIntf
794
        else:
795
            return Node.defaultIntf( self )
796

    
797
    def sendCmd( self, *cmd, **kwargs ):
798
        """Send command to Node.
799
           cmd: string"""
800
        kwargs.setdefault( 'printPid', False )
801
        if not self.execed:
802
            return Node.sendCmd( self, *cmd, **kwargs )
803
        else:
804
            error( '*** Error: %s has execed and cannot accept commands' %
805
                   self.name )
806

    
807
    def connected( self ):
808
        "Is the switch connected to a controller? (override this method)"
809
        return False and self  # satisfy pylint
810

    
811
    def __repr__( self ):
812
        "More informative string representation"
813
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
814
                              for i in self.intfList() ] ) )
815
        return '<%s %s: %s pid=%s> ' % (
816
            self.__class__.__name__, self.name, intfs, self.pid )
817

    
818
class UserSwitch( Switch ):
819
    "User-space switch."
820

    
821
    dpidLen = 12
822

    
823
    def __init__( self, name, dpopts='--no-slicing', **kwargs ):
824
        """Init.
825
           name: name for the switch
826
           dpopts: additional arguments to ofdatapath (--no-slicing)"""
827
        Switch.__init__( self, name, **kwargs )
828
        pathCheck( 'ofdatapath', 'ofprotocol',
829
                   moduleName='the OpenFlow reference user switch' +
830
                              '(openflow.org)' )
831
        if self.listenPort:
832
            self.opts += ' --listen=ptcp:%i ' % self.listenPort
833
        self.dpopts = dpopts
834

    
835
    @classmethod
836
    def setup( cls ):
837
        "Ensure any dependencies are loaded; if not, try to load them."
838
        if not os.path.exists( '/dev/net/tun' ):
839
            moduleDeps( add=TUN )
840

    
841
    def dpctl( self, *args ):
842
        "Run dpctl command"
843
        listenAddr = None
844
        if not self.listenPort:
845
            listenAddr = 'unix:/tmp/' + self.name
846
        else:
847
            listenAddr = 'tcp:127.0.0.1:%i' % self.listenPort
848
        return self.cmd( 'dpctl ' + ' '.join( args ) +
849
                         ' ' + listenAddr )
850

    
851
    def connected( self ):
852
        "Is the switch connected to a controller?"
853
        return 'remote.is-connected=true' in self.dpctl( 'status' )
854

    
855
    @staticmethod
856
    def TCReapply( intf ):
857
        """Unfortunately user switch and Mininet are fighting
858
           over tc queuing disciplines. To resolve the conflict,
859
           we re-create the user switch's configuration, but as a
860
           leaf of the TCIntf-created configuration."""
861
        if type( intf ) is TCIntf:
862
            ifspeed = 10000000000 # 10 Gbps
863
            minspeed = ifspeed * 0.001
864

    
865
            res = intf.config( **intf.params )
866

    
867
            if res is None: # link may not have TC parameters
868
                return
869

    
870
            # Re-add qdisc, root, and default classes user switch created, but
871
            # with new parent, as setup by Mininet's TCIntf
872
            parent = res['parent']
873
            intf.tc( "%s qdisc add dev %s " + parent +
874
                     " handle 1: htb default 0xfffe" )
875
            intf.tc( "%s class add dev %s classid 1:0xffff parent 1: htb rate "
876
                     + str(ifspeed) )
877
            intf.tc( "%s class add dev %s classid 1:0xfffe parent 1:0xffff " +
878
                     "htb rate " + str(minspeed) + " ceil " + str(ifspeed) )
879

    
880
    def start( self, controllers ):
881
        """Start OpenFlow reference user datapath.
882
           Log to /tmp/sN-{ofd,ofp}.log.
883
           controllers: list of controller objects"""
884
        # Add controllers
885
        clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
886
                            for c in controllers ] )
887
        ofdlog = '/tmp/' + self.name + '-ofd.log'
888
        ofplog = '/tmp/' + self.name + '-ofp.log'
889
        self.cmd( 'ifconfig lo up' )
890
        intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
891
        self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
892
                  ' punix:/tmp/' + self.name + ' -d %s ' % self.dpid +
893
                  self.dpopts +
894
                  ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
895
        self.cmd( 'ofprotocol unix:/tmp/' + self.name +
896
                  ' ' + clist +
897
                  ' --fail=closed ' + self.opts +
898
                  ' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
899
        if "no-slicing" not in self.dpopts:
900
            # Only TCReapply if slicing is enable
901
            sleep(1) # Allow ofdatapath to start before re-arranging qdisc's
902
            for intf in self.intfList():
903
                if not intf.IP():
904
                    self.TCReapply( intf )
905

    
906
    def stop( self ):
907
        "Stop OpenFlow reference user datapath."
908
        self.cmd( 'kill %ofdatapath' )
909
        self.cmd( 'kill %ofprotocol' )
910
        self.deleteIntfs()
911

    
912

    
913
class OVSLegacyKernelSwitch( Switch ):
914
    """Open VSwitch legacy kernel-space switch using ovs-openflowd.
915
       Currently only works in the root namespace."""
916

    
917
    def __init__( self, name, dp=None, **kwargs ):
918
        """Init.
919
           name: name for switch
920
           dp: netlink id (0, 1, 2, ...)
921
           defaultMAC: default MAC as unsigned int; random value if None"""
922
        Switch.__init__( self, name, **kwargs )
923
        self.dp = dp if dp else self.name
924
        self.intf = self.dp
925
        if self.inNamespace:
926
            error( "OVSKernelSwitch currently only works"
927
                   " in the root namespace.\n" )
928
            exit( 1 )
929

    
930
    @classmethod
931
    def setup( cls ):
932
        "Ensure any dependencies are loaded; if not, try to load them."
933
        pathCheck( 'ovs-dpctl', 'ovs-openflowd',
934
                   moduleName='Open vSwitch (openvswitch.org)')
935
        moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
936

    
937
    def start( self, controllers ):
938
        "Start up kernel datapath."
939
        ofplog = '/tmp/' + self.name + '-ofp.log'
940
        quietRun( 'ifconfig lo up' )
941
        # Delete local datapath if it exists;
942
        # then create a new one monitoring the given interfaces
943
        self.cmd( 'ovs-dpctl del-dp ' + self.dp )
944
        self.cmd( 'ovs-dpctl add-dp ' + self.dp )
945
        intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
946
        self.cmd( 'ovs-dpctl', 'add-if', self.dp, ' '.join( intfs ) )
947
        # Run protocol daemon
948
        clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
949
                            for c in controllers ] )
950
        self.cmd( 'ovs-openflowd ' + self.dp +
951
                  ' ' + clist +
952
                  ' --fail=secure ' + self.opts +
953
                  ' --datapath-id=' + self.dpid +
954
                  ' 1>' + ofplog + ' 2>' + ofplog + '&' )
955
        self.execed = False
956

    
957
    def stop( self ):
958
        "Terminate kernel datapath."
959
        quietRun( 'ovs-dpctl del-dp ' + self.dp )
960
        self.cmd( 'kill %ovs-openflowd' )
961
        self.deleteIntfs()
962

    
963

    
964
class OVSSwitch( Switch ):
965
    "Open vSwitch switch. Depends on ovs-vsctl."
966

    
967
    def __init__( self, name, failMode='secure', datapath='kernel',
968
                 inband=False, **params ):
969
        """Init.
970
           name: name for switch
971
           failMode: controller loss behavior (secure|open)
972
           datapath: userspace or kernel mode (kernel|user)
973
           inband: use in-band control (False)"""
974
        Switch.__init__( self, name, **params )
975
        self.failMode = failMode
976
        self.datapath = datapath
977
        self.inband = inband
978

    
979
    @classmethod
980
    def setup( cls ):
981
        "Make sure Open vSwitch is installed and working"
982
        pathCheck( 'ovs-vsctl',
983
                   moduleName='Open vSwitch (openvswitch.org)')
984
        # This should no longer be needed, and it breaks
985
        # with OVS 1.7 which has renamed the kernel module:
986
        #  moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
987
        out, err, exitcode = errRun( 'ovs-vsctl -t 1 show' )
988
        if exitcode:
989
            error( out + err +
990
                   'ovs-vsctl exited with code %d\n' % exitcode +
991
                   '*** Error connecting to ovs-db with ovs-vsctl\n'
992
                   'Make sure that Open vSwitch is installed, '
993
                   'that ovsdb-server is running, and that\n'
994
                   '"ovs-vsctl show" works correctly.\n'
995
                   'You may wish to try '
996
                   '"service openvswitch-switch start".\n' )
997
            exit( 1 )
998
        info = quietRun( 'ovs-vsctl --version' )
999
        cls.OVSVersion =  findall( '\d+\.\d+', info )[ 0 ]
1000

    
1001
    @classmethod
1002
    def isOldOVS( cls ):
1003
        return ( StrictVersion( cls.OVSVersion ) <
1004
             StrictVersion( '1.10' ) )
1005

    
1006
    @classmethod
1007
    def batchShutdown( cls, switches ):
1008
        "Call ovs-vsctl del-br on all OVSSwitches in a list"
1009
        quietRun( 'ovs-vsctl ' +
1010
                  ' -- '.join( '--if-exists del-br %s' % s
1011
                               for s in switches ) )
1012

    
1013
    def dpctl( self, *args ):
1014
        "Run ovs-ofctl command"
1015
        return self.cmd( 'ovs-ofctl', args[ 0 ], self, *args[ 1: ] )
1016

    
1017
    @staticmethod
1018
    def TCReapply( intf ):
1019
        """Unfortunately OVS and Mininet are fighting
1020
           over tc queuing disciplines. As a quick hack/
1021
           workaround, we clear OVS's and reapply our own."""
1022
        if type( intf ) is TCIntf:
1023
            intf.config( **intf.params )
1024

    
1025
    def attach( self, intf ):
1026
        "Connect a data port"
1027
        self.cmd( 'ovs-vsctl add-port', self, intf )
1028
        self.cmd( 'ifconfig', intf, 'up' )
1029
        self.TCReapply( intf )
1030

    
1031
    def detach( self, intf ):
1032
        "Disconnect a data port"
1033
        self.cmd( 'ovs-vsctl del-port', self, intf )
1034

    
1035
    def controllerUUIDs( self ):
1036
        "Return ovsdb UUIDs for our controllers"
1037
        uuids = []
1038
        controllers = self.cmd( 'ovs-vsctl -- get Bridge', self,
1039
                               'Controller' ).strip()
1040
        if controllers.startswith( '[' ) and controllers.endswith( ']' ):
1041
            controllers = controllers[ 1 : -1 ]
1042
            uuids = [ c.strip() for c in controllers.split( ',' ) ]
1043
        return uuids
1044

    
1045
    def connected( self ):
1046
        "Are we connected to at least one of our controllers?"
1047
        results = [ 'true' in self.cmd( 'ovs-vsctl -- get Controller',
1048
                                         uuid, 'is_connected' )
1049
                    for uuid in self.controllerUUIDs() ]
1050
        return reduce( or_, results, False )
1051

    
1052
    def start( self, controllers ):
1053
        "Start up a new OVS OpenFlow switch using ovs-vsctl"
1054
        if self.inNamespace:
1055
            raise Exception(
1056
                'OVS kernel switch does not work in a namespace' )
1057
        # We should probably call config instead, but this
1058
        # requires some rethinking...
1059
        self.cmd( 'ifconfig lo up' )
1060
        # Annoyingly, --if-exists option seems not to work
1061
        self.cmd( 'ovs-vsctl del-br', self )
1062
        int( self.dpid, 16 ) # DPID must be a hex string
1063
        # Interfaces and controllers
1064
        intfs = ' '.join( '-- add-port %s %s ' % ( self, intf ) +
1065
                          '-- set Interface %s ' % intf +
1066
                          'ofport_request=%s ' % self.ports[ intf ]
1067
                         for intf in self.intfList() if not intf.IP() )
1068
        clist = ' '.join( '%s:%s:%d' % ( c.protocol, c.IP(), c.port )
1069
                         for c in controllers )
1070
        if self.listenPort:
1071
            clist += ' ptcp:%s' % self.listenPort
1072
        # Construct big ovs-vsctl command for new versions of OVS
1073
        if not self.isOldOVS():
1074
            cmd = ( 'ovs-vsctl add-br %s ' % self +
1075
                    '-- set Bridge %s ' % self +
1076
                    'other_config:datapath-id=%s ' % self.dpid +
1077
                    '-- set-fail-mode %s %s ' % ( self, self.failMode ) +
1078
                    intfs +
1079
                    '-- set-controller %s %s ' % ( self, clist ) )
1080
        # Construct ovs-vsctl commands for old versions of OVS
1081
        else:
1082
            self.cmd( 'ovs-vsctl add-br', self )
1083
            for intf in self.intfList():
1084
                if not intf.IP():
1085
                    self.cmd( 'ovs-vsctl add-port', self, intf )
1086
            cmd = ( 'ovs-vsctl set Bridge %s ' % self +
1087
                    'other_config:datapath-id=%s ' % self.dpid +
1088
                    '-- set-fail-mode %s %s ' % ( self, self.failMode ) +
1089
                    '-- set-controller %s %s ' % ( self, clist ) )
1090
        if not self.inband:
1091
            cmd += ( '-- set bridge %s '
1092
                     'other-config:disable-in-band=true ' % self )
1093
        if self.datapath == 'user':
1094
            cmd += '-- set bridge %s datapath_type=netdev ' % self
1095
        # Reconnect quickly to controllers (1s vs. 15s max_backoff)
1096
        for uuid in self.controllerUUIDs():
1097
            if uuid.count( '-' ) != 4:
1098
                # Doesn't look like a UUID
1099
                continue
1100
            uuid = uuid.strip()
1101
            cmd += '-- set Controller %smax_backoff=1000 ' % uuid
1102
        # Do it!!
1103
        self.cmd( cmd )
1104
        for intf in self.intfList():
1105
            self.TCReapply( intf )
1106

    
1107

    
1108
    def stop( self ):
1109
        "Terminate OVS switch."
1110
        self.cmd( 'ovs-vsctl del-br', self )
1111
        if self.datapath == 'user':
1112
            self.cmd( 'ip link del', self )
1113
        self.deleteIntfs()
1114

    
1115
OVSKernelSwitch = OVSSwitch
1116

    
1117

    
1118
class IVSSwitch(Switch):
1119
    """IVS virtual switch"""
1120

    
1121
    def __init__( self, name, verbose=True, **kwargs ):
1122
        Switch.__init__( self, name, **kwargs )
1123
        self.verbose = verbose
1124

    
1125
    @classmethod
1126
    def setup( cls ):
1127
        "Make sure IVS is installed"
1128
        pathCheck( 'ivs-ctl', 'ivs',
1129
                   moduleName="Indigo Virtual Switch (projectfloodlight.org)" )
1130
        out, err, exitcode = errRun( 'ivs-ctl show' )
1131
        if exitcode:
1132
            error( out + err +
1133
                   'ivs-ctl exited with code %d\n' % exitcode +
1134
                   '*** The openvswitch kernel module might '
1135
                   'not be loaded. Try modprobe openvswitch.\n' )
1136
            exit( 1 )
1137

    
1138
    @classmethod
1139
    def batchShutdown( cls, switches ):
1140
        "Kill each IVS switch, to be waited on later in stop()"
1141
        for switch in switches:
1142
            switch.cmd( 'kill %ivs' )
1143

    
1144
    def start( self, controllers ):
1145
        "Start up a new IVS switch"
1146
        args = ['ivs']
1147
        args.extend( ['--name', self.name] )
1148
        args.extend( ['--dpid', self.dpid] )
1149
        if self.verbose:
1150
            args.extend( ['--verbose'] )
1151
        for intf in self.intfs.values():
1152
            if not intf.IP():
1153
                args.extend( ['-i', intf.name] )
1154
        for c in controllers:
1155
            args.extend( ['-c', '%s:%d' % (c.IP(), c.port)] )
1156
        if self.listenPort:
1157
            args.extend( ['--listen', '127.0.0.1:%i' % self.listenPort] )
1158
        args.append( self.opts )
1159

    
1160
        logfile = '/tmp/ivs.%s.log' % self.name
1161

    
1162
        self.cmd( 'ifconfig lo up' )
1163
        self.cmd( ' '.join(args) + ' >' + logfile + ' 2>&1 </dev/null &' )
1164

    
1165
    def stop( self ):
1166
        "Terminate IVS switch."
1167
        self.cmd( 'kill %ivs' )
1168
        self.cmd( 'wait' )
1169
        self.deleteIntfs()
1170

    
1171
    def attach( self, intf ):
1172
        "Connect a data port"
1173
        self.cmd( 'ivs-ctl', 'add-port', '--datapath', self.name, intf )
1174

    
1175
    def detach( self, intf ):
1176
        "Disconnect a data port"
1177
        self.cmd( 'ivs-ctl', 'del-port', '--datapath', self.name, intf )
1178

    
1179
    def dpctl( self, *args ):
1180
        "Run dpctl command"
1181
        if not self.listenPort:
1182
            return "can't run dpctl without passive listening port"
1183
        return self.cmd( 'ovs-ofctl ' + ' '.join( args ) +
1184
                         ' tcp:127.0.0.1:%i' % self.listenPort )
1185

    
1186

    
1187
class Controller( Node ):
1188
    """A Controller is a Node that is running (or has execed?) an
1189
       OpenFlow controller."""
1190

    
1191
    def __init__( self, name, inNamespace=False, command='controller',
1192
                  cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
1193
                  port=6633, protocol='tcp', **params ):
1194
        self.command = command
1195
        self.cargs = cargs
1196
        self.cdir = cdir
1197
        self.ip = ip
1198
        self.port = port
1199
        self.protocol = protocol
1200
        Node.__init__( self, name, inNamespace=inNamespace,
1201
                       ip=ip, **params  )
1202
        self.cmd( 'ifconfig lo up' )  # Shouldn't be necessary
1203
        self.checkListening()
1204

    
1205
    def checkListening( self ):
1206
        "Make sure no controllers are running on our port"
1207
        # Verify that Telnet is installed first:
1208
        out, _err, returnCode = errRun( "which telnet" )
1209
        if 'telnet' not in out or returnCode != 0:
1210
            raise Exception( "Error running telnet to check for listening "
1211
                             "controllers; please check that it is "
1212
                             "installed." )
1213
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1214
                              ( self.ip, self.port ) )
1215
        if 'Connected' in listening:
1216
            servers = self.cmd( 'netstat -natp' ).split( '\n' )
1217
            pstr = ':%d ' % self.port
1218
            clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ]
1219
            raise Exception( "Please shut down the controller which is"
1220
                             " running on port %d:\n" % self.port +
1221
                             '\n'.join( clist ) )
1222

    
1223
    def start( self ):
1224
        """Start <controller> <args> on controller.
1225
           Log to /tmp/cN.log"""
1226
        pathCheck( self.command )
1227
        cout = '/tmp/' + self.name + '.log'
1228
        if self.cdir is not None:
1229
            self.cmd( 'cd ' + self.cdir )
1230
        self.cmd( self.command + ' ' + self.cargs % self.port +
1231
                  ' 1>' + cout + ' 2>' + cout + '&' )
1232
        self.execed = False
1233

    
1234
    def stop( self ):
1235
        "Stop controller."
1236
        self.cmd( 'kill %' + self.command )
1237
        self.terminate()
1238

    
1239
    def IP( self, intf=None ):
1240
        "Return IP address of the Controller"
1241
        if self.intfs:
1242
            ip = Node.IP( self, intf )
1243
        else:
1244
            ip = self.ip
1245
        return ip
1246

    
1247
    def __repr__( self ):
1248
        "More informative string representation"
1249
        return '<%s %s: %s:%s pid=%s> ' % (
1250
            self.__class__.__name__, self.name,
1251
            self.IP(), self.port, self.pid )
1252

    
1253

    
1254
class OVSController( Controller ):
1255
    "Open vSwitch controller"
1256
    def __init__( self, name, command='ovs-controller', **kwargs ):
1257
        Controller.__init__( self, name, command=command, **kwargs )
1258

    
1259

    
1260
class NOX( Controller ):
1261
    "Controller to run a NOX application."
1262

    
1263
    def __init__( self, name, *noxArgs, **kwargs ):
1264
        """Init.
1265
           name: name to give controller
1266
           noxArgs: arguments (strings) to pass to NOX"""
1267
        if not noxArgs:
1268
            warn( 'warning: no NOX modules specified; '
1269
                  'running packetdump only\n' )
1270
            noxArgs = [ 'packetdump' ]
1271
        elif type( noxArgs ) not in ( list, tuple ):
1272
            noxArgs = [ noxArgs ]
1273

    
1274
        if 'NOX_CORE_DIR' not in os.environ:
1275
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
1276
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
1277

    
1278
        Controller.__init__( self, name,
1279
                             command=noxCoreDir + '/nox_core',
1280
                             cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
1281
                             ' '.join( noxArgs ),
1282
                             cdir=noxCoreDir,
1283
                             **kwargs )
1284

    
1285

    
1286
class RemoteController( Controller ):
1287
    "Controller running outside of Mininet's control."
1288

    
1289
    def __init__( self, name, ip='127.0.0.1',
1290
                  port=6633, **kwargs):
1291
        """Init.
1292
           name: name to give controller
1293
           ip: the IP address where the remote controller is
1294
           listening
1295
           port: the port where the remote controller is listening"""
1296
        Controller.__init__( self, name, ip=ip, port=port, **kwargs )
1297

    
1298
    def start( self ):
1299
        "Overridden to do nothing."
1300
        return
1301

    
1302
    def stop( self ):
1303
        "Overridden to do nothing."
1304
        return
1305

    
1306
    def checkListening( self ):
1307
        "Warn if remote controller is not accessible"
1308
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1309
                              ( self.ip, self.port ) )
1310
        if 'Connected' not in listening:
1311
            warn( "Unable to contact the remote controller"
1312
                  " at %s:%d\n" % ( self.ip, self.port ) )