Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 08d611f4

History | View | Annotate | Download (53.6 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 pty
54
import re
55
import signal
56
import select
57
from subprocess import Popen, PIPE, STDOUT
58
from operator import or_
59
from time import sleep
60

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

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

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

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

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

    
83
        self.name = params.get( 'name', name )
84
        self.inNamespace = params.get( 'inNamespace', inNamespace )
85

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

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

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

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

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

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

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

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

    
164
    def _popen( self, cmd, **params ):
165
        """Internal method: spawn and return a process
166
            cmd: command to run (list)
167
            params: parameters to Popen()"""
168
        return Popen( cmd, **params )
169

    
170
    def cleanup( self ):
171
        "Help python collect its garbage."
172
        # Intfs may end up in root NS
173
        for intfName in self.intfNames():
174
            if self.name in intfName:
175
                quietRun( 'ip link del ' + intfName )
176
        self.shell = None
177

    
178
    # Subshell I/O, commands and control
179

    
180
    def read( self, maxbytes=1024 ):
181
        """Buffered read from node, non-blocking.
182
           maxbytes: maximum number of bytes to return"""
183
        count = len( self.readbuf )
184
        if count < maxbytes:
185
            data = os.read( self.stdout.fileno(), maxbytes - count )
186
            self.readbuf += data
187
        if maxbytes >= len( self.readbuf ):
188
            result = self.readbuf
189
            self.readbuf = ''
190
        else:
191
            result = self.readbuf[ :maxbytes ]
192
            self.readbuf = self.readbuf[ maxbytes: ]
193
        return result
194

    
195
    def readline( self ):
196
        """Buffered readline from node, non-blocking.
197
           returns: line (minus newline) or None"""
198
        self.readbuf += self.read( 1024 )
199
        if '\n' not in self.readbuf:
200
            return None
201
        pos = self.readbuf.find( '\n' )
202
        line = self.readbuf[ 0: pos ]
203
        self.readbuf = self.readbuf[ pos + 1: ]
204
        return line
205

    
206
    def write( self, data ):
207
        """Write data to node.
208
           data: string"""
209
        os.write( self.stdin.fileno(), data )
210

    
211
    def terminate( self ):
212
        "Send kill signal to Node and clean up after it."
213
        if self.shell:
214
            if self.shell.poll() is None:
215
                os.killpg( self.shell.pid, signal.SIGHUP )
216
        self.cleanup()
217

    
218
    def stop( self ):
219
        "Stop node."
220
        self.terminate()
221

    
222
    def waitReadable( self, timeoutms=None ):
223
        """Wait until node's output is readable.
224
           timeoutms: timeout in ms or None to wait indefinitely."""
225
        if len( self.readbuf ) == 0:
226
            self.pollOut.poll( timeoutms )
227

    
228
    def sendCmd( self, *args, **kwargs ):
229
        """Send a command, followed by a command to echo a sentinel,
230
           and return without waiting for the command to complete.
231
           args: command and arguments, or string
232
           printPid: print command's PID?"""
233
        assert not self.waiting
234
        printPid = kwargs.get( 'printPid', True )
235
        # Allow sendCmd( [ list ] )
236
        if len( args ) == 1 and type( args[ 0 ] ) is list:
237
            cmd = args[ 0 ]
238
        # Allow sendCmd( cmd, arg1, arg2... )
239
        elif len( args ) > 0:
240
            cmd = args
241
        # Convert to string
242
        if not isinstance( cmd, str ):
243
            cmd = ' '.join( [ str( c ) for c in cmd ] )
244
        if not re.search( r'\w', cmd ):
245
            # Replace empty commands with something harmless
246
            cmd = 'echo -n'
247
        self.lastCmd = cmd
248
        # if a builtin command is backgrounded, it still yields a PID
249
        if len( cmd ) > 0 and cmd[ -1 ] == '&':
250
            # print ^A{pid}\n so monitor() can set lastPid
251
            cmd += ' printf "\\001%d\\012" $! '
252
        elif printPid and not isShellBuiltin( cmd ):
253
            cmd = 'mnexec -p ' + cmd
254
        self.write( cmd + '\n' )
255
        self.lastPid = None
256
        self.waiting = True
257

    
258
    def sendInt( self, intr=chr( 3 ) ):
259
        "Interrupt running command."
260
        debug( 'sendInt: writing chr(%d)\n' % ord( intr ) )
261
        self.write( intr )
262

    
263
    def monitor( self, timeoutms=None, findPid=True ):
264
        """Monitor and return the output of a command.
265
           Set self.waiting to False if command has completed.
266
           timeoutms: timeout in ms or None to wait indefinitely
267
           findPid: look for PID from mnexec -p"""
268
        self.waitReadable( timeoutms )
269
        data = self.read( 1024 )
270
        pidre = r'\[\d+\] \d+\r\n'
271
        # Look for PID
272
        marker = chr( 1 ) + r'\d+\r\n'
273
        if findPid and chr( 1 ) in data:
274
            # suppress the job and PID of a backgrounded command
275
            if re.findall( pidre, data ):
276
                data = re.sub( pidre, '', data )
277
            # Marker can be read in chunks; continue until all of it is read
278
            while not re.findall( marker, data ):
279
                data += self.read( 1024 )
280
            markers = re.findall( marker, data )
281
            if markers:
282
                self.lastPid = int( markers[ 0 ][ 1: ] )
283
                data = re.sub( marker, '', data )
284
        # Look for sentinel/EOF
285
        if len( data ) > 0 and data[ -1 ] == chr( 127 ):
286
            self.waiting = False
287
            data = data[ :-1 ]
288
        elif chr( 127 ) in data:
289
            self.waiting = False
290
            data = data.replace( chr( 127 ), '' )
291
        return data
292

    
293
    def waitOutput( self, verbose=False, findPid=True ):
294
        """Wait for a command to complete.
295
           Completion is signaled by a sentinel character, ASCII(127)
296
           appearing in the output stream.  Wait for the sentinel and return
297
           the output, including trailing newline.
298
           verbose: print output interactively"""
299
        log = info if verbose else debug
300
        output = ''
301
        while self.waiting:
302
            data = self.monitor()
303
            output += data
304
            log( data )
305
        return output
306

    
307
    def cmd( self, *args, **kwargs ):
308
        """Send a command, wait for output, and return it.
309
           cmd: string"""
310
        verbose = kwargs.get( 'verbose', False )
311
        log = info if verbose else debug
312
        log( '*** %s : %s\n' % ( self.name, args ) )
313
        self.sendCmd( *args, **kwargs )
314
        return self.waitOutput( verbose )
315

    
316
    def cmdPrint( self, *args):
317
        """Call cmd and printing its output
318
           cmd: string"""
319
        return self.cmd( *args, **{ 'verbose': True } )
320

    
321
    def popen( self, *args, **kwargs ):
322
        """Return a Popen() object in our namespace
323
           args: Popen() args, single list, or string
324
           kwargs: Popen() keyword args"""
325
        defaults = { 'stdout': PIPE, 'stderr': PIPE,
326
                     'mncmd':
327
                     [ 'mnexec', '-da', str( self.pid ) ] }
328
        defaults.update( kwargs )
329
        if len( args ) == 1:
330
            if type( args[ 0 ] ) is list:
331
                # popen([cmd, arg1, arg2...])
332
                cmd = args[ 0 ]
333
            elif type( args[ 0 ] ) is str:
334
                # popen("cmd arg1 arg2...")
335
                cmd = args[ 0 ].split()
336
            else:
337
                raise Exception( 'popen() requires a string or list' )
338
        elif len( args ) > 0:
339
            # popen( cmd, arg1, arg2... )
340
            cmd = list( args )
341
        # Attach to our namespace  using mnexec -a
342
        cmd = defaults.pop( 'mncmd' ) + cmd
343
        # Shell requires a string, not a list!
344
        if defaults.get( 'shell', False ):
345
            cmd = ' '.join( cmd )
346
        popen = self._popen( cmd, **defaults )
347
        return popen
348

    
349
    def pexec( self, *args, **kwargs ):
350
        """Execute a command using popen
351
           returns: out, err, exitcode"""
352
        popen = self.popen( *args, stdin=PIPE, stdout=PIPE, stderr=PIPE,
353
                           **kwargs )
354
        # Warning: this can fail with large numbers of fds!
355
        out, err = popen.communicate()
356
        exitcode = popen.wait()
357
        return out, err, exitcode
358

    
359
    # Interface management, configuration, and routing
360

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

    
367
    def newPort( self ):
368
        "Return the next port number to allocate."
369
        if len( self.ports ) > 0:
370
            return max( self.ports.values() ) + 1
371
        return self.portBase
372

    
373
    def addIntf( self, intf, port=None, moveIntfFn=moveIntf ):
374
        """Add an interface.
375
           intf: interface
376
           port: port number (optional, typically OpenFlow port number)
377
           moveIntfFn: function to move interface (optional)"""
378
        if port is None:
379
            port = self.newPort()
380
        self.intfs[ port ] = intf
381
        self.ports[ intf ] = port
382
        self.nameToIntf[ intf.name ] = intf
383
        debug( '\n' )
384
        debug( 'added intf %s (%d) to node %s\n' % (
385
                intf, port, self.name ) )
386
        if self.inNamespace:
387
            debug( 'moving', intf, 'into namespace for', self.name, '\n' )
388
            moveIntfFn( intf.name, self  )
389

    
390
    def defaultIntf( self ):
391
        "Return interface for lowest port"
392
        ports = self.intfs.keys()
393
        if ports:
394
            return self.intfs[ min( ports ) ]
395
        else:
396
            warn( '*** defaultIntf: warning:', self.name,
397
                  'has no interfaces\n' )
398

    
399
    def intf( self, intf='' ):
400
        """Return our interface object with given string name,
401
           default intf if name is falsy (None, empty string, etc).
402
           or the input intf arg.
403

404
        Having this fcn return its arg for Intf objects makes it
405
        easier to construct functions with flexible input args for
406
        interfaces (those that accept both string names and Intf objects).
407
        """
408
        if not intf:
409
            return self.defaultIntf()
410
        elif type( intf ) is str:
411
            return self.nameToIntf[ intf ]
412
        else:
413
            return intf
414

    
415
    def connectionsTo( self, node):
416
        "Return [ intf1, intf2... ] for all intfs that connect self to node."
417
        # We could optimize this if it is important
418
        connections = []
419
        for intf in self.intfList():
420
            link = intf.link
421
            if link:
422
                node1, node2 = link.intf1.node, link.intf2.node
423
                if node1 == self and node2 == node:
424
                    connections += [ ( intf, link.intf2 ) ]
425
                elif node1 == node and node2 == self:
426
                    connections += [ ( intf, link.intf1 ) ]
427
        return connections
428

    
429
    def deleteIntfs( self, checkName=True ):
430
        """Delete all of our interfaces.
431
           checkName: only delete interfaces that contain our name"""
432
        # In theory the interfaces should go away after we shut down.
433
        # However, this takes time, so we're better off removing them
434
        # explicitly so that we won't get errors if we run before they
435
        # have been removed by the kernel. Unfortunately this is very slow,
436
        # at least with Linux kernels before 2.6.33
437
        for intf in self.intfs.values():
438
            # Protect against deleting hardware interfaces
439
            if ( self.name in intf.name ) or ( not checkName ):
440
                intf.delete()
441
                info( '.' )
442

    
443
    # Routing support
444

    
445
    def setARP( self, ip, mac ):
446
        """Add an ARP entry.
447
           ip: IP address as string
448
           mac: MAC address as string"""
449
        result = self.cmd( 'arp', '-s', ip, mac )
450
        return result
451

    
452
    def setHostRoute( self, ip, intf ):
453
        """Add route to host.
454
           ip: IP address as dotted decimal
455
           intf: string, interface name"""
456
        return self.cmd( 'route add -host', ip, 'dev', intf )
457

    
458
    def setDefaultRoute( self, intf=None ):
459
        """Set the default route to go through intf.
460
           intf: Intf or {dev <intfname> via <gw-ip> ...}"""
461
        # Note setParam won't call us if intf is none
462
        if type( intf ) is str and ' ' in intf:
463
            params = intf
464
        else:
465
            params = 'dev %s' % intf
466
        self.cmd( 'ip route del default' )
467
        return self.cmd( 'ip route add default', params )
468

    
469
    # Convenience and configuration methods
470

    
471
    def setMAC( self, mac, intf=None ):
472
        """Set the MAC address for an interface.
473
           intf: intf or intf name
474
           mac: MAC address as string"""
475
        return self.intf( intf ).setMAC( mac )
476

    
477
    def setIP( self, ip, prefixLen=8, intf=None ):
478
        """Set the IP address for an interface.
479
           intf: intf or intf name
480
           ip: IP address as a string
481
           prefixLen: prefix length, e.g. 8 for /8 or 16M addrs"""
482
        # This should probably be rethought
483
        if '/' not in ip:
484
            ip = '%s/%s' % ( ip, prefixLen )
485
        return self.intf( intf ).setIP( ip )
486

    
487
    def IP( self, intf=None ):
488
        "Return IP address of a node or specific interface."
489
        return self.intf( intf ).IP()
490

    
491
    def MAC( self, intf=None ):
492
        "Return MAC address of a node or specific interface."
493
        return self.intf( intf ).MAC()
494

    
495
    def intfIsUp( self, intf=None ):
496
        "Check if an interface is up."
497
        return self.intf( intf ).isUp()
498

    
499
    # The reason why we configure things in this way is so
500
    # That the parameters can be listed and documented in
501
    # the config method.
502
    # Dealing with subclasses and superclasses is slightly
503
    # annoying, but at least the information is there!
504

    
505
    def setParam( self, results, method, **param ):
506
        """Internal method: configure a *single* parameter
507
           results: dict of results to update
508
           method: config method name
509
           param: arg=value (ignore if value=None)
510
           value may also be list or dict"""
511
        name, value = param.items()[ 0 ]
512
        f = getattr( self, method, None )
513
        if not f:
514
            return
515
        if type( value ) is list:
516
            result = f( *value )
517
        elif type( value ) is dict:
518
            result = f( **value )
519
        else:
520
            result = f( value )
521
        results[ name ] = result
522
        return result
523

    
524
    def config( self, mac=None, ip=None,
525
                defaultRoute=None, lo='up', **_params ):
526
        """Configure Node according to (optional) parameters:
527
           mac: MAC address for default interface
528
           ip: IP address for default interface
529
           ifconfig: arbitrary interface configuration
530
           Subclasses should override this method and call
531
           the parent class's config(**params)"""
532
        # If we were overriding this method, we would call
533
        # the superclass config method here as follows:
534
        # r = Parent.config( **_params )
535
        r = {}
536
        self.setParam( r, 'setMAC', mac=mac )
537
        self.setParam( r, 'setIP', ip=ip )
538
        self.setParam( r, 'setDefaultRoute', defaultRoute=defaultRoute )
539
        # This should be examined
540
        self.cmd( 'ifconfig lo ' + lo )
541
        return r
542

    
543
    def configDefault( self, **moreParams ):
544
        "Configure with default parameters"
545
        self.params.update( moreParams )
546
        self.config( **self.params )
547

    
548
    # This is here for backward compatibility
549
    def linkTo( self, node, link=Link ):
550
        """(Deprecated) Link to another node
551
           replace with Link( node1, node2)"""
552
        return link( self, node )
553

    
554
    # Other methods
555

    
556
    def intfList( self ):
557
        "List of our interfaces sorted by port number"
558
        return [ self.intfs[ p ] for p in sorted( self.intfs.iterkeys() ) ]
559

    
560
    def intfNames( self ):
561
        "The names of our interfaces sorted by port number"
562
        return [ str( i ) for i in self.intfList() ]
563

    
564
    def __repr__( self ):
565
        "More informative string representation"
566
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
567
                              for i in self.intfList() ] ) )
