Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 6df4371d

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

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

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

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

    
72
        # Make sure class actually works
73
        self.checkSetup()
74

    
75
        self.name = name
76
        self.inNamespace = inNamespace
77

    
78
        # Stash configuration parameters for future reference
79
        self.params = params
80

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

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

    
93
        # Start command interpreter shell
94
        self.startShell()
95

    
96
    # File descriptor to node mapping support
97
    # Class variables and methods
98

    
99
    inToNode = {}  # mapping of input fds to nodes
100
    outToNode = {}  # mapping of output fds to nodes
101

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

    
110
    # Command support via shell process in namespace
111

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

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

    
151
    # Subshell I/O, commands and control
152

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

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

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

    
184
    def terminate( self ):
185
        "Send kill signal to Node and clean up after it."
186
        os.kill( self.pid, signal.SIGKILL )
187
        self.cleanup()
188

    
189
    def stop( self ):
190
        "Stop node."
191
        self.terminate()
192

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

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

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

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

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

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

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

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

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

    
327
    # Interface management, configuration, and routing
328

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

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

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

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

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

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

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

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

    
409
    # Routing support
410

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

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

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

    
435
    # Convenience and configuration methods
436

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

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

    
453
    def IP( self, intf=None ):
454
        "Return IP address of a node or specific interface."
455
        return self.intf( intf ).IP()
456

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

    
461
    def intfIsUp( self, intf=None ):
462
        "Check if an interface is up."
463
        return self.intf( intf ).isUp()
464

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

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

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

    
509
    def configDefault( self, **moreParams ):
510
        "Configure with default parameters"
511
        self.params.update( moreParams )
512
        self.config( **self.params )
513

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

    
520
    # Other methods
521

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

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

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

    
537
    def __str__( self ):
538
        "Abbreviated string representation"
539
        return self.name
540

    
541
    # Automatic class setup support
542

    
543
    isSetup = False
544

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

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

    
559

    
560
class Host( Node ):
561
    "A host is simply a Node"
562
    pass
563

    
564

    
565
class CPULimitedHost( Host ):
566

    
567
    "CPU limited host"
568

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

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

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

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

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

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

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

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

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

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

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

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

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

    
717
    inited = False
718

    
719
    @classmethod
720
    def init( cls ):
721
        "Initialization for CPULimitedHost class"
722
        mountCgroups()
723
        cls.inited = True
724

    
725

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

    
746
class Switch( Node ):
747
    """A Switch is a Node that is running (or has execed?)
748
       an OpenFlow switch."""
749

    
750
    portBase = 1  # Switches start with port 1 in OpenFlow
751
    dpidLen = 16  # digits in dpid passed to switch
752

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

    
764
    def defaultDpid( self ):
765
        "Derive dpid from switch name, s1 -> 1"
766
        try:
767
            dpid = int( re.findall( r'\d+', self.name )[ 0 ] )
768
            dpid = hex( dpid )[ 2: ]
769
            dpid = '0' * ( self.dpidLen - len( dpid ) ) + dpid
770
            return dpid
771
        except IndexError:
772
            raise Exception( 'Unable to derive default datapath ID - '
773
                             'please either specify a dpid or use a '
774
                             'canonical switch name such as s23.' )
775

    
776
    def defaultIntf( self ):
777
        "Return control interface"
778
        if self.controlIntf:
779
            return self.controlIntf
780
        else:
781
            return Node.defaultIntf( self )
782

    
783
    def sendCmd( self, *cmd, **kwargs ):
784
        """Send command to Node.
785
           cmd: string"""
786
        kwargs.setdefault( 'printPid', False )
787
        if not self.execed:
788
            return Node.sendCmd( self, *cmd, **kwargs )
789
        else:
790
            error( '*** Error: %s has execed and cannot accept commands' %
791
                   self.name )
792

    
793
    def connected( self ):
794
        "Is the switch connected to a controller? (override this method)"
795
        return False and self  # satisfy pylint
796

    
797
    def __repr__( self ):
798
        "More informative string representation"
799
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
800
                              for i in self.intfList() ] ) )
801
        return '<%s %s: %s pid=%s> ' % (
802
            self.__class__.__name__, self.name, intfs, self.pid )
803

    
804
class UserSwitch( Switch ):
805
    "User-space switch."
806

    
807
    dpidLen = 12
808

    
809
    def __init__( self, name, dpopts='--no-slicing', **kwargs ):
810
        """Init.
811
           name: name for the switch
812
           dpopts: additional arguments to ofdatapath (--no-slicing)"""
813
        Switch.__init__( self, name, **kwargs )
