Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ c6e7eaf0

History | View | Annotate | Download (22.3 KB)

1
"""
2
Node objects for Mininet.
3

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

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

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

16
Switch: superclass for switch nodes.
17

18
UserSwitch: a switch using the user-space switch from the OpenFlow
19
    reference implementation.
20

21
KernelSwitch: a switch using the kernel switch from the OpenFlow reference
22
    implementation.
23

24
OVSSwitch: a switch using the OpenVSwitch OpenFlow-compatible switch
25
    implementation (openvswitch.org).
26

27
Controller: superclass for OpenFlow controllers. The default controller
28
    is controller(8) from the reference implementation.
29

30
NOXController: a controller node using NOX (noxrepo.org).
31

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

36
Future enhancements:
37

38
- Possibly make Node, Switch and Controller more abstract so that
39
  they can be used for both local and remote nodes
40

41
- Create proxy objects for remote nodes (Mininet: Cluster Edition)
42
"""
43

    
44
import os
45
import re
46
import signal
47
import select
48
from subprocess import Popen, PIPE, STDOUT
49
from time import sleep
50

    
51
from mininet.log import info, error, debug
52
from mininet.util import quietRun, makeIntfPair, moveIntf, isShellBuiltin
53
from mininet.moduledeps import moduleDeps, OVS_KMOD, OF_KMOD, TUN
54

    
55
class Node( object ):
56
    """A virtual network node is simply a shell in a network namespace.
57
       We communicate with it using pipes."""
58

    
59
    inToNode = {}  # mapping of input fds to nodes
60
    outToNode = {}  # mapping of output fds to nodes
61

    
62
    def __init__( self, name, inNamespace=True,
63
        defaultMAC=None, defaultIP=None ):
64
        """name: name of node
65
           inNamespace: in network namespace?
66
           defaultMAC: default MAC address for intf 0
67
           defaultIP: default IP address for intf 0"""
68
        self.name = name
69
        opts = '-cdp'
70
        self.inNamespace = inNamespace
71
        if self.inNamespace:
72
            opts += 'n'
73
        cmd = [ 'mnexec', opts, 'bash', '-m' ]
74
        self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT,
75
            close_fds=False )
76
        self.stdin = self.shell.stdin
77
        self.stdout = self.shell.stdout
78
        self.pollOut = select.poll()
79
        self.pollOut.register( self.stdout )
80
        # Maintain mapping between file descriptors and nodes
81
        # This is useful for monitoring multiple nodes
82
        # using select.poll()
83
        self.outToNode[ self.stdout.fileno() ] = self
84
        self.inToNode[ self.stdin.fileno() ] = self
85
        self.pid = self.shell.pid
86
        self.intfs = {}  # dict of port numbers to interface names
87
        self.ports = {}  # dict of interface names to port numbers
88
                         # replace with Port objects, eventually ?
89
        self.ips = {}  # dict of interfaces to ip addresses as strings
90
        self.connection = {}  # remote node connected to each interface
91
        self.execed = False
92
        self.defaultIP = defaultIP
93
        self.defaultMAC = defaultMAC
94
        self.lastCmd = None
95
        self.lastPid = None
96
        self.readbuf = ''
97
        self.waiting = False
98

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

    
107
    def cleanup( self ):
108
        "Help python collect its garbage."
109
        self.shell = None
110

    
111
    # Subshell I/O, commands and control
112
    def read( self, bytes=1024 ):
113
        """Buffered read from node, non-blocking.
114
           bytes: maximum number of bytes to return"""
115
        count = len( self.readbuf )
116
        if count < bytes:
117
            data = os.read( self.stdout.fileno(), bytes - count )
118
            self.readbuf += data
119
        if bytes >= len( self.readbuf ):
120
            result = self.readbuf
121
            self.readbuf = ''
122
        else:
123
            result = self.readbuf[ :bytes ]
124
            self.readbuf = self.readbuf[ bytes: ]
125
        return result
126

    
127
    def readline( self ):
128
        """Buffered readline from node, non-blocking.
129
           returns: line (minus newline) or None"""