568
        return '<%s %s: %s pid=%s> ' % (
569
            self.__class__.__name__, self.name, intfs, self.pid )
570

    
571
    def __str__( self ):
572
        "Abbreviated string representation"
573
        return self.name
574

    
575
    # Automatic class setup support
576

    
577
    isSetup = False
578

    
579
    @classmethod
580
    def checkSetup( cls ):
581
        "Make sure our class and superclasses are set up"
582
        while cls and not getattr( cls, 'isSetup', True ):
583
            cls.setup()
584
            cls.isSetup = True
585
            # Make pylint happy
586
            cls = getattr( type( cls ), '__base__', None )
587

    
588
    @classmethod
589
    def setup( cls ):
590
        "Make sure our class dependencies are available"
591
        pathCheck( 'mnexec', 'ifconfig', moduleName='Mininet')
592

    
593

    
594
class Host( Node ):
595
    "A host is simply a Node"
596
    pass
597

    
598

    
599
class CPULimitedHost( Host ):
600

    
601
    "CPU limited host"
602

    
603
    def __init__( self, name, sched='cfs', **kwargs ):
604
        Host.__init__( self, name, **kwargs )
605
        # Initialize class if necessary
606
        if not CPULimitedHost.inited:
607
            CPULimitedHost.init()
608
        # Create a cgroup and move shell into it