814
        pathCheck( 'ofdatapath', 'ofprotocol',
815
                   moduleName='the OpenFlow reference user switch' +
816
                              '(openflow.org)' )
817
        if self.listenPort:
818
            self.opts += ' --listen=ptcp:%i ' % self.listenPort
819
        self.dpopts = dpopts
820

    
821
    @classmethod
822
    def setup( cls ):
823
        "Ensure any dependencies are loaded; if not, try to load them."
824
        if not os.path.exists( '/dev/net/tun' ):
825
            moduleDeps( add=TUN )
826

    
827
    def dpctl( self, *args ):
828
        "Run dpctl command"
829
        if not self.listenPort:
830
            return "can't run dpctl without passive listening port"
831
        return self.cmd( 'dpctl ' + ' '.join( args ) +
832
                         ' tcp:127.0.0.1:%i' % self.listenPort )
833

    
834
    def connected( self ):
835
        "Is the switch connected to a controller?"
836
        return 'remote.is-connected=true' in self.dpctl( 'status' )
837

    
838
    @staticmethod
839
    def TCReapply( intf ):
840
        """Unfortunately user switch and Mininet are fighting
841
           over tc queuing disciplines. To resolve the conflict,
842
           we re-create the user switch's configuration, but as a
843
           leaf of the TCIntf-created configuration."""
844
        if type( intf ) is TCIntf:
845
            ifspeed = 10000000000 # 10 Gbps
846
            minspeed = ifspeed * 0.001
847

    
848
            res = intf.config( **intf.params )
849
            parent = res['parent']
850

    
851
            # Re-add qdisc, root, and default classes user switch created, but
852
            # with new parent, as setup by Mininet's TCIntf
853
            intf.tc( "%s qdisc add dev %s " + parent +
854
                     " handle 1: htb default 0xfffe" )
855
            intf.tc( "%s class add dev %s classid 1:0xffff parent 1: htb rate "
856
                     + str(ifspeed) )
857
            intf.tc( "%s class add dev %s classid 1:0xfffe parent 1:0xffff " +
858
                     "htb rate " + str(minspeed) + " ceil " + str(ifspeed) )
859

    
860
    def start( self, controllers ):
861
        """Start OpenFlow reference user datapath.
862
           Log to /tmp/sN-{ofd,ofp}.log.
863
           controllers: list of controller objects"""
864
        # Add controllers
865
        clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
866
                            for c in controllers ] )
867
        ofdlog = '/tmp/' + self.name + '-ofd.log'
868
        ofplog = '/tmp/' + self.name + '-ofp.log'
869
        self.cmd( 'ifconfig lo up' )
870
        intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
871
        self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
872
                  ' punix:/tmp/' + self.name + ' -d %s ' % self.dpid +
873
                  self.dpopts +
874
                  ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
875
        self.cmd( 'ofprotocol unix:/tmp/' + self.name +
876
                  ' ' + clist +
877
                  ' --fail=closed ' + self.opts +
878
                  ' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
879
        if "no-slicing" not in self.dpopts:
880
            # Only TCReapply if slicing is enable
881
            sleep(1) # Allow ofdatapath to start before re-arranging qdisc's
882
            for intf in self.intfList():
883
                if not intf.IP():
884
                    self.TCReapply( intf )
885

    
886
    def stop( self ):
887
        "Stop OpenFlow reference user datapath."
888
        self.cmd( 'kill %ofdatapath' )
889
        self.cmd( 'kill %ofprotocol' )
890
        self.deleteIntfs()
891

    
892

    
893
class OVSLegacyKernelSwitch( Switch ):
894
    """Open VSwitch legacy kernel-space switch using ovs-openflowd.
895
       Currently only works in the root namespace."""
896

    
897
    def __init__( self, name, dp=None, **kwargs ):
898
        """Init.
899
           name: name for switch
900
           dp: netlink id (0, 1, 2, ...)
901
           defaultMAC: default MAC as unsigned int; random value if None"""
902
        Switch.__init__( self, name, **kwargs )
903
        self.dp = dp if dp else self.name
904
        self.intf = self.dp
905
        if self.inNamespace:
906
            error( "OVSKernelSwitch currently only works"
907
                   " in the root namespace.\n" )
908
            exit( 1 )
909

    
910
    @classmethod
911
    def setup( cls ):
912
        "Ensure any dependencies are loaded; if not, try to load them."
913
        pathCheck( 'ovs-dpctl', 'ovs-openflowd',
914
                   moduleName='Open vSwitch (openvswitch.org)')
915
        moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
916

    
917
    def start( self, controllers ):
918
        "Start up kernel datapath."
919
        ofplog = '/tmp/' + self.name + '-ofp.log'
920
        quietRun( 'ifconfig lo up' )
921
        # Delete local datapath if it exists;
922
        # then create a new one monitoring the given interfaces
923
        self.cmd( 'ovs-dpctl del-dp ' + self.dp )
924
        self.cmd( 'ovs-dpctl add-dp ' + self.dp )
925
        intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
926
        self.cmd( 'ovs-dpctl', 'add-if', self.dp, ' '.join( intfs ) )
927
        # Run protocol daemon
928
        clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
929
                            for c in controllers ] )