130
        self.readbuf += self.read( 1024 )
131
        if '\n' not in self.readbuf:
132
            return None
133
        pos = self.readbuf.find( '\n' )
134
        line = self.readbuf[ 0 : pos ]
135
        self.readbuf = self.readbuf[ pos + 1: ]
136
        return line
137

    
138
    def write( self, data ):
139
        """Write data to node.
140
           data: string"""
141
        os.write( self.stdin.fileno(), data )
142

    
143
    def terminate( self ):
144
        "Send kill signal to Node and clean up after it."
145
        os.kill( self.pid, signal.SIGKILL )
146
        self.cleanup()
147

    
148
    def stop( self ):
149
        "Stop node."
150
        self.terminate()
151

    
152
    def waitReadable( self ):
153
        "Wait until node's output is readable."
154
        if len( self.readbuf ) == 0:
155
            self.pollOut.poll()
156

    
157
    def sendCmd( self, cmd, printPid=True ):
158
        """Send a command, followed by a command to echo a sentinel,
159
           and return without waiting for the command to complete."""
160
        assert not self.waiting
161
        if isinstance( cmd, list ):
162
            cmd = ' '.join( cmd )
163
        if len( cmd ) > 0 and cmd[ -1 ] == '&':
164
            separator = '&'
165
            cmd = cmd[ :-1 ]
166
        else:
167
            separator = ';'
168
            if printPid and not isShellBuiltin( cmd ):
169
                cmd = 'mnexec -p ' + cmd
170
        self.write( cmd + separator + ' printf "\\177" \n' )
171
        self.lastCmd = cmd
172
        self.lastPid = None
173
        self.waiting = True
174

    
175
    def sendInt( self, sig=signal.SIGINT ):
176
        "Interrupt running command."
177
        if self.lastPid:
178
            try:
179
                os.kill( self.lastPid, sig )
180
            except e, Exception:
181
                pass
182

    
183
    def monitor( self ):
184
        """Monitor and return the output of a command.
185
           Set self.waiting to False if command has completed."""
186
        assert self.waiting
187
        self.waitReadable()
188
        data = self.read( 1024 )
189
        # Look for PID
190
        marker = chr( 1 ) + r'\d+\n'
191
        if chr( 1 ) in data:
192
            markers = re.findall( marker, data )
193
            if markers:
194
                self.lastPid = int( markers[ 0 ][ 1: ] )
195
                data = re.sub( marker, '', data )
196
        # Look for sentinel/EOF
197
        if len( data ) > 0 and data[ -1 ] == chr( 127 ):
198
            self.waiting = False
199
            data = data[ :-1 ]
200
        elif chr( 127 ) in data:
201
            self.waiting = False
202
            data = data.replace( chr( 127 ), '' )
203
        return data
204

    
205
    def waitOutput( self, verbose=False ):
206
        """Wait for a command to complete.
207
           Completion is signaled by a sentinel character, ASCII(127)
208
           appearing in the output stream.  Wait for the sentinel and return
209
           the output, including trailing newline.
210
           verbose: print output interactively"""
211
        log = info if verbose else debug
212
        output = ''
213
        while self.waiting:
214
            data = self.monitor()
215
            output += data
216
            log( data )
217
        return output
218

    
219
    def cmd( self, cmd, verbose=False ):
220
        """Send a command, wait for output, and return it.
221
           cmd: string"""
222
        log = info if verbose else debug
223
        log( '*** %s : %s\n' % ( self.name, cmd ) )
224
        self.sendCmd( cmd )
225
        return self.waitOutput( verbose )
226

    
227
    def cmdPrint( self, cmd ):
228
        """Call cmd and printing its output
229
           cmd: string"""
230
        return self.cmd( cmd, verbose=True )
231

    
232
    # Interface management, configuration, and routing
233

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

    
240
    def intfName( self, n ):
241
        "Construct a canonical interface name node-ethN for interface n."
242
        return self.name + '-eth' + repr( n )
243

    
244
    def newPort( self ):
245
        "Return the next port number to allocate."
246
        if len( self.ports ) > 0:
247
            return max( self.ports.values() ) + 1
248
        return 0
249

    
250
    def addIntf( self, intf, port ):
251
        """Add an interface.
252
           intf: interface name (nodeN-ethM)
253
           port: port number (typically OpenFlow port number)"""
254
        self.intfs[ port ] = intf
255
        self.ports[ intf ] = port
256
        #info( '\n' )
257
        #info( 'added intf %s:%d to node %s\n' % ( intf,port, self.name ) )
258
        if self.inNamespace:
259
            #info( 'moving w/inNamespace set\n' )
260
            moveIntf( intf, self )
261

    
262
    def registerIntf( self, intf, dstNode, dstIntf ):
263
        "Register connection of intf to dstIntf on dstNode."
264
        self.connection[ intf ] = ( dstNode, dstIntf )
265

    
266
    def connectionsTo( self, node):
267
        "Return [(srcIntf, dstIntf)..] for connections to dstNode."
268
        # We could optimize this if it is important
269
        connections = []
270
        for intf in self.connection.keys():
271
            dstNode, dstIntf = self.connection[ intf ]
272
            if dstNode == node:
273
                connections.append( ( intf, dstIntf ) )
274
        return connections
275

    
276
    # This is a symmetric operation, but it makes sense to put
277
    # the code here since it is tightly coupled to routines in
278
    # this class. For a more symmetric API, you can use
279
    # mininet.util.createLink()
280

    
281
    def linkTo( self, node2, port1=None, port2=None ):
282
        """Create link to another node, making two new interfaces.
283
           node2: Node to link us to
284
           port1: our port number (optional)
285
           port2: node2 port number (optional)
286
           returns: intf1 name, intf2 name"""
287
        node1 = self
288
        if port1 is None:
289
            port1 = node1.newPort()
290
        if port2 is None:
291
            port2 = node2.newPort()
292
        intf1 = node1.intfName( port1 )
293
        intf2 = node2.intfName( port2 )
294
        makeIntfPair( intf1, intf2 )
295
        node1.addIntf( intf1, port1 )
296
        node2.addIntf( intf2, port2 )
297
        node1.registerIntf( intf1, node2, intf2 )
298
        node2.registerIntf( intf2, node1, intf1 )
299
        return intf1, intf2
300

    
301
    def deleteIntfs( self ):
302
        "Delete all of our interfaces."
303
        # In theory the interfaces should go away after we shut down.
304
        # However, this takes time, so we're better off removing them
305
        # explicitly so that we won't get errors if we run before they
306
        # have been removed by the kernel. Unfortunately this is very slow,
307
        # at least with Linux kernels before 2.6.33
308
        for intf in self.intfs.values():
309
            quietRun( 'ip link del ' + intf )
310
            info( '.' )
311
            # Does it help to sleep to let things run?
312
            sleep( 0.001 )
313

    
314
    def setMAC( self, intf, mac ):
315
        """Set the MAC address for an interface.
316
           mac: MAC address as string"""
317
        result = self.cmd( [ 'ifconfig', intf, 'down' ] )
318
        result += self.cmd( [ 'ifconfig', intf, 'hw', 'ether', mac ] )
319
        result += self.cmd( [ 'ifconfig', intf, 'up' ] )
320
        return result
321

    
322
    def setARP( self, ip, mac ):
323
        """Add an ARP entry.
324
           ip: IP address as string
325
           mac: MAC address as string"""
326
        result = self.cmd( [ 'arp', '-s', ip, mac ] )
327
        return result
328

    
329
    def setIP( self, intf, ip, prefixLen=8 ):
330
        """Set the IP address for an interface.
331
           intf: interface name
332
           ip: IP address as a string
333
           prefixLen: prefix length, e.g. 8 for /8 or 16M addrs"""
334
        ipSub = '%s/%d' % ( ip, prefixLen )
335
        result = self.cmd( [ 'ifconfig', intf, ipSub, 'up' ] )
336
        self.ips[ intf ] = ip
337
        return result
338

    
339
    def setHostRoute( self, ip, intf ):