609
        self.cgroup = 'cpu,cpuacct,cpuset:/' + self.name
610
        errFail( 'cgcreate -g ' + self.cgroup )
611
        # We don't add ourselves to a cpuset because you must
612
        # specify the cpu and memory placement first
613
        errFail( 'cgclassify -g cpu,cpuacct:/%s %s' % ( self.name, self.pid ) )
614
        # BL: Setting the correct period/quota is tricky, particularly
615
        # for RT. RT allows very small quotas, but the overhead
616
        # seems to be high. CFS has a mininimum quota of 1 ms, but
617
        # still does better with larger period values.
618
        self.period_us = kwargs.get( 'period_us', 100000 )
619
        self.sched = sched
620
        if self.sched == 'rt':
621
            release = quietRun( 'uname -r' ).strip('\r\n')
622
            output = quietRun( 'grep CONFIG_RT_GROUP_SCHED /boot/config-%s' % release )
623
            if output == '# CONFIG_RT_GROUP_SCHED is not set\n':
624
                error( '\n*** error: please enable RT_GROUP_SCHED in your kernel\n' )
625
                exit( 1 )
626
        self.rtprio = 20
627

    
628
    def cgroupSet( self, param, value, resource='cpu' ):
629
        "Set a cgroup parameter and return its value"
630
        cmd = 'cgset -r %s.%s=%s /%s' % (
631
            resource, param, value, self.name )
632
        quietRun( cmd )
633
        nvalue = int( self.cgroupGet( param, resource ) )
634
        if nvalue != value:
635
            error( '*** error: cgroupSet: %s set to %s instead of %s\n'
636
                   % ( param, nvalue, value ) )
637
        return nvalue
638

    
639
    def cgroupGet( self, param, resource='cpu' ):
640
        "Return value of cgroup parameter"
641
        cmd = 'cgget -r %s.%s /%s' % (
642
            resource, param, self.name )
643
        return int( quietRun( cmd ).split()[ -1 ] )
644

    
645
    def cgroupDel( self ):
646
        "Clean up our cgroup"
647
        # info( '*** deleting cgroup', self.cgroup, '\n' )
648
        _out, _err, exitcode = errRun( 'cgdelete -r ' + self.cgroup )
649
        return exitcode != 0
650

    
651
    def popen( self, *args, **kwargs ):
652
        """Return a Popen() object in node's namespace
653
           args: Popen() args, single list, or string
654
           kwargs: Popen() keyword args"""
655
        # Tell mnexec to execute command in our cgroup
656
        mncmd = [ 'mnexec', '-g', self.name,
657
                  '-da', str( self.pid ) ]
