Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 93cd5583

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

    
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
        if self.shell:
187
            os.killpg( self.pid, signal.SIGKILL )
188
        self.cleanup()
189

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

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

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

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

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

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

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

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

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

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

    
328
    # Interface management, configuration, and routing
329

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

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

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

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

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

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

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

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

    
410
    # Routing support
411

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

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

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

    
436
    # Convenience and configuration methods
437

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

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

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

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

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

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

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

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

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

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

    
521
    # Other methods
522

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

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

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

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

    
542
    # Automatic class setup support
543

    
544
    isSetup = False
545

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

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

    
560

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

    
565

    
566
class CPULimitedHost( Host ):
567

    
568
    "CPU limited host"
569

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

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

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

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

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

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

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

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

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

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

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

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

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

    
718
    inited = False
719

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

    
726

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

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

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

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

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

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

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

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

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

    
810
class UserSwitch( Switch ):
811
    "User-space switch."
812

    
813
    dpidLen = 12
814

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

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

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

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

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

    
857
            res = intf.config( **intf.params )
858

    
859
            if res is None: # link may not have TC parameters
860
                return
861

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

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

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

    
904

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

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

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

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

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

    
955

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

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

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

    
991
    @classmethod
992
    def batchShutdown( cls, switches ):
993
        "Call ovs-vsctl del-br on all OVSSwitches in a list"
994
        quietRun( 'ovs-vsctl ' +
995
                  ' -- '.join( '--if-exists del-br %s' % s
996
                               for s in switches if type(s) == cls ) )
997

    
998
    def dpctl( self, *args ):
999
        "Run ovs-ofctl command"
1000
        return self.cmd( 'ovs-ofctl', args[ 0 ], self, *args[ 1: ] )
1001

    
1002
    @staticmethod
1003
    def TCReapply( intf ):
1004
        """Unfortunately OVS and Mininet are fighting
1005
           over tc queuing disciplines. As a quick hack/
1006
           workaround, we clear OVS's and reapply our own."""
1007
        if type( intf ) is TCIntf:
1008
            intf.config( **intf.params )
1009

    
1010
    def attach( self, intf ):
1011
        "Connect a data port"
1012
        self.cmd( 'ovs-vsctl add-port', self, intf )
1013
        self.cmd( 'ifconfig', intf, 'up' )
1014
        self.TCReapply( intf )
1015

    
1016
    def detach( self, intf ):
1017
        "Disconnect a data port"
1018
        self.cmd( 'ovs-vsctl del-port', self, intf )
1019

    
1020
    def controllerUUIDs( self ):
1021
        "Return ovsdb UUIDs for our controllers"
1022
        uuids = []
1023
        controllers = self.cmd( 'ovs-vsctl -- get Bridge', self,
1024
                               'Controller' ).strip()
1025
        if controllers.startswith( '[' ) and controllers.endswith( ']' ):
1026
            controllers = controllers[ 1 : -1 ]
1027
            uuids = [ c.strip() for c in controllers.split( ',' ) ]
1028
        return uuids
1029

    
1030
    def connected( self ):
1031
        "Are we connected to at least one of our controllers?"
1032
        results = [ 'true' in self.cmd( 'ovs-vsctl -- get Controller',
1033
                                         uuid, 'is_connected' )
1034
                    for uuid in self.controllerUUIDs() ]
1035
        return reduce( or_, results, False )
1036

    
1037
    def start( self, controllers ):
1038
        "Start up a new OVS OpenFlow switch using ovs-vsctl"
1039
        if self.inNamespace:
1040
            raise Exception(
1041
                'OVS kernel switch does not work in a namespace' )
1042
        # We should probably call config instead, but this
1043
        # requires some rethinking...
1044
        self.cmd( 'ifconfig lo up' )
1045
        # Annoyingly, --if-exists option seems not to work
1046
        self.cmd( 'ovs-vsctl del-br', self )
1047
        int( self.dpid, 16 ) # DPID must be a hex string
1048
        # Interfaces and controllers
1049
        intfs = ' '.join( '-- add-port %s %s ' % ( self, intf )
1050
                         for intf in self.intfList() if not intf.IP() )
1051
        clist = ' '.join( '%s:%s:%d' % ( c.protocol, c.IP(), c.port )
1052
                         for c in controllers )
1053
        if self.listenPort:
1054
            clist += ' ptcp:%s' % self.listenPort
1055
        # Construct big ovs-vsctl command
1056
        cmd = ( 'ovs-vsctl add-br %s ' % self +
1057
                '-- set Bridge %s ' % self +
1058
                'other_config:datapath-id=%s ' % self.dpid +
1059
                '-- set-fail-mode %s %s ' % ( self, self.failMode ) +
1060
                intfs +
1061
                '-- set-controller %s %s ' % (self, clist ) )