340
        """Add route to host.
341
           ip: IP address as dotted decimal
342
           intf: string, interface name"""
343
        return self.cmd( 'route add -host ' + ip + ' dev ' + intf )
344

    
345
    def setDefaultRoute( self, intf ):
346
        """Set the default route to go through intf.
347
           intf: string, interface name"""
348
        self.cmd( 'ip route flush root 0/0' )
349
        return self.cmd( 'route add default ' + intf )
350

    
351
    def IP( self, intf=None ):
352
        "Return IP address of a node or specific interface."
353
        if len( self.ips ) == 1:
354
            return self.ips.values()[ 0 ]
355
        if intf:
356
            return self.ips.get( intf, None )
357

    
358
    def MAC( self, intf=None ):
359
        "Return MAC address of a node or specific interface."
360
        if intf is None and len( self.intfs ) == 1:
361
            intf = self.intfs.values()[ 0 ]
362
        ifconfig = self.cmd( 'ifconfig ' + intf )
363
        macs = re.findall( '..:..:..:..:..:..', ifconfig )
364
        if len( macs ) > 0:
365
            return macs[ 0 ]
366

    
367
    def intfIsUp( self, intf ):
368
        "Check if an interface is up."
369
        return 'UP' in self.cmd( 'ifconfig ' + intf )
370

    
371
    # Other methods
372
    def __str__( self ):
373
        result = self.name + ':'
374
        result += ' IP=' + str( self.IP() )
375
        result += ' intfs=' + ','.join( sorted( self.intfs.values() ) )
376
        result += ' waiting=' + str( self.waiting )
377
        result += ' pid=' + str( self.pid )
378
        return result
379

    
380

    
381
class Host( Node ):
382
    "A host is simply a Node."
383

    
384

    
385
class Switch( Node ):
386
    """A Switch is a Node that is running (or has execed?)
387
       an OpenFlow switch."""
388

    
389
    def sendCmd( self, cmd, printPid=False):
390
        """Send command to Node.
391
           cmd: string"""
392
        if not self.execed:
393
            return Node.sendCmd( self, cmd, printPid )
394
        else:
395
            error( '*** Error: %s has execed and cannot accept commands' %
396
                     self.name )
397

    
398
    def monitor( self ):
399
        "Monitor node."
400
        if not self.execed:
401
            return Node.monitor( self )
402
        else:
403
            return True, ''
404

    
405

    
406
class UserSwitch( Switch ):
407
    "User-space switch."
408

    
409
    def __init__( self, name, **kwargs ):
410
        """Init.
411
           name: name for the switch"""
412
        Switch.__init__( self, name, **kwargs )
413

    
414
    @staticmethod
415
    def setup():
416
        "Ensure any dependencies are loaded; if not, try to load them."
417
        moduleDeps( add = TUN )
418

    
419
    def start( self, controllers ):
420
        """Start OpenFlow reference user datapath.
421
           Log to /tmp/sN-{ofd,ofp}.log.
422
           controllers: list of controller objects"""
423
        controller = controllers[ 0 ]
424
        ofdlog = '/tmp/' + self.name + '-ofd.log'
425
        ofplog = '/tmp/' + self.name + '-ofp.log'
426
        self.cmd( 'ifconfig lo up' )
427
        intfs = sorted( self.intfs.values() )
428
        if self.inNamespace:
429
            intfs = intfs[ :-1 ]
430
        self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
431
            ' punix:/tmp/' + self.name +
432
            ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