930
        self.cmd( 'ovs-openflowd ' + self.dp +
931
                  ' ' + clist +
932
                  ' --fail=secure ' + self.opts +
933
                  ' --datapath-id=' + self.dpid +
934
                  ' 1>' + ofplog + ' 2>' + ofplog + '&' )
935
        self.execed = False
936

    
937
    def stop( self ):
938
        "Terminate kernel datapath."
939
        quietRun( 'ovs-dpctl del-dp ' + self.dp )
940
        self.cmd( 'kill %ovs-openflowd' )
941
        self.deleteIntfs()
942

    
943

    
944
class OVSSwitch( Switch ):
945
    "Open vSwitch switch. Depends on ovs-vsctl."
946

    
947
    def __init__( self, name, failMode='secure', datapath='kernel', **params ):
948
        """Init.
949
           name: name for switch
950
           failMode: controller loss behavior (secure|open)
951
           datapath: userspace or kernel mode (kernel|user)"""
952
        Switch.__init__( self, name, **params )
953
        self.failMode = failMode
954
        self.datapath = datapath
955

    
956
    @classmethod
957
    def setup( cls ):
958
        "Make sure Open vSwitch is installed and working"
959
        pathCheck( 'ovs-vsctl',
960
                   moduleName='Open vSwitch (openvswitch.org)')
961
        # This should no longer be needed, and it breaks
962
        # with OVS 1.7 which has renamed the kernel module:
963
        #  moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
964
        out, err, exitcode = errRun( 'ovs-vsctl -t 1 show' )
965
        if exitcode:
966
            error( out + err +
967
                   'ovs-vsctl exited with code %d\n' % exitcode +
968
                   '*** Error connecting to ovs-db with ovs-vsctl\n'
969
                   'Make sure that Open vSwitch is installed, '
970
                   'that ovsdb-server is running, and that\n'
971
                   '"ovs-vsctl show" works correctly.\n'
972
                   'You may wish to try '
973
                   '"service openvswitch-switch start".\n' )
974
            exit( 1 )
975

    
976
    def dpctl( self, *args ):
977
        "Run ovs-ofctl command"
978
        return self.cmd( 'ovs-ofctl', args[ 0 ], self, *args[ 1: ] )
979

    
980
    @staticmethod
981
    def TCReapply( intf ):
982
        """Unfortunately OVS and Mininet are fighting
983
           over tc queuing disciplines. As a quick hack/
984
           workaround, we clear OVS's and reapply our own."""
985
        if type( intf ) is TCIntf:
986
            intf.config( **intf.params )
987

    
988
    def attach( self, intf ):
989
        "Connect a data port"
990
        self.cmd( 'ovs-vsctl add-port', self, intf )
991
        self.cmd( 'ifconfig', intf, 'up' )
992
        self.TCReapply( intf )
993

    
994
    def detach( self, intf ):
995
        "Disconnect a data port"
996
        self.cmd( 'ovs-vsctl del-port', self, intf )
997

    
998
    def controllerUUIDs( self ):
999
        "Return ovsdb UUIDs for our controllers"
1000
        uuids = []
1001
        controllers = self.cmd( 'ovs-vsctl -- get Bridge', self,
1002
                               'Controller' ).strip()
1003
        if controllers.startswith( '[' ) and controllers.endswith( ']' ):
1004
            controllers = controllers[ 1 : -1 ]
1005
            uuids = [ c.strip() for c in controllers.split( ',' ) ]
1006
        return uuids
1007

    
1008
    def connected( self ):
1009
        "Are we connected to at least one of our controllers?"
1010
        results = [ 'true' in self.cmd( 'ovs-vsctl -- get Controller',
1011
                                         uuid, 'is_connected' )
1012
                    for uuid in self.controllerUUIDs() ]
1013
        return reduce( or_, results, False )
1014

    
1015
    def start( self, controllers ):
1016
        "Start up a new OVS OpenFlow switch using ovs-vsctl"
1017
        if self.inNamespace:
1018
            raise Exception(
1019
                'OVS kernel switch does not work in a namespace' )
1020
        # We should probably call config instead, but this