658
        cpuTime = int( self.cgroupGet( 'rt_runtime_us', 'cpu' ) )
659
        # if our cgroup is not given any cpu time,
660
        # we cannot assign the RR Scheduler.
661
        if self.sched == 'rt' and cpuTime > 0:
662
            mncmd += [ '-r', str( self.rtprio ) ]
663
        elif self.sched == 'rt' and cpuTime <= 0:
664
            debug( '***error: not enough cpu time available for %s.' % self.name,
665
                    'Using cfs scheduler for subprocess\n' )
666
        return Host.popen( self, *args, mncmd=mncmd, **kwargs )
667

    
668
    def cleanup( self ):
669
        "Clean up Node, then clean up our cgroup"
670
        super( CPULimitedHost, self ).cleanup()
671
        retry( retries=3, delaySecs=1, fn=self.cgroupDel )
672

    
673
    def chrt( self ):
674
        "Set RT scheduling priority"
675
        quietRun( 'chrt -p %s %s' % ( self.rtprio, self.pid ) )
676
        result = quietRun( 'chrt -p %s' % self.pid )
677
        firstline = result.split( '\n' )[ 0 ]
678
        lastword = firstline.split( ' ' )[ -1 ]
679
        if lastword != 'SCHED_RR':
680
            error( '*** error: could not assign SCHED_RR to %s\n' % self.name )
681
        return lastword
682

    
683
    def rtInfo( self, f ):
684
        "Internal method: return parameters for RT bandwidth"
685
        pstr, qstr = 'rt_period_us', 'rt_runtime_us'
686
        # RT uses wall clock time for period and quota
687
        quota = int( self.period_us * f )
688
        return pstr, qstr, self.period_us, quota
689

    
690
    def cfsInfo( self, f):
691
        "Internal method: return parameters for CFS bandwidth"
692
        pstr, qstr = 'cfs_period_us', 'cfs_quota_us'
693
        # CFS uses wall clock time for period and CPU time for quota.
694
        quota = int( self.period_us * f * numCores() )
695
        period = self.period_us
696
        if f > 0 and quota < 1000:
697
            debug( '(cfsInfo: increasing default period) ' )
698
            quota = 1000
699
            period = int( quota / f / numCores() )
700
        return pstr, qstr, period, quota
701

    
702
    # BL comment:
703
    # This may not be the right API,
704
    # since it doesn't specify CPU bandwidth in "absolute"
705
    # units the way link bandwidth is specified.
706
    # We should use MIPS or SPECINT or something instead.
707
    # Alternatively, we should change from system fraction
708
    # to CPU seconds per second, essentially assuming that
709
    # all CPUs are the same.
710

    
711
    def setCPUFrac( self, f=-1, sched=None):
712
        """Set overall CPU fraction for this host
713
           f: CPU bandwidth limit (fraction)
714
           sched: 'rt' or 'cfs'
715
           Note 'cfs' requires CONFIG_CFS_BANDWIDTH"""
716
        # if cpu fraction is None, reset to default of 0
717
        if not f:
718
            f = -1
719
        if not sched:
720
            sched = self.sched
721
        if sched == 'rt':
722
            pstr, qstr, period, quota = self.rtInfo( f )
723
        elif sched == 'cfs':
724
            pstr, qstr, period, quota = self.cfsInfo( f )
725
        else:
726
            return
727
        if quota < 0:
728
            # Reset to unlimited
729
            quota = -1
730
        # Set cgroup's period and quota
731
        setPeriod = self.cgroupSet( pstr, period )
732
        setQuota = self.cgroupSet( qstr, quota )
733
        if sched == 'rt':
734
            # Set RT priority if necessary
735
            sched = self.chrt()
736
        info( '(%s %d/%dus) ' % ( sched, setQuota, setPeriod ) )
737

    
738
    def setCPUs( self, cores, mems=0 ):
739
        "Specify (real) cores that our cgroup can run on"
740
        if not cores:
741
            return
742
        if type( cores ) is list:
743
            cores = ','.join( [ str( c ) for c in cores ] )
744
        self.cgroupSet( resource='cpuset', param='cpus',
745
                        value=cores )
746
        # Memory placement is probably not relevant, but we
747
        # must specify it anyway
748
        self.cgroupSet( resource='cpuset', param='mems',
749
                        value=mems)
750
        # We have to do this here after we've specified
751
        # cpus and mems
752
        errFail( 'cgclassify -g cpuset:/%s %s' % (
753
                 self.name, self.pid ) )
754

    
755
    def config( self, cpu=-1, cores=None, **params ):
756
        """cpu: desired overall system CPU fraction
757
           cores: (real) core(s) this host can run on
758
           params: parameters for Node.config()"""
759
        r = Node.config( self, **params )
760
        # Was considering cpu={'cpu': cpu , 'sched': sched}, but
761
        # that seems redundant
762
        self.setParam( r, 'setCPUFrac', cpu=cpu )
763
        self.setParam( r, 'setCPUs', cores=cores )
764
        return r
765

    
766
    inited = False
767

    
768
    @classmethod
769
    def init( cls ):
770
        "Initialization for CPULimitedHost class"
771
        mountCgroups()
772
        cls.inited = True
773

    
774
class HostWithPrivateDirs( Host ):
775
    "Host with private directories"
776

    
777
    def __init__( self, name, *args, **kwargs ):
778
        "privateDirs: list of private directory strings or tuples"
779
        self.name = name
780
        self.privateDirs = kwargs.pop( 'privateDirs', [] )
781
        Host.__init__( self, name, *args, **kwargs )
782
        self.mountPrivateDirs()
783

    
784
    def mountPrivateDirs( self ):
785
        "mount private directories"
786
        for directory in self.privateDirs:
787
            if isinstance( directory, tuple ):
788
                # mount given private directory
789
                privateDir = directory[ 1 ] % self.__dict__ 
790
                mountPoint = directory[ 0 ]
791
                self.cmd( 'mkdir -p %s' % privateDir )
792
                self.cmd( 'mkdir -p %s' % mountPoint )
793
                self.cmd( 'mount --bind %s %s' %
794
                               ( privateDir, mountPoint ) )
795
            else:
796
                # mount temporary filesystem on directory
797
                self.cmd( 'mkdir -p %s' % directory ) 
798
                self.cmd( 'mount -n -t tmpfs tmpfs %s' % directory )