433
        self.cmd( 'ofprotocol unix:/tmp/' + self.name +
434
            ' tcp:' + controller.IP() + ' --fail=closed' +
435
            ' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
436

    
437
    def stop( self ):
438
        "Stop OpenFlow reference user datapath."
439
        self.cmd( 'kill %ofdatapath' )
440
        self.cmd( 'kill %ofprotocol' )
441
        self.deleteIntfs()
442

    
443
class KernelSwitch( Switch ):
444
    """Kernel-space switch.
445
       Currently only works in root namespace."""
446

    
447
    def __init__( self, name, dp=None, **kwargs ):
448
        """Init.
449
           name: name for switch
450
           dp: netlink id (0, 1, 2, ...)
451
           defaultMAC: default MAC as string; random value if None"""
452
        Switch.__init__( self, name, **kwargs )
453
        self.dp = dp
454
        if self.inNamespace:
455
            error( "KernelSwitch currently only works"
456
                " in the root namespace." )
457
            exit( 1 )
458

    
459
    @staticmethod
460
    def setup():
461
        "Ensure any dependencies are loaded; if not, try to load them."
462
        moduleDeps( subtract = OVS_KMOD, add = OF_KMOD )
463

    
464
    def start( self, controllers ):
465
        "Start up reference kernel datapath."
466
        ofplog = '/tmp/' + self.name + '-ofp.log'
467
        quietRun( 'ifconfig lo up' )
468
        # Delete local datapath if it exists;
469
        # then create a new one monitoring the given interfaces
470
        quietRun( 'dpctl deldp nl:%i' % self.dp )
471
        self.cmd( 'dpctl adddp nl:%i' % self.dp )
472
        if self.defaultMAC:
473
            intf = 'of%i' % self.dp
474
            self.cmd( [ 'ifconfig', intf, 'hw', 'ether', self.defaultMAC ] )
475
        if len( self.intfs ) != max( self.intfs ) + 1:
476
            raise Exception( 'only contiguous, zero-indexed port ranges'
477
                            'supported: %s' % self.intfs )
478
        intfs = [ self.intfs[ port ] for port in sorted( self.intfs.keys() ) ]
479
        self.cmd( 'dpctl addif nl:' + str( self.dp ) + ' ' +
480
            ' '.join( intfs ) )
481
        # Run protocol daemon
482
        controller = controllers[ 0 ]
483
        self.cmd( 'ofprotocol nl:' + str( self.dp ) + ' tcp:' +
484
                      controller.IP() + ':' +
485
                      str( controller.port ) +
486
                      ' --fail=closed 1> ' + ofplog + ' 2>' + ofplog + ' &' )
487
        self.execed = False
488

    
489
    def stop( self ):
490
        "Terminate kernel datapath."
491
        quietRun( 'dpctl deldp nl:%i' % self.dp )
492
        self.cmd( 'kill %ofprotocol' )
493
        self.deleteIntfs()
494

    
495

    
496
class OVSKernelSwitch( Switch ):
497
    """Open VSwitch kernel-space switch.
498
       Currently only works in the root namespace."""
499

    
500
    def __init__( self, name, dp=None, **kwargs ):
501
        """Init.
502
           name: name for switch
503
           dp: netlink id (0, 1, 2, ...)
504
           defaultMAC: default MAC as unsigned int; random value if None"""
505
        Switch.__init__( self, name, **kwargs )
506
        self.dp = dp
507
        if self.inNamespace:
508
            error( "OVSKernelSwitch currently only works"
509
                " in the root namespace." )
510
            exit( 1 )
511

    
512
    @staticmethod
513
    def setup():
514
        "Ensure any dependencies are loaded; if not, try to load them."
515
        moduleDeps( subtract = OF_KMOD, add = OVS_KMOD )
516

    
517
    def start( self, controllers ):
518
        "Start up kernel datapath."
519
        ofplog = '/tmp/' + self.name + '-ofp.log'
520
        quietRun( 'ifconfig lo up' )
521
        # Delete local datapath if it exists;
522
        # then create a new one monitoring the given interfaces
523
        quietRun( 'ovs-dpctl del-dp dp%i' % self.dp )
524
        self.cmd( 'ovs-dpctl add-dp dp%i' % self.dp )
525
        if self.defaultMAC:
526
            intf = 'dp%i' % self.dp
527
            mac = self.defaultMAC
528
            self.cmd( [ 'ifconfig', intf, 'hw', 'ether', mac ] )
529

    
530
        if len( self.intfs ) != max( self.intfs ) + 1:
531
            raise Exception( 'only contiguous, zero-indexed port ranges'
532
                            'supported: %s' % self.intfs )
533
        intfs = [ self.intfs[ port ] for port in sorted( self.intfs.keys() ) ]
534
        self.cmd( 'ovs-dpctl add-if dp' + str( self.dp ) + ' ' +
535
                      ' '.join( intfs ) )
536
        # Run protocol daemon
537
        controller = controllers[ 0 ]
538
        self.cmd( 'ovs-openflowd dp' + str( self.dp ) + ' tcp:' +
539
                      controller.IP() + ':' +
540
                      ' --fail=closed 1> ' + ofplog + ' 2>' + ofplog + ' &' )
541
        self.execed = False
542

    
543
    def stop( self ):
544
        "Terminate kernel datapath."
545
        quietRun( 'ovs-dpctl del-dp dp%i' % self.dp )
546
        self.cmd( 'kill %ovs-openflowd' )
547
        self.deleteIntfs()
548

    
549

    
550
class Controller( Node ):
551
    """A Controller is a Node that is running (or has execed?) an
552
       OpenFlow controller."""
553

    
554
    def __init__( self, name, inNamespace=False, controller='controller',
555
                 cargs='-v ptcp:', cdir=None, defaultIP="127.0.0.1",
556
                 port=6633 ):
557
        self.controller = controller
558
        self.cargs = cargs
559
        self.cdir = cdir
560
        self.port = port
561
        Node.__init__( self, name, inNamespace=inNamespace,
562
            defaultIP=defaultIP )
563

    
564
    def start( self ):
565
        """Start <controller> <args> on controller.
566
           Log to /tmp/cN.log"""
567
        cout = '/tmp/' + self.name + '.log'
568
        if self.cdir is not None:
569
            self.cmd( 'cd ' + self.cdir )
570
        self.cmd( self.controller + ' ' + self.cargs +
571
            ' 1> ' + cout + ' 2> ' + cout + ' &' )
572
        self.execed = False
573

    
574
    def stop( self ):
575
        "Stop controller."
576
        self.cmd( 'kill %' + self.controller )
577
        self.terminate()
578

    
579
    def IP( self, intf=None ):
580
        "Return IP address of the Controller"
581
        ip = Node.IP( self, intf=intf )
582
        if ip is None:
583
            ip = self.defaultIP
584
        return ip
585

    
586
class ControllerParams( object ):
587
    "Container for controller IP parameters."
588

    
589
    def __init__( self, ip, prefixLen ):
590
        """Init.
591
           ip: string, controller IP address
592
           prefixLen: prefix length, e.g. 8 for /8, covering 16M"""
593
        self.ip = ip
594
        self.prefixLen = prefixLen
595

    
596

    
597
class NOX( Controller ):
598
    "Controller to run a NOX application."
599

    
600
    def __init__( self, name, noxArgs=None, **kwargs ):
601
        """Init.
602
           name: name to give controller
603
           noxArgs: list of args, or single arg, to pass to NOX"""
604
        if not noxArgs:
605
            noxArgs = [ 'packetdump' ]
606
        elif type( noxArgs ) != list:
607
            noxArgs = [ noxArgs ]
608

    
609
        if 'NOX_CORE_DIR' not in os.environ:
610
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
611
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
612

    
613
        Controller.__init__( self, name,
614
            controller=noxCoreDir + '/nox_core',
615
            cargs='--libdir=/usr/local/lib -v -i ptcp: ' +
616
                    ' '.join( noxArgs ),
617
            cdir = noxCoreDir, **kwargs )
618

    
619

    
620
class RemoteController( Controller ):
621
    "Controller running outside of Mininet's control."
622

    
623
    def __init__( self, name, defaultIP='127.0.0.1',
624
                 port=6633, **kwargs):
625
        """Init.
626
           name: name to give controller
627
           defaultIP: the IP address where the remote controller is
628
           listening
629
           port: the port where the remote controller is listening"""
630
        Controller.__init__( self, name, defaultIP=defaultIP, port=port,
631
            **kwargs )
632

    
633
    def start( self ):
634
        "Overridden to do nothing."
635
        return
636

    
637
    def stop( self ):
638
        "Overridden to do nothing."
639
        return