Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 82e0e9f3

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
113
    # Command support via shell process in namespace
114

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

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

    
168
    # Subshell I/O, commands and control
169

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
337
    # Interface management, configuration, and routing
338

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

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

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

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

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

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

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

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

    
419
    # Routing support
420

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

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

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

    
445
    # Convenience and configuration methods
446

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

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

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

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

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

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

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

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

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

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

    
530
    # Other methods
531

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

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

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

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

    
551
    # Automatic class setup support
552

    
553
    isSetup = False
554

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

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

    
569

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

    
574

    
575
class CPULimitedHost( Host ):
576

    
577
    "CPU limited host"
578

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

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

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

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

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

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

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

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

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

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

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

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

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

    
727
    inited = False
728

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

    
735

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

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

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

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

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

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

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

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

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

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

    
822
    dpidLen = 12
823

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

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

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

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

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

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

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

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

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

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

    
913

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

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

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

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

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

    
964

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

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

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

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

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

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

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

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

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

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

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

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

    
1108

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

    
1116
OVSKernelSwitch = OVSSwitch
1117

    
1118

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

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

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

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

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

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

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

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

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

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

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

    
1187

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

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

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

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

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

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

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

    
1254

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

    
1260

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

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

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

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

    
1286

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

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

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

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

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