799

    
800

    
801

    
802
# Some important things to note:
803
#
804
# The "IP" address which setIP() assigns to the switch is not
805
# an "IP address for the switch" in the sense of IP routing.
806
# Rather, it is the IP address for the control interface,
807
# on the control network, and it is only relevant to the
808
# controller. If you are running in the root namespace
809
# (which is the only way to run OVS at the moment), the
810
# control interface is the loopback interface, and you
811
# normally never want to change its IP address!
812
#
813
# In general, you NEVER want to attempt to use Linux's
814
# network stack (i.e. ifconfig) to "assign" an IP address or
815
# MAC address to a switch data port. Instead, you "assign"
816
# the IP and MAC addresses in the controller by specifying
817
# packets that you want to receive or send. The "MAC" address
818
# reported by ifconfig for a switch data port is essentially
819
# meaningless. It is important to understand this if you
820
# want to create a functional router using OpenFlow.
821

    
822
class Switch( Node ):
823
    """A Switch is a Node that is running (or has execed?)
824
       an OpenFlow switch."""
825

    
826
    portBase = 1  # Switches start with port 1 in OpenFlow
827
    dpidLen = 16  # digits in dpid passed to switch
828

    
829
    def __init__( self, name, dpid=None, opts='', listenPort=None, **params):
830
        """dpid: dpid hex string (or None to derive from name, e.g. s1 -> 1)
831
           opts: additional switch options
832
           listenPort: port to listen on for dpctl connections"""
833
        Node.__init__( self, name, **params )
834
        self.dpid = self.defaultDpid( dpid )
835
        self.opts = opts
836
        self.listenPort = listenPort
837
        if not self.inNamespace:
838
            self.controlIntf = Intf( 'lo', self, port=0 )
839

    
840
    def defaultDpid( self, dpid=None ):
841
        "Return correctly formatted dpid from dpid or switch name (s1 -> 1)"
842
        if dpid:
843
            # Remove any colons and make sure it's a good hex number
844
            dpid = dpid.translate( None, ':' )
845
            assert len( dpid ) <= self.dpidLen and int( dpid, 16 ) >= 0
846
        else:
847
            # Use hex of the first number in the switch name
848
            nums = re.findall( r'\d+', self.name )
849
            if nums:
850
                dpid = hex( int( nums[ 0 ] ) )[ 2: ]
851
            else:
852
                raise Exception( 'Unable to derive default datapath ID - '
853
                                 'please either specify a dpid or use a '
854
                                 'canonical switch name such as s23.' )
855
        return '0' * ( self.dpidLen - len( dpid ) ) + dpid
856

    
857
    def defaultIntf( self ):
858
        "Return control interface"
859
        if self.controlIntf:
860
            return self.controlIntf
861
        else:
862
            return Node.defaultIntf( self )
863

    
864
    def sendCmd( self, *cmd, **kwargs ):
865
        """Send command to Node.
866
           cmd: string"""
867
        kwargs.setdefault( 'printPid', False )
868
        if not self.execed:
869
            return Node.sendCmd( self, *cmd, **kwargs )
870
        else:
871
            error( '*** Error: %s has execed and cannot accept commands' %
872
                   self.name )
873

    
874
    def connected( self ):
875
        "Is the switch connected to a controller? (override this method)"
876
        return False and self  # satisfy pylint
877

    
878
    def __repr__( self ):
879
        "More informative string representation"
880
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
881
                              for i in self.intfList() ] ) )
882
        return '<%s %s: %s pid=%s> ' % (
883
            self.__class__.__name__, self.name, intfs, self.pid )
884

    
885
class UserSwitch( Switch ):
886
    "User-space switch."
887

    
888
    dpidLen = 12
889

    
890
    def __init__( self, name, dpopts='--no-slicing', **kwargs ):
891
        """Init.
892
           name: name for the switch
893
           dpopts: additional arguments to ofdatapath (--no-slicing)"""
894
        Switch.__init__( self, name, **kwargs )
895
        pathCheck( 'ofdatapath', 'ofprotocol',
896
                   moduleName='the OpenFlow reference user switch' +
897
                              '(openflow.org)' )
898
        if self.listenPort:
899
            self.opts += ' --listen=ptcp:%i ' % self.listenPort
900
        else:
901
            self.opts += ' --listen=punix:/tmp/%s.listen' % self.name
902
        self.dpopts = dpopts
903

    
904
    @classmethod
905
    def setup( cls ):
906
        "Ensure any dependencies are loaded; if not, try to load them."
907
        if not os.path.exists( '/dev/net/tun' ):
908
            moduleDeps( add=TUN )
909

    
910
    def dpctl( self, *args ):
911
        "Run dpctl command"
912
        listenAddr = None
913
        if not self.listenPort:
914
            listenAddr = 'unix:/tmp/%s.listen' % self.name
915
        else:
916
            listenAddr = 'tcp:127.0.0.1:%i' % self.listenPort
917
        return self.cmd( 'dpctl ' + ' '.join( args ) +
918
                         ' ' + listenAddr )
919

    
920
    def connected( self ):
921
        "Is the switch connected to a controller?"
922
        status = self.dpctl( 'status' )
923
        return ( 'remote.is-connected=true' in status and
924
                 'local.is-connected=true' in status )
925

    
926
    @staticmethod
927
    def TCReapply( intf ):
928
        """Unfortunately user switch and Mininet are fighting
929
           over tc queuing disciplines. To resolve the conflict,
930
           we re-create the user switch's configuration, but as a
931
           leaf of the TCIntf-created configuration."""
932
        if type( intf ) is TCIntf:
933
            ifspeed = 10000000000 # 10 Gbps
934
            minspeed = ifspeed * 0.001
935

    
936
            res = intf.config( **intf.params )
937

    
938
            if res is None: # link may not have TC parameters
939
                return
940

    
941
            # Re-add qdisc, root, and default classes user switch created, but
942
            # with new parent, as setup by Mininet's TCIntf
943
            parent = res['parent']
944
            intf.tc( "%s qdisc add dev %s " + parent +
945
                     " handle 1: htb default 0xfffe" )
946
            intf.tc( "%s class add dev %s classid 1:0xffff parent 1: htb rate "
947
                     + str(ifspeed) )
948
            intf.tc( "%s class add dev %s classid 1:0xfffe parent 1:0xffff " +
949
                     "htb rate " + str(minspeed) + " ceil " + str(ifspeed) )
950

    
951
    def start( self, controllers ):
952
        """Start OpenFlow reference user datapath.
953
           Log to /tmp/sN-{ofd,ofp}.log.
954
           controllers: list of controller objects"""
955
        # Add controllers
956
        clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
957
                            for c in controllers ] )
958
        ofdlog = '/tmp/' + self.name + '-ofd.log'
959
        ofplog = '/tmp/' + self.name + '-ofp.log'
960
        intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
961
        self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
962
                  ' punix:/tmp/' + self.name + ' -d %s ' % self.dpid +