1021
        # requires some rethinking...
1022
        self.cmd( 'ifconfig lo up' )
1023
        # Annoyingly, --if-exists option seems not to work
1024
        self.cmd( 'ovs-vsctl del-br', self )
1025
        self.cmd( 'ovs-vsctl add-br', self )
1026
        if self.datapath == 'user':
1027
            self.cmd( 'ovs-vsctl set bridge', self,'datapath_type=netdev' )
1028
        int( self.dpid, 16 ) # DPID must be a hex string
1029
        self.cmd( 'ovs-vsctl -- set Bridge', self,
1030
                  'other_config:datapath-id=' + self.dpid )
1031
        self.cmd( 'ovs-vsctl set-fail-mode', self, self.failMode )
1032
        for intf in self.intfList():
1033
            if not intf.IP():
1034
                self.attach( intf )
1035
        # Add controllers
1036
        clist = ' '.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
1037
                            for c in controllers ] )
1038
        if self.listenPort:
1039
            clist += ' ptcp:%s' % self.listenPort
1040
        self.cmd( 'ovs-vsctl set-controller', self, clist )
1041
        # Reconnect quickly to controllers (1s vs. 15s max_backoff)
1042
        for uuid in self.controllerUUIDs():
1043
            if uuid.count( '-' ) != 4:
1044
                # Doesn't look like a UUID
1045
                continue
1046
            uuid = uuid.strip()
1047
            self.cmd( 'ovs-vsctl set Controller', uuid,
1048
                      'max_backoff=1000' )
1049

    
1050
    def stop( self ):
1051
        "Terminate OVS switch."
1052
        self.cmd( 'ovs-vsctl del-br', self )
1053
        self.deleteIntfs()
1054

    
1055
OVSKernelSwitch = OVSSwitch
1056

    
1057

    
1058
class IVSSwitch(Switch):
1059
    """IVS virtual switch"""
1060

    
1061
    def __init__( self, name, **kwargs ):
1062
        Switch.__init__( self, name, **kwargs )
1063

    
1064
    @classmethod
1065
    def setup( cls ):
1066
        "Make sure IVS is installed"
1067
        pathCheck( 'ivs-ctl', 'ivs',
1068
                   moduleName="Indigo Virtual Switch (projectfloodlight.org)" )
1069
        out, err, exitcode = errRun( 'ivs-ctl show' )
1070
        if exitcode:
1071
            error( out + err +
1072
                   'ivs-ctl exited with code %d\n' % exitcode +
1073
                   '*** The openvswitch kernel module might '
1074
                   'not be loaded. Try modprobe openvswitch.\n' )
1075
            exit( 1 )
1076

    
1077
    def start( self, controllers ):
1078
        "Start up a new IVS switch"
1079
        args = ['ivs']
1080
        args.extend( ['--name', self.name] )
1081
        args.extend( ['--dpid', self.dpid] )
1082
        args.extend( ['--verbose'] )
1083
        for intf in self.intfs.values():
1084
            if not intf.IP():
1085
                args.extend( ['-i', intf.name] )
1086
        for c in controllers:
1087
            args.extend( ['-c', '%s:%d' % (c.IP(), c.port)] )
1088
        if self.listenPort:
1089
            args.extend( ['--listen', '127.0.0.1:%i' % self.listenPort] )
1090

    
1091
        logfile = '/tmp/ivs.%s.log' % self.name
1092

    
1093
        self.cmd( ' '.join(args) + ' >' + logfile + ' 2>&1 </dev/null &' )
1094

    
1095
    def stop( self ):
1096
        "Terminate IVS switch."
1097
        self.cmd( 'kill %ivs' )
1098
        self.deleteIntfs()
1099

    
1100
    def attach( self, intf ):
1101
        "Connect a data port"
1102
        self.cmd( 'ivs-ctl', 'add-port', '--datapath', self.name, intf )
1103

    
1104
    def detach( self, intf ):
1105
        "Disconnect a data port"
1106
        self.cmd( 'ivs-ctl', 'del-port', '--datapath', self.name, intf )
1107

    
1108
    def dpctl( self, *args ):
1109
        "Run dpctl command"
1110
        if not self.listenPort:
1111
            return "can't run dpctl without passive listening port"
1112
        return self.cmd( 'ovs-ofctl ' + ' '.join( args ) +
1113
                         ' tcp:127.0.0.1:%i' % self.listenPort )
1114

    
1115

    
1116
class Controller( Node ):
1117
    """A Controller is a Node that is running (or has execed?) an
1118
       OpenFlow controller."""
1119

    
1120
    def __init__( self, name, inNamespace=False, command='controller',
1121
                  cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
1122
                  port=6633, **params ):