1062
        if not self.inband:
1063
            cmd += ( '-- set bridge %s '
1064
                     'other-config:disable-in-band=true ' % self )
1065
        if self.datapath == 'user':
1066
            cmd +=  '-- set bridge %s datapath_type=netdev ' % self
1067
        # Reconnect quickly to controllers (1s vs. 15s max_backoff)
1068
        for uuid in self.controllerUUIDs():
1069
            if uuid.count( '-' ) != 4:
1070
                # Doesn't look like a UUID
1071
                continue
1072
            uuid = uuid.strip()
1073
            cmd += '-- set Controller %smax_backoff=1000 ' % uuid
1074
        # Do it!!
1075
        self.cmd( cmd )
1076
        for intf in self.intfList():
1077
            self.TCReapply( intf )
1078

    
1079

    
1080
    def stop( self ):
1081
        "Terminate OVS switch."
1082
        self.cmd( 'ovs-vsctl del-br', self )
1083
        if self.datapath == 'user':
1084
            self.cmd( 'ip link del', self )
1085
        self.deleteIntfs()
1086

    
1087
OVSKernelSwitch = OVSSwitch
1088

    
1089

    
1090
class IVSSwitch(Switch):
1091
    """IVS virtual switch"""
1092

    
1093
    def __init__( self, name, **kwargs ):
1094
        Switch.__init__( self, name, **kwargs )
1095

    
1096
    @classmethod
1097
    def setup( cls ):
1098
        "Make sure IVS is installed"
1099
        pathCheck( 'ivs-ctl', 'ivs',
1100
                   moduleName="Indigo Virtual Switch (projectfloodlight.org)" )
1101
        out, err, exitcode = errRun( 'ivs-ctl show' )
1102
        if exitcode:
1103
            error( out + err +
1104
                   'ivs-ctl exited with code %d\n' % exitcode +
1105
                   '*** The openvswitch kernel module might '
1106
                   'not be loaded. Try modprobe openvswitch.\n' )
1107
            exit( 1 )
1108

    
1109
    @classmethod
1110
    def batchShutdown( cls, switches ):
1111
        "Kill each IVS switch, to be waited on later in stop()"
1112
        for switch in switches:
1113
            if type(switch) == cls:
1114
                switch.cmd( 'kill %ivs' )
1115

    
1116
    def start( self, controllers ):
1117
        "Start up a new IVS switch"
1118
        args = ['ivs']
1119
        args.extend( ['--name', self.name] )
1120
        args.extend( ['--dpid', self.dpid] )
1121
        args.extend( ['--verbose'] )
1122
        for intf in self.intfs.values():
1123
            if not intf.IP():
1124
                args.extend( ['-i', intf.name] )
1125
        for c in controllers:
1126
            args.extend( ['-c', '%s:%d' % (c.IP(), c.port)] )
1127
        if self.listenPort:
1128
            args.extend( ['--listen', '127.0.0.1:%i' % self.listenPort] )
1129
        args.append( self.opts )
1130

    
1131
        logfile = '/tmp/ivs.%s.log' % self.name
1132

    
1133
        self.cmd( 'ifconfig lo up' )
1134
        self.cmd( ' '.join(args) + ' >' + logfile + ' 2>&1 </dev/null &' )
1135

    
1136
    def stop( self ):
1137
        "Terminate IVS switch."
1138
        self.cmd( 'kill %ivs' )
1139
        self.cmd( 'wait' )
1140
        self.deleteIntfs()
1141

    
1142
    def attach( self, intf ):
1143
        "Connect a data port"
1144
        self.cmd( 'ivs-ctl', 'add-port', '--datapath', self.name, intf )
1145

    
1146
    def detach( self, intf ):
1147
        "Disconnect a data port"
1148
        self.cmd( 'ivs-ctl', 'del-port', '--datapath', self.name, intf )
1149

    
1150
    def dpctl( self, *args ):
1151
        "Run dpctl command"
1152
        if not self.listenPort:
1153
            return "can't run dpctl without passive listening port"
1154
        return self.cmd( 'ovs-ofctl ' + ' '.join( args ) +
1155
                         ' tcp:127.0.0.1:%i' % self.listenPort )
1156

    
1157

    
1158
class Controller( Node ):
1159
    """A Controller is a Node that is running (or has execed?) an
1160
       OpenFlow controller."""
1161

    
1162
    def __init__( self, name, inNamespace=False, command='controller',
1163
                  cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
1164
                  port=6633, protocol='tcp', **params ):