963
                  self.dpopts +
964
                  ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
965
        self.cmd( 'ofprotocol unix:/tmp/' + self.name +
966
                  ' ' + clist +
967
                  ' --fail=closed ' + self.opts +
968
                  ' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
969
        if "no-slicing" not in self.dpopts:
970
            # Only TCReapply if slicing is enable
971
            sleep(1) # Allow ofdatapath to start before re-arranging qdisc's
972
            for intf in self.intfList():
973
                if not intf.IP():
974
                    self.TCReapply( intf )
975

    
976
    def stop( self ):
977
        "Stop OpenFlow reference user datapath."
978
        self.cmd( 'kill %ofdatapath' )
979
        self.cmd( 'kill %ofprotocol' )
980
        self.deleteIntfs()
981

    
982

    
983
class OVSLegacyKernelSwitch( Switch ):
984
    """Open VSwitch legacy kernel-space switch using ovs-openflowd.
985
       Currently only works in the root namespace."""
986

    
987
    def __init__( self, name, dp=None, **kwargs ):
988
        """Init.
989
           name: name for switch
990
           dp: netlink id (0, 1, 2, ...)
991
           defaultMAC: default MAC as unsigned int; random value if None"""
992
        Switch.__init__( self, name, **kwargs )
993
        self.dp = dp if dp else self.name
994
        self.intf = self.dp
995
        if self.inNamespace:
996
            error( "OVSKernelSwitch currently only works"
997
                   " in the root namespace.\n" )
998
            exit( 1 )
999

    
1000
    @classmethod
1001
    def setup( cls ):
1002
        "Ensure any dependencies are loaded; if not, try to load them."
1003
        pathCheck( 'ovs-dpctl', 'ovs-openflowd',
1004
                   moduleName='Open vSwitch (openvswitch.org)')
1005
        moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
1006

    
1007
    def start( self, controllers ):
1008
        "Start up kernel datapath."
1009
        ofplog = '/tmp/' + self.name + '-ofp.log'
1010
        # Delete local datapath if it exists;
1011
        # then create a new one monitoring the given interfaces
1012
        self.cmd( 'ovs-dpctl del-dp ' + self.dp )
1013
        self.cmd( 'ovs-dpctl add-dp ' + self.dp )
1014
        intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
1015
        self.cmd( 'ovs-dpctl', 'add-if', self.dp, ' '.join( intfs ) )
1016
        # Run protocol daemon
1017
        clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
1018
                            for c in controllers ] )
1019
        self.cmd( 'ovs-openflowd ' + self.dp +
1020
                  ' ' + clist +
1021
                  ' --fail=secure ' + self.opts +
1022
                  ' --datapath-id=' + self.dpid +
1023
                  ' 1>' + ofplog + ' 2>' + ofplog + '&' )
1024
        self.execed = False
1025

    
1026
    def stop( self ):
1027
        "Terminate kernel datapath."
1028
        quietRun( 'ovs-dpctl del-dp ' + self.dp )
1029
        self.cmd( 'kill %ovs-openflowd' )
1030
        self.deleteIntfs()
1031

    
1032

    
1033
class OVSSwitch( Switch ):
1034
    "Open vSwitch switch. Depends on ovs-vsctl."
1035

    
1036
    def __init__( self, name, failMode='secure', datapath='kernel',
1037
                 inband=False, protocols=None, **params ):
1038
        """Init.
1039
           name: name for switch
1040
           failMode: controller loss behavior (secure|open)
1041
           datapath: userspace or kernel mode (kernel|user)
1042
           inband: use in-band control (False)"""
1043
        Switch.__init__( self, name, **params )
1044
        self.failMode = failMode
1045
        self.datapath = datapath
1046
        self.inband = inband
1047
        self.protocols = protocols
1048

    
1049
    @classmethod
1050
    def setup( cls ):
1051
        "Make sure Open vSwitch is installed and working"
1052
        pathCheck( 'ovs-vsctl',
1053
                   moduleName='Open vSwitch (openvswitch.org)')
1054
        # This should no longer be needed, and it breaks
1055
        # with OVS 1.7 which has renamed the kernel module:
1056
        #  moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
1057
        out, err, exitcode = errRun( 'ovs-vsctl -t 1 show' )
1058
        if exitcode:
1059
            error( out + err +
1060
                   'ovs-vsctl exited with code %d\n' % exitcode +
1061
                   '*** Error connecting to ovs-db with ovs-vsctl\n'
1062
                   'Make sure that Open vSwitch is installed, '
1063
                   'that ovsdb-server is running, and that\n'
1064
                   '"ovs-vsctl show" works correctly.\n'
1065
                   'You may wish to try '
1066
                   '"service openvswitch-switch start".\n' )
1067
            exit( 1 )
1068
        info = quietRun( 'ovs-vsctl --version' )
1069
        cls.OVSVersion =  findall( '\d+\.\d+', info )[ 0 ]
1070

    
1071
    @classmethod
1072
    def isOldOVS( cls ):
1073
        return ( StrictVersion( cls.OVSVersion ) <
1074
             StrictVersion( '1.10' ) )
1075

    
1076
    @classmethod
1077
    def batchShutdown( cls, switches ):
1078
        "Call ovs-vsctl del-br on all OVSSwitches in a list"
1079
        quietRun( 'ovs-vsctl ' +
1080
                  ' -- '.join( '--if-exists del-br %s' % s
1081
                               for s in switches ) )
1082

    
1083
    def dpctl( self, *args ):
1084
        "Run ovs-ofctl command"
1085
        return self.cmd( 'ovs-ofctl', args[ 0 ], self, *args[ 1: ] )
1086

    
1087
    @staticmethod
1088
    def TCReapply( intf ):
1089
        """Unfortunately OVS and Mininet are fighting
1090
           over tc queuing disciplines. As a quick hack/
1091
           workaround, we clear OVS's and reapply our own."""
1092
        if type( intf ) is TCIntf:
1093
            intf.config( **intf.params )
1094

    
1095
    def attach( self, intf ):
1096
        "Connect a data port"
1097
        self.cmd( 'ovs-vsctl add-port', self, intf )
1098
        self.cmd( 'ifconfig', intf, 'up' )
1099
        self.TCReapply( intf )
1100

    
1101
    def detach( self, intf ):
1102
        "Disconnect a data port"
1103
        self.cmd( 'ovs-vsctl del-port', self, intf )
1104

    
1105
    def controllerUUIDs( self ):
1106
        "Return ovsdb UUIDs for our controllers"
1107
        uuids = []
1108
        controllers = self.cmd( 'ovs-vsctl -- get Bridge', self,
1109
                               'Controller' ).strip()
