Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 9c3ecfe3

History | View | Annotate | Download (49.5 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
HostWithPrivateDirs: a virtual host that has user-specified private
20
    directories. These may be temporary directories stored as a tmpfs,
21
    or persistent directories that are mounted from another directory in
22
    the root filesystem.
23

24
Switch: superclass for switch nodes.
25

26
UserSwitch: a switch using the user-space switch from the OpenFlow
27
    reference implementation.
28

29
KernelSwitch: a switch using the kernel switch from the OpenFlow reference
30
    implementation.
31

32
OVSSwitch: a switch using the OpenVSwitch OpenFlow-compatible switch
33
    implementation (openvswitch.org).
34

35
Controller: superclass for OpenFlow controllers. The default controller
36
    is controller(8) from the reference implementation.
37

38
NOXController: a controller node using NOX (noxrepo.org).
39

40
RemoteController: a remote controller node, which may use any
41
    arbitrary OpenFlow-compatible controller, and which is not
42
    created or managed by mininet.
43

44
Future enhancements:
45

46
- Possibly make Node, Switch and Controller more abstract so that
47
  they can be used for both local and remote nodes
48

49
- Create proxy objects for remote nodes (Mininet: Cluster Edition)
50
"""
51

    
52
import os
53
import re
54
import signal
55
import select
56
from subprocess import Popen, PIPE, STDOUT
57
from operator import or_
58
from time import sleep
59

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

    
68
class Node( object ):
69
    """A virtual network node is simply a shell in a network namespace.
70
       We communicate with it using pipes."""
71

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

    
74
    def __init__( self, name, inNamespace=True, **params ):
75
        """name: name of node
76
           inNamespace: in network namespace?
77
           params: Node parameters (see config() for details)"""
78

    
79
        # Make sure class actually works
80
        self.checkSetup()
81

    
82
        self.name = name
83
        self.inNamespace = inNamespace
84

    
85
        # Stash configuration parameters for future reference
86
        self.params = params
87

    
88
        self.intfs = {}  # dict of port numbers to interfaces
89
        self.ports = {}  # dict of interfaces to port numbers
90
                         # replace with Port objects, eventually ?
91
        self.nameToIntf = {}  # dict of interface names to Intfs
92

    
93
        # Make pylint happy
94
        ( self.shell, self.execed, self.pid, self.stdin, self.stdout,
95
            self.lastPid, self.lastCmd, self.pollOut ) = (
96
                None, None, None, None, None, None, None, None )
97
        self.waiting = False
98
        self.readbuf = ''
99

    
100
        # Start command interpreter shell
101
        self.startShell()
102

    
103
    # File descriptor to node mapping support
104
    # Class variables and methods
105

    
106
    inToNode = {}  # mapping of input fds to nodes
107
    outToNode = {}  # mapping of output fds to nodes
108

    
109
    @classmethod
110
    def fdToNode( cls, fd ):
111
        """Return node corresponding to given file descriptor.
112
           fd: file descriptor
113
           returns: node"""
114
        node = cls.outToNode.get( fd )
115
        return node or cls.inToNode.get( fd )
116

    
117
    # Command support via shell process in namespace
118

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

    
150
    def cleanup( self ):
151
        "Help python collect its garbage."
152
        # Intfs may end up in root NS
153
        for intfName in self.intfNames():
154
            if self.name in intfName:
155
                quietRun( 'ip link del ' + intfName )
156
        self.shell = None
157

    
158
    # Subshell I/O, commands and control
159

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

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

    
186
    def write( self, data ):
187
        """Write data to node.
188
           data: string"""
189
        os.write( self.stdin.fileno(), data )
190

    
191
    def terminate( self ):
192
        "Send kill signal to Node and clean up after it."
193
        if self.shell:
194
            os.killpg( self.pid, signal.SIGKILL )
195
        self.cleanup()
196

    
197
    def stop( self ):
198
        "Stop node."
199
        self.terminate()
200

    
201
    def waitReadable( self, timeoutms=None ):
202
        """Wait until node's output is readable.
203
           timeoutms: timeout in ms or None to wait indefinitely."""
204
        if len( self.readbuf ) == 0:
205
            self.pollOut.poll( timeoutms )
206

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

    
240
    def sendInt( self, sig=signal.SIGINT ):
241
        "Interrupt running command."
242
        if self.lastPid:
243
            try:
244
                os.kill( self.lastPid, sig )
245
            except OSError:
246
                pass
247

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

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

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

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

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

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

    
335
    # Interface management, configuration, and routing
336

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

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

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

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

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

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

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

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

    
417
    # Routing support
418

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

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

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

    
443
    # Convenience and configuration methods
444

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

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

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

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

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

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

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

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

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

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

    
528
    # Other methods
529

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

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

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

    
545
    def __str__( self ):
546
        "Abbreviated string representation"
547
        return self.name
548

    
549
    # Automatic class setup support
550

    
551
    isSetup = False
552

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

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

    
567

    
568
class Host( Node ):
569
    "A host is simply a Node"
570
    pass
571

    
572

    
573
class CPULimitedHost( Host ):
574

    
575
    "CPU limited host"
576

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

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

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

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

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

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

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

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

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

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

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

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

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

    
725
    inited = False
726

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

    
733
class HostWithPrivateDirs( Host ):
734
    "Host with private directories"
735

    
736
    def __init__( self, name, *args, **kwargs ):
737
        "privateDirs: list of private directory strings or tuples"
738
        self.name = name
739
        self.privateDirs = kwargs.pop( 'privateDirs', [] )
740
        Host.__init__( self, name, *args, **kwargs )
741
        self.mountPrivateDirs()
742

    
743
    def mountPrivateDirs( self ):
744
        "mount private directories"
745
        for directory in self.privateDirs:
746
            if isinstance( directory, tuple ):
747
                # mount given private directory
748
                privateDir = directory[ 1 ] % self.__dict__ 
749
                mountPoint = directory[ 0 ]
750
                self.cmd( 'mkdir -p %s' % privateDir )
751
                self.cmd( 'mkdir -p %s' % mountPoint )
752
                self.cmd( 'mount --bind %s %s' %
753
                               ( privateDir, mountPoint ) )
754
            else:
755
                # mount temporary filesystem on directory
756
                self.cmd( 'mkdir -p %s' % directory ) 
757
                self.cmd( 'mount -n -t tmpfs tmpfs %s' % directory )
758

    
759

    
760

    
761
# Some important things to note:
762
#
763
# The "IP" address which setIP() assigns to the switch is not
764
# an "IP address for the switch" in the sense of IP routing.
765
# Rather, it is the IP address for the control interface,
766
# on the control network, and it is only relevant to the
767
# controller. If you are running in the root namespace
768
# (which is the only way to run OVS at the moment), the
769
# control interface is the loopback interface, and you
770
# normally never want to change its IP address!
771
#
772
# In general, you NEVER want to attempt to use Linux's
773
# network stack (i.e. ifconfig) to "assign" an IP address or
774
# MAC address to a switch data port. Instead, you "assign"
775
# the IP and MAC addresses in the controller by specifying
776
# packets that you want to receive or send. The "MAC" address
777
# reported by ifconfig for a switch data port is essentially
778
# meaningless. It is important to understand this if you
779
# want to create a functional router using OpenFlow.
780

    
781
class Switch( Node ):
782
    """A Switch is a Node that is running (or has execed?)
783
       an OpenFlow switch."""
784

    
785
    portBase = 1  # Switches start with port 1 in OpenFlow
786
    dpidLen = 16  # digits in dpid passed to switch
787

    
788
    def __init__( self, name, dpid=None, opts='', listenPort=None, **params):
789
        """dpid: dpid hex string (or None to derive from name, e.g. s1 -> 1)
790
           opts: additional switch options
791
           listenPort: port to listen on for dpctl connections"""
792
        Node.__init__( self, name, **params )
793
        self.dpid = self.defaultDpid( dpid )
794
        self.opts = opts
795
        self.listenPort = listenPort
796
        if not self.inNamespace:
797
            self.controlIntf = Intf( 'lo', self, port=0 )
798

    
799
    def defaultDpid( self, dpid=None ):
800
        "Return correctly formatted dpid from dpid or switch name (s1 -> 1)"
801
        if dpid:
802
            # Remove any colons and make sure it's a good hex number
803
            dpid = dpid.translate( None, ':' )
804
            assert len( dpid ) <= self.dpidLen and int( dpid, 16 ) >= 0
805
        else:
806
            # Use hex of the first number in the switch name
807
            nums = re.findall( r'\d+', self.name )
808
            if nums:
809
                dpid = hex( int( nums[ 0 ] ) )[ 2: ]
810
            else:
811
                raise Exception( 'Unable to derive default datapath ID - '
812
                                 'please either specify a dpid or use a '
813
                                 'canonical switch name such as s23.' )
814
        return '0' * ( self.dpidLen - len( dpid ) ) + dpid
815

    
816
    def defaultIntf( self ):
817
        "Return control interface"
818
        if self.controlIntf:
819
            return self.controlIntf
820
        else:
821
            return Node.defaultIntf( self )
822

    
823
    def sendCmd( self, *cmd, **kwargs ):
824
        """Send command to Node.
825
           cmd: string"""
826
        kwargs.setdefault( 'printPid', False )
827
        if not self.execed:
828
            return Node.sendCmd( self, *cmd, **kwargs )
829
        else:
830
            error( '*** Error: %s has execed and cannot accept commands' %
831
                   self.name )
832

    
833
    def connected( self ):
834
        "Is the switch connected to a controller? (override this method)"
835
        return False and self  # satisfy pylint
836

    
837
    def __repr__( self ):
838
        "More informative string representation"
839
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
840
                              for i in self.intfList() ] ) )
841
        return '<%s %s: %s pid=%s> ' % (
842
            self.__class__.__name__, self.name, intfs, self.pid )
843

    
844
class UserSwitch( Switch ):
845
    "User-space switch."
846

    
847
    dpidLen = 12
848

    
849
    def __init__( self, name, dpopts='--no-slicing', **kwargs ):
850
        """Init.
851
           name: name for the switch
852
           dpopts: additional arguments to ofdatapath (--no-slicing)"""
853
        Switch.__init__( self, name, **kwargs )
854
        pathCheck( 'ofdatapath', 'ofprotocol',
855
                   moduleName='the OpenFlow reference user switch' +
856
                              '(openflow.org)' )
857
        if self.listenPort:
858
            self.opts += ' --listen=ptcp:%i ' % self.listenPort
859
        self.dpopts = dpopts
860

    
861
    @classmethod
862
    def setup( cls ):
863
        "Ensure any dependencies are loaded; if not, try to load them."
864
        if not os.path.exists( '/dev/net/tun' ):
865
            moduleDeps( add=TUN )
866

    
867
    def dpctl( self, *args ):
868
        "Run dpctl command"
869
        listenAddr = None
870
        if not self.listenPort:
871
            listenAddr = 'unix:/tmp/' + self.name
872
        else:
873
            listenAddr = 'tcp:127.0.0.1:%i' % self.listenPort
874
        return self.cmd( 'dpctl ' + ' '.join( args ) +
875
                         ' ' + listenAddr )
876

    
877
    def connected( self ):
878
        "Is the switch connected to a controller?"
879
        return 'remote.is-connected=true' in self.dpctl( 'status' )
880

    
881
    @staticmethod
882
    def TCReapply( intf ):
883
        """Unfortunately user switch and Mininet are fighting
884
           over tc queuing disciplines. To resolve the conflict,
885
           we re-create the user switch's configuration, but as a
886
           leaf of the TCIntf-created configuration."""
887
        if type( intf ) is TCIntf:
888
            ifspeed = 10000000000 # 10 Gbps
889
            minspeed = ifspeed * 0.001
890

    
891
            res = intf.config( **intf.params )
892

    
893
            if res is None: # link may not have TC parameters
894
                return
895

    
896
            # Re-add qdisc, root, and default classes user switch created, but
897
            # with new parent, as setup by Mininet's TCIntf
898
            parent = res['parent']
899
            intf.tc( "%s qdisc add dev %s " + parent +
900
                     " handle 1: htb default 0xfffe" )
901
            intf.tc( "%s class add dev %s classid 1:0xffff parent 1: htb rate "
902
                     + str(ifspeed) )
903
            intf.tc( "%s class add dev %s classid 1:0xfffe parent 1:0xffff " +
904
                     "htb rate " + str(minspeed) + " ceil " + str(ifspeed) )
905

    
906
    def start( self, controllers ):
907
        """Start OpenFlow reference user datapath.
908
           Log to /tmp/sN-{ofd,ofp}.log.
909
           controllers: list of controller objects"""
910
        # Add controllers
911
        clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
912
                            for c in controllers ] )
913
        ofdlog = '/tmp/' + self.name + '-ofd.log'
914
        ofplog = '/tmp/' + self.name + '-ofp.log'
915
        self.cmd( 'ifconfig lo up' )
916
        intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
917
        self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
918
                  ' punix:/tmp/' + self.name + ' -d %s ' % self.dpid +
919
                  self.dpopts +
920
                  ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
921
        self.cmd( 'ofprotocol unix:/tmp/' + self.name +
922
                  ' ' + clist +
923
                  ' --fail=closed ' + self.opts +
924
                  ' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
925
        if "no-slicing" not in self.dpopts:
926
            # Only TCReapply if slicing is enable
927
            sleep(1) # Allow ofdatapath to start before re-arranging qdisc's
928
            for intf in self.intfList():
929
                if not intf.IP():
930
                    self.TCReapply( intf )
931

    
932
    def stop( self ):
933
        "Stop OpenFlow reference user datapath."
934
        self.cmd( 'kill %ofdatapath' )
935
        self.cmd( 'kill %ofprotocol' )
936
        self.deleteIntfs()
937

    
938

    
939
class OVSLegacyKernelSwitch( Switch ):
940
    """Open VSwitch legacy kernel-space switch using ovs-openflowd.
941
       Currently only works in the root namespace."""
942

    
943
    def __init__( self, name, dp=None, **kwargs ):
944
        """Init.
945
           name: name for switch
946
           dp: netlink id (0, 1, 2, ...)
947
           defaultMAC: default MAC as unsigned int; random value if None"""
948
        Switch.__init__( self, name, **kwargs )
949
        self.dp = dp if dp else self.name
950
        self.intf = self.dp
951
        if self.inNamespace:
952
            error( "OVSKernelSwitch currently only works"
953
                   " in the root namespace.\n" )
954
            exit( 1 )
955

    
956
    @classmethod
957
    def setup( cls ):
958
        "Ensure any dependencies are loaded; if not, try to load them."
959
        pathCheck( 'ovs-dpctl', 'ovs-openflowd',
960
                   moduleName='Open vSwitch (openvswitch.org)')
961
        moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
962

    
963
    def start( self, controllers ):
964
        "Start up kernel datapath."
965
        ofplog = '/tmp/' + self.name + '-ofp.log'
966
        quietRun( 'ifconfig lo up' )
967
        # Delete local datapath if it exists;
968
        # then create a new one monitoring the given interfaces
969
        self.cmd( 'ovs-dpctl del-dp ' + self.dp )
970
        self.cmd( 'ovs-dpctl add-dp ' + self.dp )
971
        intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
972
        self.cmd( 'ovs-dpctl', 'add-if', self.dp, ' '.join( intfs ) )
973
        # Run protocol daemon
974
        clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
975
                            for c in controllers ] )
976
        self.cmd( 'ovs-openflowd ' + self.dp +
977
                  ' ' + clist +
978
                  ' --fail=secure ' + self.opts +
979
                  ' --datapath-id=' + self.dpid +
980
                  ' 1>' + ofplog + ' 2>' + ofplog + '&' )
981
        self.execed = False
982

    
983
    def stop( self ):
984
        "Terminate kernel datapath."
985
        quietRun( 'ovs-dpctl del-dp ' + self.dp )
986
        self.cmd( 'kill %ovs-openflowd' )
987
        self.deleteIntfs()
988

    
989

    
990
class OVSSwitch( Switch ):
991
    "Open vSwitch switch. Depends on ovs-vsctl."
992

    
993
    def __init__( self, name, failMode='secure', datapath='kernel',
994
                 inband=False, **params ):
995
        """Init.
996
           name: name for switch
997
           failMode: controller loss behavior (secure|open)
998
           datapath: userspace or kernel mode (kernel|user)
999
           inband: use in-band control (False)"""
1000
        Switch.__init__( self, name, **params )
1001
        self.failMode = failMode
1002
        self.datapath = datapath
1003
        self.inband = inband
1004

    
1005
    @classmethod
1006
    def setup( cls ):
1007
        "Make sure Open vSwitch is installed and working"
1008
        pathCheck( 'ovs-vsctl',
1009
                   moduleName='Open vSwitch (openvswitch.org)')
1010
        # This should no longer be needed, and it breaks
1011
        # with OVS 1.7 which has renamed the kernel module:
1012
        #  moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
1013
        out, err, exitcode = errRun( 'ovs-vsctl -t 1 show' )
1014
        if exitcode:
1015
            error( out + err +
1016
                   'ovs-vsctl exited with code %d\n' % exitcode +
1017
                   '*** Error connecting to ovs-db with ovs-vsctl\n'
1018
                   'Make sure that Open vSwitch is installed, '
1019
                   'that ovsdb-server is running, and that\n'
1020
                   '"ovs-vsctl show" works correctly.\n'
1021
                   'You may wish to try '
1022
                   '"service openvswitch-switch start".\n' )
1023
            exit( 1 )
1024
        info = quietRun( 'ovs-vsctl --version' )
1025
        cls.OVSVersion =  findall( '\d+\.\d+', info )[ 0 ]
1026

    
1027
    @classmethod
1028
    def isOldOVS( cls ):
1029
        return ( StrictVersion( cls.OVSVersion ) <
1030
             StrictVersion( '1.10' ) )
1031

    
1032
    @classmethod
1033
    def batchShutdown( cls, switches ):
1034
        "Call ovs-vsctl del-br on all OVSSwitches in a list"
1035
        quietRun( 'ovs-vsctl ' +
1036
                  ' -- '.join( '--if-exists del-br %s' % s
1037
                               for s in switches if type(s) == cls ) )
1038

    
1039
    def dpctl( self, *args ):
1040
        "Run ovs-ofctl command"
1041
        return self.cmd( 'ovs-ofctl', args[ 0 ], self, *args[ 1: ] )
1042

    
1043
    @staticmethod
1044
    def TCReapply( intf ):
1045
        """Unfortunately OVS and Mininet are fighting
1046
           over tc queuing disciplines. As a quick hack/
1047
           workaround, we clear OVS's and reapply our own."""
1048
        if type( intf ) is TCIntf:
1049
            intf.config( **intf.params )
1050

    
1051
    def attach( self, intf ):
1052
        "Connect a data port"
1053
        self.cmd( 'ovs-vsctl add-port', self, intf )
1054
        self.cmd( 'ifconfig', intf, 'up' )
1055
        self.TCReapply( intf )
1056

    
1057
    def detach( self, intf ):
1058
        "Disconnect a data port"
1059
        self.cmd( 'ovs-vsctl del-port', self, intf )
1060

    
1061
    def controllerUUIDs( self ):
1062
        "Return ovsdb UUIDs for our controllers"
1063
        uuids = []
1064
        controllers = self.cmd( 'ovs-vsctl -- get Bridge', self,
1065
                               'Controller' ).strip()
1066
        if controllers.startswith( '[' ) and controllers.endswith( ']' ):
1067
            controllers = controllers[ 1 : -1 ]
1068
            uuids = [ c.strip() for c in controllers.split( ',' ) ]
1069
        return uuids
1070

    
1071
    def connected( self ):
1072
        "Are we connected to at least one of our controllers?"
1073
        results = [ 'true' in self.cmd( 'ovs-vsctl -- get Controller',
1074
                                         uuid, 'is_connected' )
1075
                    for uuid in self.controllerUUIDs() ]
1076
        return reduce( or_, results, False )
1077

    
1078
    def start( self, controllers ):
1079
        "Start up a new OVS OpenFlow switch using ovs-vsctl"
1080
        if self.inNamespace:
1081
            raise Exception(
1082
                'OVS kernel switch does not work in a namespace' )
1083
        # We should probably call config instead, but this
1084
        # requires some rethinking...
1085
        self.cmd( 'ifconfig lo up' )
1086
        # Annoyingly, --if-exists option seems not to work
1087
        self.cmd( 'ovs-vsctl del-br', self )
1088
        int( self.dpid, 16 ) # DPID must be a hex string
1089
        # Interfaces and controllers
1090
        intfs = ' '.join( '-- add-port %s %s ' % ( self, intf ) +
1091
                          '-- set Interface %s ' % intf +
1092
                          'ofport_request=%s ' % self.ports[ intf ]
1093
                         for intf in self.intfList() if not intf.IP() )
1094
        clist = ' '.join( '%s:%s:%d' % ( c.protocol, c.IP(), c.port )
1095
                         for c in controllers )
1096
        if self.listenPort:
1097
            clist += ' ptcp:%s' % self.listenPort
1098
        # Construct big ovs-vsctl command for new versions of OVS
1099
        if not self.isOldOVS():
1100
            cmd = ( 'ovs-vsctl add-br %s ' % self +
1101
                    '-- set Bridge %s ' % self +
1102
                    'other_config:datapath-id=%s ' % self.dpid +
1103
                    '-- set-fail-mode %s %s ' % ( self, self.failMode ) +
1104
                    intfs +
1105
                    '-- set-controller %s %s ' % ( self, clist ) )
1106
        # Construct ovs-vsctl commands for old versions of OVS
1107
        else:
1108
            self.cmd( 'ovs-vsctl add-br', self )
1109
            for intf in self.intfList():
1110
                if not intf.IP():
1111
                    self.cmd('ovs-vsctl add-port', self, intf )
1112
            cmd = ('ovs-vsctl set Bridge %s ' % self +
1113
                'other_config:datapath-id=%s ' % self.dpid +
1114
                '-- set-fail-mode %s %s ' % ( self, self.failMode ) +
1115
                '-- set-controller %s %s ' % ( self, clist ) )
1116
        if not self.inband:
1117
            cmd += ( '-- set bridge %s '
1118
                     'other-config:disable-in-band=true ' % self )
1119
        if self.datapath == 'user':
1120
            cmd +=  '-- set bridge %s datapath_type=netdev ' % self
1121
        # Reconnect quickly to controllers (1s vs. 15s max_backoff)
1122
        for uuid in self.controllerUUIDs():
1123
            if uuid.count( '-' ) != 4:
1124
                # Doesn't look like a UUID
1125
                continue
1126
            uuid = uuid.strip()
1127
            cmd += '-- set Controller %smax_backoff=1000 ' % uuid
1128
        # Do it!!
1129
        self.cmd( cmd )
1130
        for intf in self.intfList():
1131
            self.TCReapply( intf )
1132

    
1133

    
1134
    def stop( self ):
1135
        "Terminate OVS switch."
1136
        self.cmd( 'ovs-vsctl del-br', self )
1137
        if self.datapath == 'user':
1138
            self.cmd( 'ip link del', self )
1139
        self.deleteIntfs()
1140

    
1141
OVSKernelSwitch = OVSSwitch
1142

    
1143

    
1144
class IVSSwitch(Switch):
1145
    """IVS virtual switch"""
1146

    
1147
    def __init__( self, name, **kwargs ):
1148
        Switch.__init__( self, name, **kwargs )
1149

    
1150
    @classmethod
1151
    def setup( cls ):
1152
        "Make sure IVS is installed"
1153
        pathCheck( 'ivs-ctl', 'ivs',
1154
                   moduleName="Indigo Virtual Switch (projectfloodlight.org)" )
1155
        out, err, exitcode = errRun( 'ivs-ctl show' )
1156
        if exitcode:
1157
            error( out + err +
1158
                   'ivs-ctl exited with code %d\n' % exitcode +
1159
                   '*** The openvswitch kernel module might '
1160
                   'not be loaded. Try modprobe openvswitch.\n' )
1161
            exit( 1 )
1162

    
1163
    def start( self, controllers ):
1164
        "Start up a new IVS switch"
1165
        args = ['ivs']
1166
        args.extend( ['--name', self.name] )
1167
        args.extend( ['--dpid', self.dpid] )
1168
        args.extend( ['--verbose'] )
1169
        for intf in self.intfs.values():
1170
            if not intf.IP():
1171
                args.extend( ['-i', intf.name] )
1172
        for c in controllers:
1173
            args.extend( ['-c', '%s:%d' % (c.IP(), c.port)] )
1174
        if self.listenPort:
1175
            args.extend( ['--listen', '127.0.0.1:%i' % self.listenPort] )
1176
        args.append( self.opts )
1177

    
1178
        logfile = '/tmp/ivs.%s.log' % self.name
1179

    
1180
        self.cmd( 'ifconfig lo up' )
1181
        self.cmd( ' '.join(args) + ' >' + logfile + ' 2>&1 </dev/null &' )
1182

    
1183
    def stop( self ):
1184
        "Terminate IVS switch."
1185
        self.cmd( 'kill %ivs' )
1186
        self.cmd( 'wait' )
1187
        self.deleteIntfs()
1188

    
1189
    def attach( self, intf ):
1190
        "Connect a data port"
1191
        self.cmd( 'ivs-ctl', 'add-port', '--datapath', self.name, intf )
1192

    
1193
    def detach( self, intf ):
1194
        "Disconnect a data port"
1195
        self.cmd( 'ivs-ctl', 'del-port', '--datapath', self.name, intf )
1196

    
1197
    def dpctl( self, *args ):
1198
        "Run dpctl command"
1199
        if not self.listenPort:
1200
            return "can't run dpctl without passive listening port"
1201
        return self.cmd( 'ovs-ofctl ' + ' '.join( args ) +
1202
                         ' tcp:127.0.0.1:%i' % self.listenPort )
1203

    
1204

    
1205
class Controller( Node ):
1206
    """A Controller is a Node that is running (or has execed?) an
1207
       OpenFlow controller."""
1208

    
1209
    def __init__( self, name, inNamespace=False, command='controller',
1210
                  cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
1211
                  port=6633, protocol='tcp', **params ):
1212
        self.command = command
1213
        self.cargs = cargs
1214
        self.cdir = cdir
1215
        self.ip = ip
1216
        self.port = port
1217
        self.protocol = protocol
1218
        Node.__init__( self, name, inNamespace=inNamespace,
1219
                       ip=ip, **params  )
1220
        self.cmd( 'ifconfig lo up' )  # Shouldn't be necessary
1221
        self.checkListening()
1222

    
1223
    def checkListening( self ):
1224
        "Make sure no controllers are running on our port"
1225
        # Verify that Telnet is installed first:
1226
        out, _err, returnCode = errRun( "which telnet" )
1227
        if 'telnet' not in out or returnCode != 0:
1228
            raise Exception( "Error running telnet to check for listening "
1229
                             "controllers; please check that it is "
1230
                             "installed." )
1231
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1232
                              ( self.ip, self.port ) )