1123
        self.command = command
1124
        self.cargs = cargs
1125
        self.cdir = cdir
1126
        self.ip = ip
1127
        self.port = port
1128
        Node.__init__( self, name, inNamespace=inNamespace,
1129
                       ip=ip, **params  )
1130
        self.cmd( 'ifconfig lo up' )  # Shouldn't be necessary
1131
        self.checkListening()
1132

    
1133
    def checkListening( self ):
1134
        "Make sure no controllers are running on our port"
1135
        # Verify that Telnet is installed first:
1136
        out, _err, returnCode = errRun( "which telnet" )
1137
        if 'telnet' not in out or returnCode != 0:
1138
            raise Exception( "Error running telnet to check for listening "
1139
                             "controllers; please check that it is "
1140
                             "installed." )
1141
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1142
                              ( self.ip, self.port ) )
1143
        if 'Unable' not in listening:
1144
            servers = self.cmd( 'netstat -atp' ).split( '\n' )
1145
            pstr = ':%d ' % self.port
1146
            clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ]
1147
            raise Exception( "Please shut down the controller which is"
1148
                             " running on port %d:\n" % self.port +
1149
                             '\n'.join( clist ) )
1150

    
1151
    def start( self ):
1152
        """Start <controller> <args> on controller.
1153
           Log to /tmp/cN.log"""
1154
        pathCheck( self.command )
1155
        cout = '/tmp/' + self.name + '.log'
1156
        if self.cdir is not None:
1157
            self.cmd( 'cd ' + self.cdir )
1158
        self.cmd( self.command + ' ' + self.cargs % self.port +
1159
                  ' 1>' + cout + ' 2>' + cout + '&' )
1160
        self.execed = False
1161

    
1162
    def stop( self ):
1163
        "Stop controller."
1164
        self.cmd( 'kill %' + self.command )
1165
        self.terminate()
1166

    
1167
    def IP( self, intf=None ):
1168
        "Return IP address of the Controller"
1169
        if self.intfs:
1170
            ip = Node.IP( self, intf )
1171
        else:
1172
            ip = self.ip
1173
        return ip
1174

    
1175
    def __repr__( self ):
1176
        "More informative string representation"
1177
        return '<%s %s: %s:%s pid=%s> ' % (
1178
            self.__class__.__name__, self.name,
1179
            self.IP(), self.port, self.pid )
1180

    
1181

    
1182
class OVSController( Controller ):
1183
    "Open vSwitch controller"
1184
    def __init__( self, name, command='ovs-controller', **kwargs ):
1185
        Controller.__init__( self, name, command=command, **kwargs )
1186

    
1187

    
1188
class NOX( Controller ):
1189
    "Controller to run a NOX application."
1190

    
1191
    def __init__( self, name, *noxArgs, **kwargs ):
1192
        """Init.
1193
           name: name to give controller
1194
           noxArgs: arguments (strings) to pass to NOX"""
1195
        if not noxArgs:
1196
            warn( 'warning: no NOX modules specified; '
1197
                  'running packetdump only\n' )
1198
            noxArgs = [ 'packetdump' ]
1199
        elif type( noxArgs ) not in ( list, tuple ):
1200
            noxArgs = [ noxArgs ]
1201

    
1202
        if 'NOX_CORE_DIR' not in os.environ:
1203
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
1204
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
1205

    
1206
        Controller.__init__( self, name,
1207
                             command=noxCoreDir + '/nox_core',
1208
                             cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
1209
                             ' '.join( noxArgs ),
1210
                             cdir=noxCoreDir,
1211
                             **kwargs )
1212

    
1213

    
1214
class RemoteController( Controller ):
1215
    "Controller running outside of Mininet's control."
1216

    
1217
    def __init__( self, name, ip='127.0.0.1',
1218
                  port=6633, **kwargs):
1219
        """Init.
1220
           name: name to give controller
1221
           ip: the IP address where the remote controller is
1222
           listening
1223
           port: the port where the remote controller is listening"""
1224
        Controller.__init__( self, name, ip=ip, port=port, **kwargs )
1225

    
1226
    def start( self ):
1227
        "Overridden to do nothing."
1228
        return
1229

    
1230
    def stop( self ):
1231
        "Overridden to do nothing."
1232
        return
1233

    
1234
    def checkListening( self ):
1235
        "Warn if remote controller is not accessible"
1236
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1237
                              ( self.ip, self.port ) )
1238
        if 'Unable' in listening:
1239
            warn( "Unable to contact the remote controller"
1240
                  " at %s:%d\n" % ( self.ip, self.port ) )