1110
        if controllers.startswith( '[' ) and controllers.endswith( ']' ):
1111
            controllers = controllers[ 1 : -1 ]
1112
            uuids = [ c.strip() for c in controllers.split( ',' ) ]
1113
        return uuids
1114

    
1115
    def connected( self ):
1116
        "Are we connected to at least one of our controllers?"
1117
        results = [ 'true' in self.cmd( 'ovs-vsctl -- get Controller',
1118
                                         uuid, 'is_connected' )
1119
                    for uuid in self.controllerUUIDs() ]
1120
        return reduce( or_, results, False )
1121

    
1122
    def start( self, controllers ):
1123
        "Start up a new OVS OpenFlow switch using ovs-vsctl"
1124
        if self.inNamespace:
1125
            raise Exception(
1126
                'OVS kernel switch does not work in a namespace' )
1127
        # Annoyingly, --if-exists option seems not to work
1128
        self.cmd( 'ovs-vsctl del-br', self )
1129
        int( self.dpid, 16 ) # DPID must be a hex string
1130
        # Interfaces and controllers
1131
        intfs = ' '.join( '-- add-port %s %s ' % ( self, intf ) +
1132
                          '-- set Interface %s ' % intf +
1133
                          'ofport_request=%s ' % self.ports[ intf ]
1134
                         for intf in self.intfList()
1135
                         if self.ports[ intf ] and not intf.IP() )
1136
        clist = ' '.join( '%s:%s:%d' % ( c.protocol, c.IP(), c.port )
1137
                         for c in controllers )
1138
        if self.listenPort:
1139
            clist += ' ptcp:%s' % self.listenPort
1140
        # Construct big ovs-vsctl command for new versions of OVS
1141
        if not self.isOldOVS():
1142
            cmd = ( 'ovs-vsctl add-br %s ' % self +
1143
                    '-- set Bridge %s ' % self +
1144
                    'other_config:datapath-id=%s ' % self.dpid +
1145
                    '-- set-fail-mode %s %s ' % ( self, self.failMode ) +
1146
                    intfs +
1147
                    '-- set-controller %s %s ' % ( self, clist ) )
1148
        # Construct ovs-vsctl commands for old versions of OVS
1149
        else:
1150
            self.cmd( 'ovs-vsctl add-br', self )
1151
            for intf in self.intfList():
1152
                if not intf.IP():
1153
                    self.cmd( 'ovs-vsctl add-port', self, intf )
1154
            cmd = ( 'ovs-vsctl set Bridge %s ' % self +
1155
                    'other_config:datapath-id=%s ' % self.dpid +
1156
                    '-- set-fail-mode %s %s ' % ( self, self.failMode ) +
1157
                    '-- set-controller %s %s ' % ( self, clist ) )
1158
        if not self.inband:
1159
            cmd += ( '-- set bridge %s '
1160
                     'other-config:disable-in-band=true ' % self )
1161
        if self.datapath == 'user':
1162
            cmd += '-- set bridge %s datapath_type=netdev ' % self
1163
        if self.protocols:
1164
            cmd += '-- set bridge %s protocols=%s' % ( self, self.protocols )
1165
        # Reconnect quickly to controllers (1s vs. 15s max_backoff)
1166
        for uuid in self.controllerUUIDs():
1167
            if uuid.count( '-' ) != 4:
1168
                # Doesn't look like a UUID
1169
                continue
1170
            uuid = uuid.strip()
1171
            cmd += '-- set Controller %smax_backoff=1000 ' % uuid
1172
        # Do it!!
1173
        self.cmd( cmd )
1174
        for intf in self.intfList():
1175
            self.TCReapply( intf )
1176

    
1177

    
1178
    def stop( self ):
1179
        "Terminate OVS switch."
1180
        self.cmd( 'ovs-vsctl del-br', self )
1181
        if self.datapath == 'user':
1182
            self.cmd( 'ip link del', self )
1183
        self.deleteIntfs()
1184

    
1185
OVSKernelSwitch = OVSSwitch
1186

    
1187

    
1188
class IVSSwitch(Switch):
1189
    """IVS virtual switch"""
1190

    
1191
    def __init__( self, name, verbose=True, **kwargs ):
1192
        Switch.__init__( self, name, **kwargs )
1193
        self.verbose = verbose
1194

    
1195
    @classmethod
1196
    def setup( cls ):
1197
        "Make sure IVS is installed"
1198
        pathCheck( 'ivs-ctl', 'ivs',
1199
                   moduleName="Indigo Virtual Switch (projectfloodlight.org)" )
1200
        out, err, exitcode = errRun( 'ivs-ctl show' )
1201
        if exitcode:
1202
            error( out + err +
1203
                   'ivs-ctl exited with code %d\n' % exitcode +
1204
                   '*** The openvswitch kernel module might '
1205
                   'not be loaded. Try modprobe openvswitch.\n' )
1206
            exit( 1 )
1207

    
1208
    @classmethod
1209
    def batchShutdown( cls, switches ):
1210
        "Kill each IVS switch, to be waited on later in stop()"
1211
        for switch in switches:
1212
            switch.cmd( 'kill %ivs' )
1213

    
1214
    def start( self, controllers ):
1215
        "Start up a new IVS switch"
1216
        args = ['ivs']
1217
        args.extend( ['--name', self.name] )
1218
        args.extend( ['--dpid', self.dpid] )
1219
        if self.verbose:
1220
            args.extend( ['--verbose'] )
1221
        for intf in self.intfs.values():
1222
            if not intf.IP():
1223
                args.extend( ['-i', intf.name] )
1224
        for c in controllers:
1225
            args.extend( ['-c', '%s:%d' % (c.IP(), c.port)] )
1226
        if self.listenPort:
1227
            args.extend( ['--listen', '127.0.0.1:%i' % self.listenPort] )
1228
        args.append( self.opts )
1229

    
1230
        logfile = '/tmp/ivs.%s.log' % self.name
1231

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

    
1234
    def stop( self ):
1235
        "Terminate IVS switch."
1236
        self.cmd( 'kill %ivs' )
1237
        self.cmd( 'wait' )
1238
        self.deleteIntfs()
1239

    
1240
    def attach( self, intf ):
1241
        "Connect a data port"
1242
        self.cmd( 'ivs-ctl', 'add-port', '--datapath', self.name, intf )
1243

    
1244
    def detach( self, intf ):
1245
        "Disconnect a data port"
1246
        self.cmd( 'ivs-ctl', 'del-port', '--datapath', self.name, intf )
1247

    
1248
    def dpctl( self, *args ):
1249
        "Run dpctl command"
1250
        if not self.listenPort:
1251
            return "can't run dpctl without passive listening port"