1165
        self.command = command
1166
        self.cargs = cargs
1167
        self.cdir = cdir
1168
        self.ip = ip
1169
        self.port = port
1170
        self.protocol = protocol
1171
        Node.__init__( self, name, inNamespace=inNamespace,
1172
                       ip=ip, **params  )
1173
        self.cmd( 'ifconfig lo up' )  # Shouldn't be necessary
1174
        self.checkListening()
1175

    
1176
    def checkListening( self ):
1177
        "Make sure no controllers are running on our port"
1178
        # Verify that Telnet is installed first:
1179
        out, _err, returnCode = errRun( "which telnet" )
1180
        if 'telnet' not in out or returnCode != 0:
1181
            raise Exception( "Error running telnet to check for listening "
1182
                             "controllers; please check that it is "
1183
                             "installed." )
1184
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1185
                              ( self.ip, self.port ) )
1186
        if 'Connected' in listening:
1187
            servers = self.cmd( 'netstat -natp' ).split( '\n' )
1188
            pstr = ':%d ' % self.port
1189
            clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ]
1190
            raise Exception( "Please shut down the controller which is"
1191
                             " running on port %d:\n" % self.port +
1192
                             '\n'.join( clist ) )
1193

    
1194
    def start( self ):
1195
        """Start <controller> <args> on controller.
1196
           Log to /tmp/cN.log"""
1197
        pathCheck( self.command )
1198
        cout = '/tmp/' + self.name + '.log'
1199
        if self.cdir is not None:
1200
            self.cmd( 'cd ' + self.cdir )
1201
        self.cmd( self.command + ' ' + self.cargs % self.port +
1202
                  ' 1>' + cout + ' 2>' + cout + '&' )
1203
        self.execed = False
1204

    
1205
    def stop( self ):
1206
        "Stop controller."
1207
        self.cmd( 'kill %' + self.command )
1208
        self.terminate()
1209

    
1210
    def IP( self, intf=None ):
1211
        "Return IP address of the Controller"
1212
        if self.intfs:
1213
            ip = Node.IP( self, intf )
1214
        else:
1215
            ip = self.ip
1216
        return ip
1217

    
1218
    def __repr__( self ):
1219
        "More informative string representation"
1220
        return '<%s %s: %s:%s pid=%s> ' % (
1221
            self.__class__.__name__, self.name,
1222
            self.IP(), self.port, self.pid )
1223

    
1224

    
1225
class OVSController( Controller ):
1226
    "Open vSwitch controller"
1227
    def __init__( self, name, command='ovs-controller', **kwargs ):
1228
        Controller.__init__( self, name, command=command, **kwargs )
1229

    
1230

    
1231
class NOX( Controller ):
1232
    "Controller to run a NOX application."
1233

    
1234
    def __init__( self, name, *noxArgs, **kwargs ):
1235
        """Init.
1236
           name: name to give controller
1237
           noxArgs: arguments (strings) to pass to NOX"""
1238
        if not noxArgs:
1239
            warn( 'warning: no NOX modules specified; '
1240
                  'running packetdump only\n' )
1241
            noxArgs = [ 'packetdump' ]
1242
        elif type( noxArgs ) not in ( list, tuple ):
1243
            noxArgs = [ noxArgs ]
1244

    
1245
        if 'NOX_CORE_DIR' not in os.environ:
1246
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
1247
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
1248

    
1249
        Controller.__init__( self, name,
1250
                             command=noxCoreDir + '/nox_core',
1251
                             cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
1252
                             ' '.join( noxArgs ),
1253
                             cdir=noxCoreDir,
1254
                             **kwargs )
1255

    
1256

    
1257
class RemoteController( Controller ):
1258
    "Controller running outside of Mininet's control."
1259

    
1260
    def __init__( self, name, ip='127.0.0.1',
1261
                  port=6633, **kwargs):
1262
        """Init.
1263
           name: name to give controller
1264
           ip: the IP address where the remote controller is
1265
           listening
1266
           port: the port where the remote controller is listening"""
1267
        Controller.__init__( self, name, ip=ip, port=port, **kwargs )
1268

    
1269
    def start( self ):
1270
        "Overridden to do nothing."
1271
        return
1272

    
1273
    def stop( self ):
1274
        "Overridden to do nothing."
1275
        return
1276

    
1277
    def checkListening( self ):
1278
        "Warn if remote controller is not accessible"
1279
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1280
                              ( self.ip, self.port ) )
1281
        if 'Connected' not in listening:
1282
            warn( "Unable to contact the remote controller"
1283
                  " at %s:%d\n" % ( self.ip, self.port ) )
1284