Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 00803bcd

History | View | Annotate | Download (48.4 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 re
49
import signal
50
import select
51
from subprocess import Popen, PIPE, STDOUT
52
from operator import or_
53
from time import sleep
54

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

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

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

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

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

    
77
        self.name = name
78
        self.inNamespace = inNamespace
79

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

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

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

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

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

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

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

    
112
    # Command support via shell process in namespace
113

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

    
145
    def cleanup( self ):
146
        "Help python collect its garbage."
147
        # Intfs may end up in root NS
148
        for intfName in self.intfNames():
149
            if self.name in intfName:
150
                quietRun( 'ip link del ' + intfName )
151
        self.shell = None
152

    
153
    # Subshell I/O, commands and control
154

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

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

    
181
    def write( self, data ):
182
        """Write data to node.
183
           data: string"""
184
        os.write( self.stdin.fileno(), data )
185

    
186
    def terminate( self ):
187
        "Send kill signal to Node and clean up after it."
188
        if self.shell:
189
            os.killpg( self.pid, signal.SIGKILL )
190
        self.cleanup()
191

    
192
    def stop( self ):
193
        "Stop node."
194
        self.terminate()
195

    
196
    def waitReadable( self, timeoutms=None ):
197
        """Wait until node's output is readable.
198
           timeoutms: timeout in ms or None to wait indefinitely."""
199
        if len( self.readbuf ) == 0:
200
            self.pollOut.poll( timeoutms )
201

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

    
235
    def sendInt( self, sig=signal.SIGINT ):
236
        "Interrupt running command."
237
        if self.lastPid:
238
            try:
239
                os.kill( self.lastPid, sig )
240
            except OSError:
241
                pass
242

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

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

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

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

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

    
322
    def pexec( self, *args, **kwargs ):
323
        """Execute a command using popen
324
           returns: out, err, exitcode"""
325
        popen = self.popen( *args, **kwargs)
326
        out, err = popen.communicate()
327
        exitcode = popen.wait()
328
        return out, err, exitcode
329

    
330
    # Interface management, configuration, and routing
331

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

    
338
    def newPort( self ):
339
        "Return the next port number to allocate."
340
        if len( self.ports ) > 0:
341
            return max( self.ports.values() ) + 1
342
        return self.portBase
343

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

    
359
    def defaultIntf( self ):
360
        "Return interface for lowest port"
361
        ports = self.intfs.keys()
362
        if ports:
363
            return self.intfs[ min( ports ) ]
364
        else:
365
            warn( '*** defaultIntf: warning:', self.name,
366
                  'has no interfaces\n' )
367

    
368
    def intf( self, intf='' ):
369
        """Return our interface object with given string name,
370
           default intf if name is falsy (None, empty string, etc).
371
           or the input intf arg.
372

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

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

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

    
412
    # Routing support
413

    
414
    def setARP( self, ip, mac ):
415
        """Add an ARP entry.
416
           ip: IP address as string
417
           mac: MAC address as string"""
418
        result = self.cmd( 'arp', '-s', ip, mac )
419
        return result
420

    
421
    def setHostRoute( self, ip, intf ):
422
        """Add route to host.
423
           ip: IP address as dotted decimal
424
           intf: string, interface name"""
425
        return self.cmd( 'route add -host', ip, 'dev', intf )
426

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

    
438
    # Convenience and configuration methods
439

    
440
    def setMAC( self, mac, intf=None ):
441
        """Set the MAC address for an interface.
442
           intf: intf or intf name
443
           mac: MAC address as string"""
444
        return self.intf( intf ).setMAC( mac )
445

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

    
456
    def IP( self, intf=None ):
457
        "Return IP address of a node or specific interface."
458
        return self.intf( intf ).IP()
459

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

    
464
    def intfIsUp( self, intf=None ):
465
        "Check if an interface is up."
466
        return self.intf( intf ).isUp()
467

    
468
    # The reason why we configure things in this way is so
469
    # That the parameters can be listed and documented in
470
    # the config method.
471
    # Dealing with subclasses and superclasses is slightly
472
    # annoying, but at least the information is there!
473

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

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

    
512
    def configDefault( self, **moreParams ):
513
        "Configure with default parameters"
514
        self.params.update( moreParams )
515
        self.config( **self.params )
516

    
517
    # This is here for backward compatibility
518
    def linkTo( self, node, link=Link ):
519
        """(Deprecated) Link to another node
520
           replace with Link( node1, node2)"""
521
        return link( self, node )
522

    
523
    # Other methods
524

    
525
    def intfList( self ):
526
        "List of our interfaces sorted by port number"
527
        return [ self.intfs[ p ] for p in sorted( self.intfs.iterkeys() ) ]
528

    
529
    def intfNames( self ):
530
        "The names of our interfaces sorted by port number"
531
        return [ str( i ) for i in self.intfList() ]
532

    
533
    def __repr__( self ):
534
        "More informative string representation"
535
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
536
                              for i in self.intfList() ] ) )
537
        return '<%s %s: %s pid=%s> ' % (
538
            self.__class__.__name__, self.name, intfs, self.pid )
539

    
540
    def __str__( self ):
541
        "Abbreviated string representation"
542
        return self.name
543

    
544
    # Automatic class setup support
545

    
546
    isSetup = False
547

    
548
    @classmethod
549
    def checkSetup( cls ):
550
        "Make sure our class and superclasses are set up"
551
        while cls and not getattr( cls, 'isSetup', True ):
552
            cls.setup()
553
            cls.isSetup = True
554
            # Make pylint happy
555
            cls = getattr( type( cls ), '__base__', None )
556

    
557
    @classmethod
558
    def setup( cls ):
559
        "Make sure our class dependencies are available"
560
        pathCheck( 'mnexec', 'ifconfig', moduleName='Mininet')
561

    
562

    
563
class Host( Node ):
564
    "A host is simply a Node"
565
    pass
566

    
567

    
568
class CPULimitedHost( Host ):
569

    
570
    "CPU limited host"
571

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

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

    
602
    def cgroupGet( self, param, resource='cpu' ):
603
        "Return value of cgroup parameter"
604
        cmd = 'cgget -r %s.%s /%s' % (
605
            resource, param, self.name )
606
        return int( quietRun( cmd ).split()[ -1 ] )
607

    
608
    def cgroupDel( self ):
609
        "Clean up our cgroup"
610
        # info( '*** deleting cgroup', self.cgroup, '\n' )
611
        _out, _err, exitcode = errRun( 'cgdelete -r ' + self.cgroup )
612
        return exitcode != 0
613

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

    
625
    def cleanup( self ):
626
        "Clean up Node, then clean up our cgroup"
627
        super( CPULimitedHost, self ).cleanup()
628
        retry( retries=3, delaySecs=1, fn=self.cgroupDel )
629

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

    
640
    def rtInfo( self, f ):
641
        "Internal method: return parameters for RT bandwidth"
642
        pstr, qstr = 'rt_period_us', 'rt_runtime_us'
643
        # RT uses wall clock time for period and quota
644
        quota = int( self.period_us * f * numCores() )
645
        return pstr, qstr, self.period_us, quota
646

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

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

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

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

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

    
720
    inited = False
721

    
722
    @classmethod
723
    def init( cls ):
724
        "Initialization for CPULimitedHost class"
725
        mountCgroups()
726
        cls.inited = True
727

    
728

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

    
749
class Switch( Node ):
750
    """A Switch is a Node that is running (or has execed?)
751
       an OpenFlow switch."""
752

    
753
    portBase = 1  # Switches start with port 1 in OpenFlow
754
    dpidLen = 16  # digits in dpid passed to switch
755

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

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

    
784
    def defaultIntf( self ):
785
        "Return control interface"
786
        if self.controlIntf:
787
            return self.controlIntf
788
        else:
789
            return Node.defaultIntf( self )
790

    
791
    def sendCmd( self, *cmd, **kwargs ):
792
        """Send command to Node.
793
           cmd: string"""
794
        kwargs.setdefault( 'printPid', False )
795
        if not self.execed:
796
            return Node.sendCmd( self, *cmd, **kwargs )
797
        else:
798
            error( '*** Error: %s has execed and cannot accept commands' %
799
                   self.name )
800

    
801
    def connected( self ):
802
        "Is the switch connected to a controller? (override this method)"
803
        return False and self  # satisfy pylint
804

    
805
    def __repr__( self ):
806
        "More informative string representation"
807
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
808
                              for i in self.intfList() ] ) )
809
        return '<%s %s: %s pid=%s> ' % (
810
            self.__class__.__name__, self.name, intfs, self.pid )
811

    
812
class UserSwitch( Switch ):
813
    "User-space switch."
814

    
815
    dpidLen = 12
816

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

    
829
    @classmethod
830
    def setup( cls ):
831
        "Ensure any dependencies are loaded; if not, try to load them."
832
        if not os.path.exists( '/dev/net/tun' ):
833
            moduleDeps( add=TUN )
834

    
835
    def dpctl( self, *args ):
836
        "Run dpctl command"
837
        listenAddr = None
838
        if not self.listenPort:
839
            listenAddr = 'unix:/tmp/' + self.name
840
        else:
841
            listenAddr = 'tcp:127.0.0.1:%i' % self.listenPort
842
        return self.cmd( 'dpctl ' + ' '.join( args ) +
843
                         ' ' + listenAddr )
844

    
845
    def connected( self ):
846
        "Is the switch connected to a controller?"
847
        return 'remote.is-connected=true' in self.dpctl( 'status' )
848

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

    
859
            res = intf.config( **intf.params )
860

    
861
            if res is None: # link may not have TC parameters
862
                return
863

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

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

    
900
    def stop( self ):
901
        "Stop OpenFlow reference user datapath."
902
        self.cmd( 'kill %ofdatapath' )
903
        self.cmd( 'kill %ofprotocol' )
904
        self.deleteIntfs()
905

    
906

    
907
class OVSLegacyKernelSwitch( Switch ):
908
    """Open VSwitch legacy kernel-space switch using ovs-openflowd.
909
       Currently only works in the root namespace."""
910

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

    
924
    @classmethod
925
    def setup( cls ):
926
        "Ensure any dependencies are loaded; if not, try to load them."
927
        pathCheck( 'ovs-dpctl', 'ovs-openflowd',
928
                   moduleName='Open vSwitch (openvswitch.org)')
929
        moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
930

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

    
951
    def stop( self ):
952
        "Terminate kernel datapath."
953
        quietRun( 'ovs-dpctl del-dp ' + self.dp )
954
        self.cmd( 'kill %ovs-openflowd' )
955
        self.deleteIntfs()
956

    
957

    
958
class OVSSwitch( Switch ):
959
    "Open vSwitch switch. Depends on ovs-vsctl."
960

    
961
    def __init__( self, name, failMode='secure', datapath='kernel',
962
                 inband=False, **params ):
963
        """Init.
964
           name: name for switch
965
           failMode: controller loss behavior (secure|open)
966
           datapath: userspace or kernel mode (kernel|user)
967
           inband: use in-band control (False)"""
968
        Switch.__init__( self, name, **params )
969
        self.failMode = failMode
970
        self.datapath = datapath
971
        self.inband = inband
972

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

    
995
    @classmethod
996
    def isOldOVS( cls ):
997
        return ( StrictVersion( cls.OVSVersion ) <
998
             StrictVersion( '1.10' ) )
999

    
1000
    @classmethod
1001
    def batchShutdown( cls, switches ):
1002
        "Call ovs-vsctl del-br on all OVSSwitches in a list"
1003
        quietRun( 'ovs-vsctl ' +
1004
                  ' -- '.join( '--if-exists del-br %s' % s
1005
                               for s in switches ) )
1006

    
1007
    def dpctl( self, *args ):
1008
        "Run ovs-ofctl command"
1009
        return self.cmd( 'ovs-ofctl', args[ 0 ], self, *args[ 1: ] )
1010

    
1011
    @staticmethod
1012
    def TCReapply( intf ):
1013
        """Unfortunately OVS and Mininet are fighting
1014
           over tc queuing disciplines. As a quick hack/
1015
           workaround, we clear OVS's and reapply our own."""
1016
        if type( intf ) is TCIntf:
1017
            intf.config( **intf.params )
1018

    
1019
    def attach( self, intf ):
1020
        "Connect a data port"
1021
        self.cmd( 'ovs-vsctl add-port', self, intf )
1022
        self.cmd( 'ifconfig', intf, 'up' )
1023
        self.TCReapply( intf )
1024

    
1025
    def detach( self, intf ):
1026
        "Disconnect a data port"
1027
        self.cmd( 'ovs-vsctl del-port', self, intf )
1028

    
1029
    def controllerUUIDs( self ):
1030
        "Return ovsdb UUIDs for our controllers"
1031
        uuids = []
1032
        controllers = self.cmd( 'ovs-vsctl -- get Bridge', self,
1033
                               'Controller' ).strip()
1034
        if controllers.startswith( '[' ) and controllers.endswith( ']' ):
1035
            controllers = controllers[ 1 : -1 ]
1036
            uuids = [ c.strip() for c in controllers.split( ',' ) ]
1037
        return uuids
1038

    
1039
    def connected( self ):
1040
        "Are we connected to at least one of our controllers?"
1041
        results = [ 'true' in self.cmd( 'ovs-vsctl -- get Controller',
1042
                                         uuid, 'is_connected' )
1043
                    for uuid in self.controllerUUIDs() ]
1044
        return reduce( or_, results, False )
1045

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

    
1101

    
1102
    def stop( self ):
1103
        "Terminate OVS switch."
1104
        self.cmd( 'ovs-vsctl del-br', self )
1105
        if self.datapath == 'user':
1106
            self.cmd( 'ip link del', self )
1107
        self.deleteIntfs()
1108

    
1109
OVSKernelSwitch = OVSSwitch
1110

    
1111

    
1112
class IVSSwitch(Switch):
1113
    """IVS virtual switch"""
1114

    
1115
    def __init__( self, name, verbose=True, **kwargs ):
1116
        Switch.__init__( self, name, **kwargs )
1117
        self.verbose = verbose
1118

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

    
1132
    @classmethod
1133
    def batchShutdown( cls, switches ):
1134
        "Kill each IVS switch, to be waited on later in stop()"
1135
        for switch in switches:
1136
            switch.cmd( 'kill %ivs' )
1137

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

    
1154
        logfile = '/tmp/ivs.%s.log' % self.name
1155

    
1156
        self.cmd( 'ifconfig lo up' )
1157
        self.cmd( ' '.join(args) + ' >' + logfile + ' 2>&1 </dev/null &' )
1158

    
1159
    def stop( self ):
1160
        "Terminate IVS switch."
1161
        self.cmd( 'kill %ivs' )
1162
        self.cmd( 'wait' )
1163
        self.deleteIntfs()
1164

    
1165
    def attach( self, intf ):
1166
        "Connect a data port"
1167
        self.cmd( 'ivs-ctl', 'add-port', '--datapath', self.name, intf )
1168

    
1169
    def detach( self, intf ):
1170
        "Disconnect a data port"
1171
        self.cmd( 'ivs-ctl', 'del-port', '--datapath', self.name, intf )
1172

    
1173
    def dpctl( self, *args ):
1174
        "Run dpctl command"
1175
        if not self.listenPort:
1176
            return "can't run dpctl without passive listening port"
1177
        return self.cmd( 'ovs-ofctl ' + ' '.join( args ) +
1178
                         ' tcp:127.0.0.1:%i' % self.listenPort )
1179

    
1180

    
1181
class Controller( Node ):
1182
    """A Controller is a Node that is running (or has execed?) an
1183
       OpenFlow controller."""
1184

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

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

    
1217
    def start( self ):
1218
        """Start <controller> <args> on controller.
1219
           Log to /tmp/cN.log"""
1220
        pathCheck( self.command )
1221
        cout = '/tmp/' + self.name + '.log'
1222
        if self.cdir is not None:
1223
            self.cmd( 'cd ' + self.cdir )
1224
        self.cmd( self.command + ' ' + self.cargs % self.port +
1225
                  ' 1>' + cout + ' 2>' + cout + '&' )
1226
        self.execed = False
1227

    
1228
    def stop( self ):
1229
        "Stop controller."
1230
        self.cmd( 'kill %' + self.command )
1231
        self.terminate()
1232

    
1233
    def IP( self, intf=None ):
1234
        "Return IP address of the Controller"
1235
        if self.intfs:
1236
            ip = Node.IP( self, intf )
1237
        else:
1238
            ip = self.ip
1239
        return ip
1240

    
1241
    def __repr__( self ):
1242
        "More informative string representation"
1243
        return '<%s %s: %s:%s pid=%s> ' % (
1244
            self.__class__.__name__, self.name,
1245
            self.IP(), self.port, self.pid )
1246

    
1247

    
1248
class OVSController( Controller ):
1249
    "Open vSwitch controller"
1250
    def __init__( self, name, command='ovs-controller', **kwargs ):
1251
        Controller.__init__( self, name, command=command, **kwargs )
1252

    
1253

    
1254
class NOX( Controller ):
1255
    "Controller to run a NOX application."
1256

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

    
1268
        if 'NOX_CORE_DIR' not in os.environ:
1269
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
1270
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
1271

    
1272
        Controller.__init__( self, name,
1273
                             command=noxCoreDir + '/nox_core',
1274
                             cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
1275
                             ' '.join( noxArgs ),
1276
                             cdir=noxCoreDir,
1277
                             **kwargs )
1278

    
1279

    
1280
class RemoteController( Controller ):
1281
    "Controller running outside of Mininet's control."
1282

    
1283
    def __init__( self, name, ip='127.0.0.1',
1284
                  port=6633, **kwargs):
1285
        """Init.
1286
           name: name to give controller
1287
           ip: the IP address where the remote controller is
1288
           listening
1289
           port: the port where the remote controller is listening"""
1290
        Controller.__init__( self, name, ip=ip, port=port, **kwargs )
1291

    
1292
    def start( self ):
1293
        "Overridden to do nothing."
1294
        return
1295

    
1296
    def stop( self ):
1297
        "Overridden to do nothing."
1298
        return
1299

    
1300
    def checkListening( self ):
1301
        "Warn if remote controller is not accessible"
1302
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1303
                              ( self.ip, self.port ) )
1304
        if 'Connected' not in listening:
1305
            warn( "Unable to contact the remote controller"
1306
                  " at %s:%d\n" % ( self.ip, self.port ) )