1252
        return self.cmd( 'ovs-ofctl ' + ' '.join( args ) +
1253
                         ' tcp:127.0.0.1:%i' % self.listenPort )
1254

    
1255

    
1256
class Controller( Node ):
1257
    """A Controller is a Node that is running (or has execed?) an
1258
       OpenFlow controller."""
1259

    
1260
    def __init__( self, name, inNamespace=False, command='controller',
1261
                  cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
1262
                  port=6633, protocol='tcp', **params ):
1263
        self.command = command
1264
        self.cargs = cargs
1265
        self.cdir = cdir
1266
        self.ip = ip
1267
        self.port = port
1268
        self.protocol = protocol
1269
        Node.__init__( self, name, inNamespace=inNamespace,
1270
                       ip=ip, **params  )
1271
        self.checkListening()
1272

    
1273
    def checkListening( self ):
1274
        "Make sure no controllers are running on our port"
1275
        # Verify that Telnet is installed first:
1276
        out, _err, returnCode = errRun( "which telnet" )
1277
        if 'telnet' not in out or returnCode != 0:
1278
            raise Exception( "Error running telnet to check for listening "
1279
                             "controllers; please check that it is "
1280
                             "installed." )
1281
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1282
                              ( self.ip, self.port ) )
1283
        if 'Connected' in listening:
1284
            servers = self.cmd( 'netstat -natp' ).split( '\n' )
1285
            pstr = ':%d ' % self.port
1286
            clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ]
1287
            raise Exception( "Please shut down the controller which is"
1288
                             " running on port %d:\n" % self.port +
1289
                             '\n'.join( clist ) )
1290

    
1291
    def start( self ):
1292
        """Start <controller> <args> on controller.
1293
           Log to /tmp/cN.log"""
1294
        pathCheck( self.command )
1295
        cout = '/tmp/' + self.name + '.log'
1296
        if self.cdir is not None:
1297
            self.cmd( 'cd ' + self.cdir )
1298
        self.cmd( self.command + ' ' + self.cargs % self.port +
1299
                  ' 1>' + cout + ' 2>' + cout + ' &' )
1300
        self.execed = False
1301

    
1302
    def stop( self ):
1303
        "Stop controller."
1304
        self.cmd( 'kill %' + self.command )
1305
        self.cmd( 'wait %' + self.command )
1306
        self.terminate()
1307

    
1308
    def IP( self, intf=None ):
1309
        "Return IP address of the Controller"
1310
        if self.intfs:
1311
            ip = Node.IP( self, intf )
1312
        else:
1313
            ip = self.ip
1314
        return ip
1315

    
1316
    def __repr__( self ):
1317
        "More informative string representation"
1318
        return '<%s %s: %s:%s pid=%s> ' % (
1319
            self.__class__.__name__, self.name,
1320
            self.IP(), self.port, self.pid )
1321
    @classmethod
1322
    def isAvailable( self ):
1323
        return quietRun( 'which controller' )
1324

    
1325
class OVSController( Controller ):
1326
    "Open vSwitch controller"
1327
    def __init__( self, name, command='ovs-controller', **kwargs ):
1328
        if quietRun( 'which test-controller' ):
1329
            command = 'test-controller'
1330
        Controller.__init__( self, name, command=command, **kwargs )
1331
    @classmethod
1332
    def isAvailable( self ):
1333
        return quietRun( 'which ovs-controller' ) or quietRun( 'which test-controller' )
1334

    
1335
class NOX( Controller ):
1336
    "Controller to run a NOX application."
1337

    
1338
    def __init__( self, name, *noxArgs, **kwargs ):
1339
        """Init.
1340
           name: name to give controller
1341
           noxArgs: arguments (strings) to pass to NOX"""
1342
        if not noxArgs:
1343
            warn( 'warning: no NOX modules specified; '
1344
                  'running packetdump only\n' )
1345
            noxArgs = [ 'packetdump' ]
1346
        elif type( noxArgs ) not in ( list, tuple ):
1347
            noxArgs = [ noxArgs ]
1348

    
1349
        if 'NOX_CORE_DIR' not in os.environ:
1350
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
1351
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
1352

    
1353
        Controller.__init__( self, name,
1354
                             command=noxCoreDir + '/nox_core',
1355
                             cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
1356
                             ' '.join( noxArgs ),
1357
                             cdir=noxCoreDir,
1358
                             **kwargs )
1359

    
1360
class RYU( Controller ):
1361
    "Controller to run Ryu application"
1362
    def __init__( self, name, *ryuArgs, **kwargs ):
1363
        """Init.
1364
        name: name to give controller.
1365
        ryuArgs: arguments and modules to pass to Ryu"""
1366
        homeDir = quietRun( 'printenv HOME' ).strip( '\r\n' )
1367
        ryuCoreDir = '%s/ryu/ryu/app/' % homeDir
1368
        if not ryuArgs:
1369
            warn( 'warning: no Ryu modules specified; '
1370
                  'running simple_switch only\n' )
1371
            ryuArgs = [ ryuCoreDir + 'simple_switch.py' ]
1372
        elif type( ryuArgs ) not in ( list, tuple ):
1373
            ryuArgs = [ ryuArgs ]
1374

    
1375
        Controller.__init__( self, name,
1376
                         command='ryu-manager',
1377
                         cargs='--ofp-tcp-listen-port %s ' + 
1378
                         ' '.join( ryuArgs ),
1379
                         cdir=ryuCoreDir,
1380
                         **kwargs )
1381

    
1382
class RemoteController( Controller ):
1383
    "Controller running outside of Mininet's control."
1384

    
1385
    def __init__( self, name, ip='127.0.0.1',
1386
                  port=6633, **kwargs):
1387
        """Init.
1388
           name: name to give controller
1389
           ip: the IP address where the remote controller is
1390
           listening
1391
           port: the port where the remote controller is listening"""
1392
        Controller.__init__( self, name, ip=ip, port=port, **kwargs )
1393

    
1394
    def start( self ):
1395
        "Overridden to do nothing."
1396
        return
1397

    
1398
    def stop( self ):
1399
        "Overridden to do nothing."
1400
        return
1401

    
1402
    def checkListening( self ):
1403
        "Warn if remote controller is not accessible"
1404
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1405
                              ( self.ip, self.port ) )
1406
        if 'Connected' not in listening:
1407
            warn( "Unable to contact the remote controller"
1408
                  " at %s:%d\n" % ( self.ip, self.port ) )
1409

    
1410
def DefaultController( name, order=[ Controller, OVSController ], **kwargs ):
1411
    "find any controller that is available and run it"
1412
    for controller in order:
1413
        if controller.isAvailable():
1414
            return controller( name, **kwargs )