1233
        if 'Connected' in listening:
1234
            servers = self.cmd( 'netstat -natp' ).split( '\n' )
1235
            pstr = ':%d ' % self.port
1236
            clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ]
1237
            raise Exception( "Please shut down the controller which is"
1238
                             " running on port %d:\n" % self.port +
1239
                             '\n'.join( clist ) )
1240

    
1241
    def start( self ):
1242
        """Start <controller> <args> on controller.
1243
           Log to /tmp/cN.log"""
1244
        pathCheck( self.command )
1245
        cout = '/tmp/' + self.name + '.log'
1246
        if self.cdir is not None:
1247
            self.cmd( 'cd ' + self.cdir )
1248
        self.cmd( self.command + ' ' + self.cargs % self.port +
1249
                  ' 1>' + cout + ' 2>' + cout + '&' )
1250
        self.execed = False
1251

    
1252
    def stop( self ):
1253
        "Stop controller."
1254
        self.cmd( 'kill %' + self.command )
1255
        self.terminate()
1256

    
1257
    def IP( self, intf=None ):
1258
        "Return IP address of the Controller"
1259
        if self.intfs:
1260
            ip = Node.IP( self, intf )
1261
        else:
1262
            ip = self.ip
1263
        return ip
1264

    
1265
    def __repr__( self ):
1266
        "More informative string representation"
1267
        return '<%s %s: %s:%s pid=%s> ' % (
1268
            self.__class__.__name__, self.name,
1269
            self.IP(), self.port, self.pid )
1270

    
1271

    
1272
class OVSController( Controller ):
1273
    "Open vSwitch controller"
1274
    def __init__( self, name, command='ovs-controller', **kwargs ):
1275
        Controller.__init__( self, name, command=command, **kwargs )
1276

    
1277

    
1278
class NOX( Controller ):
1279
    "Controller to run a NOX application."
1280

    
1281
    def __init__( self, name, *noxArgs, **kwargs ):
1282
        """Init.
1283
           name: name to give controller
1284
           noxArgs: arguments (strings) to pass to NOX"""
1285
        if not noxArgs:
1286
            warn( 'warning: no NOX modules specified; '
1287
                  'running packetdump only\n' )
1288
            noxArgs = [ 'packetdump' ]
1289
        elif type( noxArgs ) not in ( list, tuple ):
1290
            noxArgs = [ noxArgs ]
1291

    
1292
        if 'NOX_CORE_DIR' not in os.environ:
1293
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
1294
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
1295

    
1296
        Controller.__init__( self, name,
1297
                             command=noxCoreDir + '/nox_core',
1298
                             cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
1299
                             ' '.join( noxArgs ),
1300
                             cdir=noxCoreDir,
1301
                             **kwargs )
1302

    
1303

    
1304
class RemoteController( Controller ):
1305
    "Controller running outside of Mininet's control."
1306

    
1307
    def __init__( self, name, ip='127.0.0.1',
1308
                  port=6633, **kwargs):
1309
        """Init.
1310
           name: name to give controller
1311
           ip: the IP address where the remote controller is
1312
           listening
1313
           port: the port where the remote controller is listening"""
1314
        Controller.__init__( self, name, ip=ip, port=port, **kwargs )
1315

    
1316
    def start( self ):
1317
        "Overridden to do nothing."
1318
        return
1319

    
1320
    def stop( self ):
1321
        "Overridden to do nothing."
1322
        return
1323

    
1324
    def checkListening( self ):
1325
        "Warn if remote controller is not accessible"
1326
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1327
                              ( self.ip, self.port ) )
1328
        if 'Connected' not in listening:
1329
            warn( "Unable to contact the remote controller"
1330
                  " at %s:%d\n" % ( self.ip, self.port ) )