Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ bdd43bea

History | View | Annotate | Download (41.3 KB)

1 7d4b7b7f Bob Lantz
"""
2
Node objects for Mininet.
3

4 6d72a3cb Bob Lantz
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 7d4b7b7f Bob Lantz

8 6d72a3cb Bob Lantz
Node: superclass for all (primarily local) network nodes.
9 7d4b7b7f Bob Lantz

10 31b43002 Bob Lantz
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 7d4b7b7f Bob Lantz

16 0dbfd3a6 Bob Lantz
CPULimitedHost: a virtual host whose CPU bandwidth is limited by
17
    RT or CFS bandwidth limiting.
18

19 7d4b7b7f Bob Lantz
Switch: superclass for switch nodes.
20

21
UserSwitch: a switch using the user-space switch from the OpenFlow
22
    reference implementation.
23

24
KernelSwitch: a switch using the kernel switch from the OpenFlow reference
25
    implementation.
26

27
OVSSwitch: a switch using the OpenVSwitch OpenFlow-compatible switch
28
    implementation (openvswitch.org).
29

30
Controller: superclass for OpenFlow controllers. The default controller
31
    is controller(8) from the reference implementation.
32

33
NOXController: a controller node using NOX (noxrepo.org).
34

35
RemoteController: a remote controller node, which may use any
36 7c371cf3 Bob Lantz
    arbitrary OpenFlow-compatible controller, and which is not
37
    created or managed by mininet.
38 31b43002 Bob Lantz

39 80be5642 Bob Lantz
Future enhancements:
40

41
- Possibly make Node, Switch and Controller more abstract so that
42
  they can be used for both local and remote nodes
43

44
- Create proxy objects for remote nodes (Mininet: Cluster Edition)
45 7d4b7b7f Bob Lantz
"""
46 89bf3103 Brandon Heller
47 723d068c Brandon Heller
import os
48 75d72d96 Bob Lantz
import re
49 723d068c Brandon Heller
import signal
50
import select
51 75d72d96 Bob Lantz
from subprocess import Popen, PIPE, STDOUT
52 60d9ead6 David Erickson
53 2db4268b Bob Lantz
from mininet.log import info, error, warn, debug
54 197b083f Bob Lantz
from mininet.util import ( quietRun, errRun, errFail, moveIntf, isShellBuiltin,
55 edf60032 Brandon Heller
                           numCores, retry, mountCgroups )
56 f0010171 Bob Lantz
from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN
57 1aec55d9 Bob Lantz
from mininet.link import Link, Intf, TCIntf
58 2fffa0bb Brandon Heller
59 80a8fa62 Bob Lantz
class Node( object ):
60
    """A virtual network node is simply a shell in a network namespace.
61
       We communicate with it using pipes."""
62 6d72a3cb Bob Lantz
63 dd159b4a Bob Lantz
    portBase = 0  # Nodes always start with eth0/port0, even in OF 1.0
64 54d026f6 Bob Lantz
65 84a91a14 Bob Lantz
    def __init__( self, name, inNamespace=True, **params ):
66 086ef80e Bob Lantz
        """name: name of node
67
           inNamespace: in network namespace?
68 84a91a14 Bob Lantz
           params: Node parameters (see config() for details)"""
69
70
        # Make sure class actually works
71
        self.checkSetup()
72
73 89bf3103 Brandon Heller
        self.name = name
74
        self.inNamespace = inNamespace
75 84a91a14 Bob Lantz
76
        # Stash configuration parameters for future reference
77
        self.params = params
78
79
        self.intfs = {}  # dict of port numbers to interfaces
80
        self.ports = {}  # dict of interfaces to port numbers
81
                         # replace with Port objects, eventually ?
82
        self.nameToIntf = {}  # dict of interface names to Intfs
83
84 14ff3ad3 Bob Lantz
        # Make pylint happy
85
        ( self.shell, self.execed, self.pid, self.stdin, self.stdout,
86 33d548b4 Brandon Heller
            self.lastPid, self.lastCmd, self.pollOut ) = (
87
                None, None, None, None, None, None, None, None )
88 14ff3ad3 Bob Lantz
        self.waiting = False
89
        self.readbuf = ''
90
91 84a91a14 Bob Lantz
        # Start command interpreter shell
92
        self.startShell()
93
94
    # File descriptor to node mapping support
95
    # Class variables and methods
96
97
    inToNode = {}  # mapping of input fds to nodes
98
    outToNode = {}  # mapping of output fds to nodes
99
100
    @classmethod
101
    def fdToNode( cls, fd ):
102
        """Return node corresponding to given file descriptor.
103
           fd: file descriptor
104
           returns: node"""
105
        node = cls.outToNode.get( fd )
106
        return node or cls.inToNode.get( fd )
107
108
    # Command support via shell process in namespace
109
110
    def startShell( self ):
111
        "Start a shell process for running commands"
112
        if self.shell:
113
            error( "%s: shell is already running" )
114
            return
115 216a4b7c Bob Lantz
        # mnexec: (c)lose descriptors, (d)etach from tty,
116 14ff3ad3 Bob Lantz
        # (p)rint pid, and run in (n)amespace
117 121eb449 Bob Lantz
        opts = '-cdp'
118 89bf3103 Brandon Heller
        if self.inNamespace:
119 b14b1ee4 Bob Lantz
            opts += 'n'
120 216a4b7c Bob Lantz
        # bash -m: enable job control
121 1bf1a4d5 Bob Lantz
        # -s: pass $* to shell, and make process easy to find in ps
122
        cmd = [ 'mnexec', opts, 'bash', '-ms', 'mininet:' + self.name ]
123 80a8fa62 Bob Lantz
        self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT,
124 edf60032 Brandon Heller
                            close_fds=True )
125 89bf3103 Brandon Heller
        self.stdin = self.shell.stdin
126
        self.stdout = self.shell.stdout
127 121eb449 Bob Lantz
        self.pid = self.shell.pid
128 89bf3103 Brandon Heller
        self.pollOut = select.poll()
129 80a8fa62 Bob Lantz
        self.pollOut.register( self.stdout )
130 89bf3103 Brandon Heller
        # Maintain mapping between file descriptors and nodes
131 bcacfc05 Bob Lantz
        # This is useful for monitoring multiple nodes
132 89bf3103 Brandon Heller
        # using select.poll()
133 80a8fa62 Bob Lantz
        self.outToNode[ self.stdout.fileno() ] = self
134
        self.inToNode[ self.stdin.fileno() ] = self
135 89bf3103 Brandon Heller
        self.execed = False
136 bcacfc05 Bob Lantz
        self.lastCmd = None
137
        self.lastPid = None
138 ec7b211c Bob Lantz
        self.readbuf = ''
139 bcacfc05 Bob Lantz
        self.waiting = False
140 89bf3103 Brandon Heller
141 80a8fa62 Bob Lantz
    def cleanup( self ):
142
        "Help python collect its garbage."
143 28833d86 Bob Lantz
        if not self.inNamespace:
144
            for intfName in self.intfNames():
145
                if self.name in intfName:
146
                    quietRun( 'ip link del ' + intfName )
147 89bf3103 Brandon Heller
        self.shell = None
148
149
    # Subshell I/O, commands and control
150 163a6cf3 Bob Lantz
151 28f46c8d Bob Lantz
    def read( self, maxbytes=1024 ):
152 ec7b211c Bob Lantz
        """Buffered read from node, non-blocking.
153 28f46c8d Bob Lantz
           maxbytes: maximum number of bytes to return"""
154 ec7b211c Bob Lantz
        count = len( self.readbuf )
155 28f46c8d Bob Lantz
        if count < maxbytes:
156
            data = os.read( self.stdout.fileno(), maxbytes - count )
157 ec7b211c Bob Lantz
            self.readbuf += data
158 28f46c8d Bob Lantz
        if maxbytes >= len( self.readbuf ):
159 ec7b211c Bob Lantz
            result = self.readbuf
160
            self.readbuf = ''
161
        else:
162 28f46c8d Bob Lantz
            result = self.readbuf[ :maxbytes ]
163
            self.readbuf = self.readbuf[ maxbytes: ]
164 ec7b211c Bob Lantz
        return result
165
166
    def readline( self ):
167
        """Buffered readline from node, non-blocking.
168
           returns: line (minus newline) or None"""
169
        self.readbuf += self.read( 1024 )
170
        if '\n' not in self.readbuf:
171
            return None
172
        pos = self.readbuf.find( '\n' )
173 0bd5c651 Brandon Heller
        line = self.readbuf[ 0: pos ]
174 ec7b211c Bob Lantz
        self.readbuf = self.readbuf[ pos + 1: ]
175
        return line
176 80a8fa62 Bob Lantz
177
    def write( self, data ):
178
        """Write data to node.
179
           data: string"""
180
        os.write( self.stdin.fileno(), data )
181
182
    def terminate( self ):
183 6d72a3cb Bob Lantz
        "Send kill signal to Node and clean up after it."
184 80a8fa62 Bob Lantz
        os.kill( self.pid, signal.SIGKILL )
185 89bf3103 Brandon Heller
        self.cleanup()
186
187 80a8fa62 Bob Lantz
    def stop( self ):
188
        "Stop node."
189 89bf3103 Brandon Heller
        self.terminate()
190
191 f24e70a4 Bob Lantz
    def waitReadable( self, timeoutms=None ):
192
        """Wait until node's output is readable.
193
           timeoutms: timeout in ms or None to wait indefinitely."""
194 ec7b211c Bob Lantz
        if len( self.readbuf ) == 0:
195 f24e70a4 Bob Lantz
            self.pollOut.poll( timeoutms )
196 89bf3103 Brandon Heller
197 121eb449 Bob Lantz
    def sendCmd( self, *args, **kwargs ):
198 80a8fa62 Bob Lantz
        """Send a command, followed by a command to echo a sentinel,
199 121eb449 Bob Lantz
           and return without waiting for the command to complete.
200
           args: command and arguments, or string
201
           printPid: print command's PID?"""
202 89bf3103 Brandon Heller
        assert not self.waiting
203 121eb449 Bob Lantz
        printPid = kwargs.get( 'printPid', True )
204 318ae55e Bob Lantz
        # Allow sendCmd( [ list ] )
205
        if len( args ) == 1 and type( args[ 0 ] ) is list:
206
            cmd = args[ 0 ]
207
        # Allow sendCmd( cmd, arg1, arg2... )
208
        elif len( args ) > 0:
209 121eb449 Bob Lantz
            cmd = args
210 318ae55e Bob Lantz
        # Convert to string
211 121eb449 Bob Lantz
        if not isinstance( cmd, str ):
212 a6bcad8f Bob Lantz
            cmd = ' '.join( [ str( c ) for c in cmd ] )
213 f24e70a4 Bob Lantz
        if not re.search( r'\w', cmd ):
214
            # Replace empty commands with something harmless
215
            cmd = 'echo -n'
216 d1b29d58 Bob Lantz
        self.lastCmd = cmd
217
        printPid = printPid and not isShellBuiltin( cmd )
218 c6e7eaf0 Bob Lantz
        if len( cmd ) > 0 and cmd[ -1 ] == '&':
219 d1b29d58 Bob Lantz
            # print ^A{pid}\n{sentinel}
220
            cmd += ' printf "\\001%d\n\\177" $! \n'
221 89bf3103 Brandon Heller
        else:
222 d1b29d58 Bob Lantz
            # print sentinel
223
            cmd += '; printf "\\177"'
224 bcacfc05 Bob Lantz
            if printPid and not isShellBuiltin( cmd ):
225
                cmd = 'mnexec -p ' + cmd
226 612b21cb Bob Lantz
        self.write( cmd + '\n' )
227 bcacfc05 Bob Lantz
        self.lastPid = None
228 89bf3103 Brandon Heller
        self.waiting = True
229
230 ec7b211c Bob Lantz
    def sendInt( self, sig=signal.SIGINT ):
231 bcacfc05 Bob Lantz
        "Interrupt running command."
232
        if self.lastPid:
233 b5672f15 Bob Lantz
            try:
234
                os.kill( self.lastPid, sig )
235 82b72072 Bob Lantz
            except OSError:
236 b5672f15 Bob Lantz
                pass
237 4065511a Bob Lantz
238 f24e70a4 Bob Lantz
    def monitor( self, timeoutms=None ):
239 e4c82e52 Bob Lantz
        """Monitor and return the output of a command.
240 f24e70a4 Bob Lantz
           Set self.waiting to False if command has completed.
241
           timeoutms: timeout in ms or None to wait indefinitely."""
242
        self.waitReadable( timeoutms )
243 80a8fa62 Bob Lantz
        data = self.read( 1024 )
244 bcacfc05 Bob Lantz
        # Look for PID
245
        marker = chr( 1 ) + r'\d+\n'
246
        if chr( 1 ) in data:
247
            markers = re.findall( marker, data )
248
            if markers:
249
                self.lastPid = int( markers[ 0 ][ 1: ] )
250
                data = re.sub( marker, '', data )
251
        # Look for sentinel/EOF
252 4065511a Bob Lantz
        if len( data ) > 0 and data[ -1 ] == chr( 127 ):
253 89bf3103 Brandon Heller
            self.waiting = False
254 e4c82e52 Bob Lantz
            data = data[ :-1 ]
255 4065511a Bob Lantz
        elif chr( 127 ) in data:
256 bcacfc05 Bob Lantz
            self.waiting = False
257 e4c82e52 Bob Lantz
            data = data.replace( chr( 127 ), '' )
258
        return data
259 723d068c Brandon Heller
260 efc9a01c Bob Lantz
    def waitOutput( self, verbose=False ):
261 80a8fa62 Bob Lantz
        """Wait for a command to complete.
262 7c371cf3 Bob Lantz
           Completion is signaled by a sentinel character, ASCII(127)
263 80a8fa62 Bob Lantz
           appearing in the output stream.  Wait for the sentinel and return
264 efc9a01c Bob Lantz
           the output, including trailing newline.
265
           verbose: print output interactively"""
266
        log = info if verbose else debug
267 89bf3103 Brandon Heller
        output = ''
268 e4c82e52 Bob Lantz
        while self.waiting:
269
            data = self.monitor()
270 4065511a Bob Lantz
            output += data
271
            log( data )
272 89bf3103 Brandon Heller
        return output
273
274 121eb449 Bob Lantz
    def cmd( self, *args, **kwargs ):
275 80a8fa62 Bob Lantz
        """Send a command, wait for output, and return it.
276
           cmd: string"""
277 121eb449 Bob Lantz
        verbose = kwargs.get( 'verbose', False )
278 efc9a01c Bob Lantz
        log = info if verbose else debug
279 121eb449 Bob Lantz
        log( '*** %s : %s\n' % ( self.name, args ) )
280
        self.sendCmd( *args, **kwargs )
281 efc9a01c Bob Lantz
        return self.waitOutput( verbose )
282 89bf3103 Brandon Heller
283 121eb449 Bob Lantz
    def cmdPrint( self, *args):
284 80a8fa62 Bob Lantz
        """Call cmd and printing its output
285
           cmd: string"""
286 121eb449 Bob Lantz
        return self.cmd( *args, **{ 'verbose': True } )
287 89bf3103 Brandon Heller
288 089e8130 Bob Lantz
    def popen( self, *args, **kwargs ):
289
        """Return a Popen() object in our namespace
290
           args: Popen() args, single list, or string
291
           kwargs: Popen() keyword args"""
292
        defaults = { 'stdout': PIPE, 'stderr': PIPE,
293 5ca91f9c Bob Lantz
                     'mncmd':
294 e5a15ced Bob Lantz
                     [ 'mnexec', '-da', str( self.pid ) ] }
295 089e8130 Bob Lantz
        defaults.update( kwargs )
296
        if len( args ) == 1:
297
            if type( args[ 0 ] ) is list:
298
                # popen([cmd, arg1, arg2...])
299
                cmd = args[ 0 ]
300
            elif type( args[ 0 ] ) is str:
301
                # popen("cmd arg1 arg2...")
302
                cmd = args[ 0 ].split()
303 b9100834 Bob Lantz
            else:
304
                raise Exception( 'popen() requires a string or list' )
305 089e8130 Bob Lantz
        elif len( args ) > 0:
306
            # popen( cmd, arg1, arg2... )
307 06f7408c Bob Lantz
            cmd = list( args )
308 089e8130 Bob Lantz
        # Attach to our namespace  using mnexec -a
309
        mncmd = defaults[ 'mncmd' ]
310
        del defaults[ 'mncmd' ]
311 df600200 Bob Lantz
        cmd = mncmd + cmd
312 b9100834 Bob Lantz
        # Shell requires a string, not a list!
313
        if defaults.get( 'shell', False ):
314
            cmd = ' '.join( cmd )
315 089e8130 Bob Lantz
        return Popen( cmd, **defaults )
316
317
    def pexec( self, *args, **kwargs ):
318
        """Execute a command using popen
319
           returns: out, err, exitcode"""
320
        popen = self.popen( *args, **kwargs)
321
        out, err = popen.communicate()
322
        exitcode = popen.wait()
323
        return out, err, exitcode
324
325 89bf3103 Brandon Heller
    # Interface management, configuration, and routing
326 efc9a01c Bob Lantz
327
    # BL notes: This might be a bit redundant or over-complicated.
328
    # However, it does allow a bit of specialization, including
329
    # changing the canonical interface names. It's also tricky since
330
    # the real interfaces are created as veth pairs, so we can't
331
    # make a single interface at a time.
332
333 d44a5843 Bob Lantz
    def newPort( self ):
334
        "Return the next port number to allocate."
335
        if len( self.ports ) > 0:
336
            return max( self.ports.values() ) + 1
337 dd159b4a Bob Lantz
        return self.portBase
338 89bf3103 Brandon Heller
339 121eb449 Bob Lantz
    def addIntf( self, intf, port=None ):
340 efc9a01c Bob Lantz
        """Add an interface.
341 a6bcad8f Bob Lantz
           intf: interface
342 121eb449 Bob Lantz
           port: port number (optional, typically OpenFlow port number)"""
343
        if port is None:
344
            port = self.newPort()
345 efc9a01c Bob Lantz
        self.intfs[ port ] = intf
346
        self.ports[ intf ] = port
347 a6bcad8f Bob Lantz
        self.nameToIntf[ intf.name ] = intf
348 84a91a14 Bob Lantz
        debug( '\n' )
349 14ff3ad3 Bob Lantz
        debug( 'added intf %s:%d to node %s\n' % ( intf, port, self.name ) )
350 efc9a01c Bob Lantz
        if self.inNamespace:
351 84a91a14 Bob Lantz
            debug( 'moving', intf, 'into namespace for', self.name, '\n' )
352 a6bcad8f Bob Lantz
            moveIntf( intf.name, self )
353 efc9a01c Bob Lantz
354 a6bcad8f Bob Lantz
    def defaultIntf( self ):
355
        "Return interface for lowest port"
356
        ports = self.intfs.keys()
357
        if ports:
358
            return self.intfs[ min( ports ) ]
359 efc99154 Bob Lantz
        else:
360
            warn( '*** defaultIntf: warning:', self.name,
361 edf60032 Brandon Heller
                  'has no interfaces\n' )
362 efc9a01c Bob Lantz
363 a6bcad8f Bob Lantz
    def intf( self, intf='' ):
364 bf208cde Brandon Heller
        """Return our interface object with given string name,
365
           default intf if name is falsy (None, empty string, etc).
366
           or the input intf arg.
367

368
        Having this fcn return its arg for Intf objects makes it
369
        easier to construct functions with flexible input args for
370
        interfaces (those that accept both string names and Intf objects).
371
        """
372 a6bcad8f Bob Lantz
        if not intf:
373
            return self.defaultIntf()
374 12758046 Bob Lantz
        elif type( intf ) is str:
375 a6bcad8f Bob Lantz
            return self.nameToIntf[ intf ]
376
        else:
377 bf208cde Brandon Heller
            return intf
378 efc9a01c Bob Lantz
379 fb2f6523 Bob Lantz
    def connectionsTo( self, node):
380 8856d284 Bob Lantz
        "Return [ intf1, intf2... ] for all intfs that connect self to node."
381 fb2f6523 Bob Lantz
        # We could optimize this if it is important
382
        connections = []
383 8856d284 Bob Lantz
        for intf in self.intfList():
384 a6bcad8f Bob Lantz
            link = intf.link
385 8856d284 Bob Lantz
            if link:
386
                node1, node2 = link.intf1.node, link.intf2.node
387
                if node1 == self and node2 == node:
388
                    connections += [ ( intf, link.intf2 ) ]
389
                elif node1 == node and node2 == self:
390
                    connections += [ ( intf, link.intf1 ) ]
391 fb2f6523 Bob Lantz
        return connections
392 35341142 Bob Lantz
393 086ef80e Bob Lantz
    def deleteIntfs( self ):
394
        "Delete all of our interfaces."
395
        # In theory the interfaces should go away after we shut down.
396
        # However, this takes time, so we're better off removing them
397
        # explicitly so that we won't get errors if we run before they
398 d44a5843 Bob Lantz
        # have been removed by the kernel. Unfortunately this is very slow,
399
        # at least with Linux kernels before 2.6.33
400 086ef80e Bob Lantz
        for intf in self.intfs.values():
401 a6bcad8f Bob Lantz
            intf.delete()
402 086ef80e Bob Lantz
            info( '.' )
403
404 a6bcad8f Bob Lantz
    # Routing support
405 376bcba4 Brandon Heller
406 80a8fa62 Bob Lantz
    def setARP( self, ip, mac ):
407
        """Add an ARP entry.
408 019bff82 Bob Lantz
           ip: IP address as string
409
           mac: MAC address as string"""
410 121eb449 Bob Lantz
        result = self.cmd( 'arp', '-s', ip, mac )
411 376bcba4 Brandon Heller
        return result
412
413 80a8fa62 Bob Lantz
    def setHostRoute( self, ip, intf ):
414
        """Add route to host.
415
           ip: IP address as dotted decimal
416
           intf: string, interface name"""
417 14ff3ad3 Bob Lantz
        return self.cmd( 'route add -host', ip, 'dev', intf )
418 89bf3103 Brandon Heller
419 84a91a14 Bob Lantz
    def setDefaultRoute( self, intf=None ):
420 80a8fa62 Bob Lantz
        """Set the default route to go through intf.
421 dd21df3c Bob Lantz
           intf: Intf or {dev <intfname> via <gw-ip> ...}"""
422
        # Note setParam won't call us if intf is none
423 12758046 Bob Lantz
        if type( intf ) is str and ' ' in intf:
424 dd21df3c Bob Lantz
            params = intf
425
        else:
426 12758046 Bob Lantz
            params = 'dev %s' % intf
427 dd21df3c Bob Lantz
        self.cmd( 'ip route del default' )
428 12758046 Bob Lantz
        return self.cmd( 'ip route add default', params )
429 89bf3103 Brandon Heller
430 84a91a14 Bob Lantz
    # Convenience and configuration methods
431 54d026f6 Bob Lantz
432 b684ff78 Bob Lantz
    def setMAC( self, mac, intf=None ):
433 a6bcad8f Bob Lantz
        """Set the MAC address for an interface.
434
           intf: intf or intf name
435
           mac: MAC address as string"""
436
        return self.intf( intf ).setMAC( mac )
437
438 b684ff78 Bob Lantz
    def setIP( self, ip, prefixLen=8, intf=None ):
439 a6bcad8f Bob Lantz
        """Set the IP address for an interface.
440 bf208cde Brandon Heller
           intf: intf or intf name
441 a6bcad8f Bob Lantz
           ip: IP address as a string
442
           prefixLen: prefix length, e.g. 8 for /8 or 16M addrs"""
443 a49c85a6 Bob Lantz
        # This should probably be rethought
444
        if '/' not in ip:
445
            ip = '%s/%s' % ( ip, prefixLen )
446
        return self.intf( intf ).setIP( ip )
447 54d026f6 Bob Lantz
448 d44a5843 Bob Lantz
    def IP( self, intf=None ):
449
        "Return IP address of a node or specific interface."
450 b684ff78 Bob Lantz
        return self.intf( intf ).IP()
451 d44a5843 Bob Lantz
452
    def MAC( self, intf=None ):
453
        "Return MAC address of a node or specific interface."
454 0aefb0e0 Bob Lantz
        return self.intf( intf ).MAC()
455 54d026f6 Bob Lantz
456 a6bcad8f Bob Lantz
    def intfIsUp( self, intf=None ):
457 d44a5843 Bob Lantz
        "Check if an interface is up."
458 a6bcad8f Bob Lantz
        return self.intf( intf ).isUp()
459
460 84a91a14 Bob Lantz
    # The reason why we configure things in this way is so
461
    # That the parameters can be listed and documented in
462
    # the config method.
463
    # Dealing with subclasses and superclasses is slightly
464
    # annoying, but at least the information is there!
465
466
    def setParam( self, results, method, **param ):
467 216a4b7c Bob Lantz
        """Internal method: configure a *single* parameter
468
           results: dict of results to update
469
           method: config method name
470
           param: arg=value (ignore if value=None)
471
           value may also be list or dict"""
472 84a91a14 Bob Lantz
        name, value = param.items()[ 0 ]
473
        f = getattr( self, method, None )
474 216a4b7c Bob Lantz
        if not f or value is None:
475 84a91a14 Bob Lantz
            return
476
        if type( value ) is list:
477
            result = f( *value )
478
        elif type( value ) is dict:
479
            result = f( **value )
480 54d026f6 Bob Lantz
        else:
481 84a91a14 Bob Lantz
            result = f( value )
482
        results[ name ] = result
483 216a4b7c Bob Lantz
        return result
484 54d026f6 Bob Lantz
485 8856d284 Bob Lantz
    def config( self, mac=None, ip=None,
486
                defaultRoute=None, lo='up', **_params ):
487 84a91a14 Bob Lantz
        """Configure Node according to (optional) parameters:
488
           mac: MAC address for default interface
489
           ip: IP address for default interface
490
           ifconfig: arbitrary interface configuration
491
           Subclasses should override this method and call
492
           the parent class's config(**params)"""
493
        # If we were overriding this method, we would call
494
        # the superclass config method here as follows:
495 14ff3ad3 Bob Lantz
        # r = Parent.config( **_params )
496 84a91a14 Bob Lantz
        r = {}
497
        self.setParam( r, 'setMAC', mac=mac )
498
        self.setParam( r, 'setIP', ip=ip )
499 e5754ae9 Shaun Crampton
        self.setParam( r, 'setDefaultRoute', defaultRoute=defaultRoute )
500 8856d284 Bob Lantz
        # This should be examined
501
        self.cmd( 'ifconfig lo ' + lo )
502 84a91a14 Bob Lantz
        return r
503
504
    def configDefault( self, **moreParams ):
505
        "Configure with default parameters"
506
        self.params.update( moreParams )
507
        self.config( **self.params )
508
509 a6bcad8f Bob Lantz
    # This is here for backward compatibility
510
    def linkTo( self, node, link=Link ):
511
        """(Deprecated) Link to another node
512
           replace with Link( node1, node2)"""
513
        return link( self, node )
514 89bf3103 Brandon Heller
515
    # Other methods
516 a6bcad8f Bob Lantz
517 03dd914e Bob Lantz
    def intfList( self ):
518
        "List of our interfaces sorted by port number"
519
        return [ self.intfs[ p ] for p in sorted( self.intfs.iterkeys() ) ]
520
521 a6bcad8f Bob Lantz
    def intfNames( self ):
522 03dd914e Bob Lantz
        "The names of our interfaces sorted by port number"
523
        return [ str( i ) for i in self.intfList() ]
524 a6bcad8f Bob Lantz
525 8856d284 Bob Lantz
    def __repr__( self ):
526
        "More informative string representation"
527
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
528 edf60032 Brandon Heller
                              for i in self.intfList() ] ) )
529 8856d284 Bob Lantz
        return '<%s %s: %s pid=%s> ' % (
530 c0095746 Brandon Heller
            self.__class__.__name__, self.name, intfs, self.pid )
531 8856d284 Bob Lantz
532 80a8fa62 Bob Lantz
    def __str__( self ):
533 8856d284 Bob Lantz
        "Abbreviated string representation"
534
        return self.name
535 89bf3103 Brandon Heller
536 14ff3ad3 Bob Lantz
    # Automatic class setup support
537
538
    isSetup = False
539
540
    @classmethod
541
    def checkSetup( cls ):
542
        "Make sure our class and superclasses are set up"
543
        while cls and not getattr( cls, 'isSetup', True ):
544
            cls.setup()
545
            cls.isSetup = True
546
            # Make pylint happy
547
            cls = getattr( type( cls ), '__base__', None )
548
549
    @classmethod
550
    def setup( cls ):
551
        "Make sure our class dependencies are available"
552
        pathCheck( 'mnexec', 'ifconfig', moduleName='Mininet')
553 89bf3103 Brandon Heller
554
555 80a8fa62 Bob Lantz
class Host( Node ):
556 216a4b7c Bob Lantz
    "A host is simply a Node"
557
    pass
558
559
560
class CPULimitedHost( Host ):
561 84a91a14 Bob Lantz
562
    "CPU limited host"
563
564 59542784 Bob Lantz
    def __init__( self, name, sched='cfs', **kwargs ):
565
        Host.__init__( self, name, **kwargs )
566 197b083f Bob Lantz
        # Initialize class if necessary
567
        if not CPULimitedHost.inited:
568
            CPULimitedHost.init()
569 84a91a14 Bob Lantz
        # Create a cgroup and move shell into it
570 197b083f Bob Lantz
        self.cgroup = 'cpu,cpuacct,cpuset:/' + self.name
571 8a622c3a Bob Lantz
        errFail( 'cgcreate -g ' + self.cgroup )
572 197b083f Bob Lantz
        # We don't add ourselves to a cpuset because you must
573
        # specify the cpu and memory placement first
574
        errFail( 'cgclassify -g cpu,cpuacct:/%s %s' % ( self.name, self.pid ) )
575 44af37bc Bob Lantz
        # BL: Setting the correct period/quota is tricky, particularly
576
        # for RT. RT allows very small quotas, but the overhead
577
        # seems to be high. CFS has a mininimum quota of 1 ms, but
578
        # still does better with larger period values.
579
        self.period_us = kwargs.get( 'period_us', 100000 )
580 59542784 Bob Lantz
        self.sched = sched
581 089e8130 Bob Lantz
        self.rtprio = 20
582 84a91a14 Bob Lantz
583
    def cgroupSet( self, param, value, resource='cpu' ):
584
        "Set a cgroup parameter and return its value"
585
        cmd = 'cgset -r %s.%s=%s /%s' % (
586
            resource, param, value, self.name )
587 8e3699ec Bob Lantz
        quietRun( cmd )
588 8a622c3a Bob Lantz
        nvalue = int( self.cgroupGet( param, resource ) )
589
        if nvalue != value:
590
            error( '*** error: cgroupSet: %s set to %s instead of %s\n'
591
                   % ( param, nvalue, value ) )
592
        return nvalue
593 84a91a14 Bob Lantz
594
    def cgroupGet( self, param, resource='cpu' ):
595 14ff3ad3 Bob Lantz
        "Return value of cgroup parameter"
596 84a91a14 Bob Lantz
        cmd = 'cgget -r %s.%s /%s' % (
597
            resource, param, self.name )
598 197b083f Bob Lantz
        return int( quietRun( cmd ).split()[ -1 ] )
599 84a91a14 Bob Lantz
600 28833d86 Bob Lantz
    def cgroupDel( self ):
601
        "Clean up our cgroup"
602
        # info( '*** deleting cgroup', self.cgroup, '\n' )
603 612b21cb Bob Lantz
        _out, _err, exitcode = errRun( 'cgdelete -r ' + self.cgroup )
604 28833d86 Bob Lantz
        return exitcode != 0
605
606 089e8130 Bob Lantz
    def popen( self, *args, **kwargs ):
607
        """Return a Popen() object in node's namespace
608
           args: Popen() args, single list, or string
609
           kwargs: Popen() keyword args"""
610
        # Tell mnexec to execute command in our cgroup
611 e5a15ced Bob Lantz
        mncmd = [ 'mnexec', '-da', str( self.pid ),
612 df600200 Bob Lantz
                  '-g', self.name ]
613 089e8130 Bob Lantz
        if self.sched == 'rt':
614 df600200 Bob Lantz
            mncmd += [ '-r', str( self.rtprio ) ]
615 089e8130 Bob Lantz
        return Host.popen( self, *args, mncmd=mncmd, **kwargs )
616
617 28833d86 Bob Lantz
    def cleanup( self ):
618
        "Clean up our cgroup"
619
        retry( retries=3, delaySecs=1, fn=self.cgroupDel )
620
621 089e8130 Bob Lantz
    def chrt( self ):
622 84a91a14 Bob Lantz
        "Set RT scheduling priority"
623 089e8130 Bob Lantz
        quietRun( 'chrt -p %s %s' % ( self.rtprio, self.pid ) )
624 84a91a14 Bob Lantz
        result = quietRun( 'chrt -p %s' % self.pid )
625
        firstline = result.split( '\n' )[ 0 ]
626
        lastword = firstline.split( ' ' )[ -1 ]
627 8a622c3a Bob Lantz
        if lastword != 'SCHED_RR':
628
            error( '*** error: could not assign SCHED_RR to %s\n' % self.name )
629 84a91a14 Bob Lantz
        return lastword
630
631 8a622c3a Bob Lantz
    def rtInfo( self, f ):
632
        "Internal method: return parameters for RT bandwidth"
633
        pstr, qstr = 'rt_period_us', 'rt_runtime_us'
634
        # RT uses wall clock time for period and quota
635
        quota = int( self.period_us * f * numCores() )
636
        return pstr, qstr, self.period_us, quota
637
638
    def cfsInfo( self, f):
639
        "Internal method: return parameters for CFS bandwidth"
640
        pstr, qstr = 'cfs_period_us', 'cfs_quota_us'
641
        # CFS uses wall clock time for period and CPU time for quota.
642
        quota = int( self.period_us * f * numCores() )
643
        period = self.period_us
644
        if f > 0 and quota < 1000:
645
            debug( '(cfsInfo: increasing default period) ' )
646
            quota = 1000
647
            period = int( quota / f / numCores() )
648
        return pstr, qstr, period, quota
649
650 216a4b7c Bob Lantz
    # BL comment:
651 14ff3ad3 Bob Lantz
    # This may not be the right API,
652 216a4b7c Bob Lantz
    # since it doesn't specify CPU bandwidth in "absolute"
653
    # units the way link bandwidth is specified.
654
    # We should use MIPS or SPECINT or something instead.
655
    # Alternatively, we should change from system fraction
656
    # to CPU seconds per second, essentially assuming that
657
    # all CPUs are the same.
658 8a622c3a Bob Lantz
659 216a4b7c Bob Lantz
    def setCPUFrac( self, f=-1, sched=None):
660
        """Set overall CPU fraction for this host
661
           f: CPU bandwidth limit (fraction)
662
           sched: 'rt' or 'cfs'
663
           Note 'cfs' requires CONFIG_CFS_BANDWIDTH"""
664
        if not f:
665
            return
666
        if not sched:
667
            sched = self.sched
668
        if sched == 'rt':
669 8a622c3a Bob Lantz
            pstr, qstr, period, quota = self.rtInfo( f )
670 216a4b7c Bob Lantz
        elif sched == 'cfs':
671 8a622c3a Bob Lantz
            pstr, qstr, period, quota = self.cfsInfo( f )
672 216a4b7c Bob Lantz
        else:
673
            return
674
        if quota < 0:
675 84a91a14 Bob Lantz
            # Reset to unlimited
676 216a4b7c Bob Lantz
            quota = -1
677
        # Set cgroup's period and quota
678 8e3699ec Bob Lantz
        self.cgroupSet( pstr, period )
679
        self.cgroupSet( qstr, quota )
680 216a4b7c Bob Lantz
        if sched == 'rt':
681
            # Set RT priority if necessary
682 df600200 Bob Lantz
            self.chrt()
683 8a622c3a Bob Lantz
        info( '(%s %d/%dus) ' % ( sched, quota, period ) )
684 84a91a14 Bob Lantz
685 669e420c Bob Lantz
    def setCPUs( self, cores, mems=0 ):
686 197b083f Bob Lantz
        "Specify (real) cores that our cgroup can run on"
687
        if type( cores ) is list:
688
            cores = ','.join( [ str( c ) for c in cores ] )
689
        self.cgroupSet( resource='cpuset', param='cpus',
690 669e420c Bob Lantz
                        value=cores )
691 197b083f Bob Lantz
        # Memory placement is probably not relevant, but we
692
        # must specify it anyway
693
        self.cgroupSet( resource='cpuset', param='mems',
694 669e420c Bob Lantz
                        value=mems)
695 197b083f Bob Lantz
        # We have to do this here after we've specified
696
        # cpus and mems
697
        errFail( 'cgclassify -g cpuset:/%s %s' % (
698 c0095746 Brandon Heller
                 self.name, self.pid ) )
699 197b083f Bob Lantz
700
    def config( self, cpu=None, cores=None, **params ):
701 84a91a14 Bob Lantz
        """cpu: desired overall system CPU fraction
702 197b083f Bob Lantz
           cores: (real) core(s) this host can run on
703 84a91a14 Bob Lantz
           params: parameters for Node.config()"""
704
        r = Node.config( self, **params )
705 216a4b7c Bob Lantz
        # Was considering cpu={'cpu': cpu , 'sched': sched}, but
706
        # that seems redundant
707 84a91a14 Bob Lantz
        self.setParam( r, 'setCPUFrac', cpu=cpu )
708 197b083f Bob Lantz
        self.setParam( r, 'setCPUs', cores=cores )
709 84a91a14 Bob Lantz
        return r
710
711 197b083f Bob Lantz
    inited = False
712 89bf3103 Brandon Heller
713 197b083f Bob Lantz
    @classmethod
714
    def init( cls ):
715
        "Initialization for CPULimitedHost class"
716
        mountCgroups()
717
        cls.inited = True
718
719
720 84a91a14 Bob Lantz
# Some important things to note:
721
#
722 bf9c6ab7 Bob Lantz
# The "IP" address which setIP() assigns to the switch is not
723 84a91a14 Bob Lantz
# an "IP address for the switch" in the sense of IP routing.
724 bf9c6ab7 Bob Lantz
# Rather, it is the IP address for the control interface,
725
# on the control network, and it is only relevant to the
726
# controller. If you are running in the root namespace
727
# (which is the only way to run OVS at the moment), the
728
# control interface is the loopback interface, and you
729
# normally never want to change its IP address!
730 84a91a14 Bob Lantz
#
731
# In general, you NEVER want to attempt to use Linux's
732
# network stack (i.e. ifconfig) to "assign" an IP address or
733
# MAC address to a switch data port. Instead, you "assign"
734
# the IP and MAC addresses in the controller by specifying
735
# packets that you want to receive or send. The "MAC" address
736
# reported by ifconfig for a switch data port is essentially
737 bf9c6ab7 Bob Lantz
# meaningless. It is important to understand this if you
738
# want to create a functional router using OpenFlow.
739 89bf3103 Brandon Heller
740 80a8fa62 Bob Lantz
class Switch( Node ):
741 6d72a3cb Bob Lantz
    """A Switch is a Node that is running (or has execed?)
742 80a8fa62 Bob Lantz
       an OpenFlow switch."""
743 89bf3103 Brandon Heller
744 8a622c3a Bob Lantz
    portBase = 1  # Switches start with port 1 in OpenFlow
745 0d94548a Bob Lantz
    dpidLen = 16  # digits in dpid passed to switch
746 dd159b4a Bob Lantz
747 84a91a14 Bob Lantz
    def __init__( self, name, dpid=None, opts='', listenPort=None, **params):
748 55cf19c4 Bob Lantz
        """dpid: dpid for switch (or None to derive from name, e.g. s1 -> 1)
749 84a91a14 Bob Lantz
           opts: additional switch options
750
           listenPort: port to listen on for dpctl connections"""
751
        Node.__init__( self, name, **params )
752
        self.dpid = dpid if dpid else self.defaultDpid()
753 121eb449 Bob Lantz
        self.opts = opts
754 ccca871a Brandon Heller
        self.listenPort = listenPort
755 8856d284 Bob Lantz
        if not self.inNamespace:
756 14c19260 Bob Lantz
            self.controlIntf = Intf( 'lo', self, port=0 )
757 84a91a14 Bob Lantz
758
    def defaultDpid( self ):
759
        "Derive dpid from switch name, s1 -> 1"
760 55cf19c4 Bob Lantz
        try:
761
            dpid = int( re.findall( '\d+', self.name )[ 0 ] )
762
            dpid = hex( dpid )[ 2: ]
763 0d94548a Bob Lantz
            dpid = '0' * ( self.dpidLen - len( dpid ) ) + dpid
764 55cf19c4 Bob Lantz
            return dpid
765
        except IndexError:
766
            raise Exception( 'Unable to derive default datapath ID - '
767
                             'please either specify a dpid or use a '
768
                             'canonical switch name such as s23.' )
769 121eb449 Bob Lantz
770 9802686b Bob Lantz
    def defaultIntf( self ):
771 8856d284 Bob Lantz
        "Return control interface"
772
        if self.controlIntf:
773
            return self.controlIntf
774
        else:
775
            return Node.defaultIntf( self )
776 121eb449 Bob Lantz
777
    def sendCmd( self, *cmd, **kwargs ):
778 80a8fa62 Bob Lantz
        """Send command to Node.
779
           cmd: string"""
780 121eb449 Bob Lantz
        kwargs.setdefault( 'printPid', False )
781 1bb4412f Brandon Heller
        if not self.execed:
782 121eb449 Bob Lantz
            return Node.sendCmd( self, *cmd, **kwargs )
783 1bb4412f Brandon Heller
        else:
784 6d72a3cb Bob Lantz
            error( '*** Error: %s has execed and cannot accept commands' %
785 2e089b5e Brandon Heller
                   self.name )
786 89bf3103 Brandon Heller
787 8856d284 Bob Lantz
    def __repr__( self ):
788
        "More informative string representation"
789
        intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
790 edf60032 Brandon Heller
                              for i in self.intfList() ] ) )
791 8856d284 Bob Lantz
        return '<%s %s: %s pid=%s> ' % (
792 c0095746 Brandon Heller
            self.__class__.__name__, self.name, intfs, self.pid )
793 89bf3103 Brandon Heller
794 80a8fa62 Bob Lantz
class UserSwitch( Switch ):
795 d44a5843 Bob Lantz
    "User-space switch."
796 89bf3103 Brandon Heller
797 0d94548a Bob Lantz
    dpidLen = 12
798
799 b2ef87ae Bob Lantz
    def __init__( self, name, **kwargs ):
800 80a8fa62 Bob Lantz
        """Init.
801
           name: name for the switch"""
802 d44a5843 Bob Lantz
        Switch.__init__( self, name, **kwargs )
803 57fd19ef Bob Lantz
        pathCheck( 'ofdatapath', 'ofprotocol',
804 edf60032 Brandon Heller
                   moduleName='the OpenFlow reference user switch' +
805
                              '(openflow.org)' )
806 8856d284 Bob Lantz
        if self.listenPort:
807
            self.opts += ' --listen=ptcp:%i ' % self.listenPort
808 89bf3103 Brandon Heller
809 14ff3ad3 Bob Lantz
    @classmethod
810
    def setup( cls ):
811 b055728f Brandon Heller
        "Ensure any dependencies are loaded; if not, try to load them."
812 e900a16c Bob Lantz
        if not os.path.exists( '/dev/net/tun' ):
813
            moduleDeps( add=TUN )
814 b055728f Brandon Heller
815 8856d284 Bob Lantz
    def dpctl( self, *args ):
816
        "Run dpctl command"
817
        if not self.listenPort:
818 14c19260 Bob Lantz
            return "can't run dpctl without passive listening port"
819
        return self.cmd( 'dpctl ' + ' '.join( args ) +
820 8856d284 Bob Lantz
                         ' tcp:127.0.0.1:%i' % self.listenPort )
821
822 80a8fa62 Bob Lantz
    def start( self, controllers ):
823
        """Start OpenFlow reference user datapath.
824 6d72a3cb Bob Lantz
           Log to /tmp/sN-{ofd,ofp}.log.
825 efc9a01c Bob Lantz
           controllers: list of controller objects"""
826 b69ef234 Bob Lantz
        # Add controllers
827
        clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
828 edf60032 Brandon Heller
                            for c in controllers ] )
829 89bf3103 Brandon Heller
        ofdlog = '/tmp/' + self.name + '-ofd.log'
830
        ofplog = '/tmp/' + self.name + '-ofp.log'
831 80a8fa62 Bob Lantz
        self.cmd( 'ifconfig lo up' )
832 14c19260 Bob Lantz
        intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
833 efc9a01c Bob Lantz
        self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
834 edf60032 Brandon Heller
                  ' punix:/tmp/' + self.name + ' -d ' + self.dpid +
835
                  ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
836 efc9a01c Bob Lantz
        self.cmd( 'ofprotocol unix:/tmp/' + self.name +
837 edf60032 Brandon Heller
                  ' ' + clist +
838
                  ' --fail=closed ' + self.opts +
839
                  ' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
840 89bf3103 Brandon Heller
841 80a8fa62 Bob Lantz
    def stop( self ):
842
        "Stop OpenFlow reference user datapath."
843
        self.cmd( 'kill %ofdatapath' )
844
        self.cmd( 'kill %ofprotocol' )
845 086ef80e Bob Lantz
        self.deleteIntfs()
846 1bb4412f Brandon Heller
847 82b72072 Bob Lantz
848 8a7d42db Bob Lantz
class OVSLegacyKernelSwitch( Switch ):
849
    """Open VSwitch legacy kernel-space switch using ovs-openflowd.
850 80a8fa62 Bob Lantz
       Currently only works in the root namespace."""
851 f7c2df25 Brandon Heller
852 086ef80e Bob Lantz
    def __init__( self, name, dp=None, **kwargs ):
853 80a8fa62 Bob Lantz
        """Init.
854 e3a2ef01 Bob Lantz
           name: name for switch
855 6d72a3cb Bob Lantz
           dp: netlink id (0, 1, 2, ...)
856 d44a5843 Bob Lantz
           defaultMAC: default MAC as unsigned int; random value if None"""
857
        Switch.__init__( self, name, **kwargs )
858 612b21cb Bob Lantz
        self.dp = dp if dp else self.name
859 121eb449 Bob Lantz
        self.intf = self.dp
860 d44a5843 Bob Lantz
        if self.inNamespace:
861
            error( "OVSKernelSwitch currently only works"
862 edf60032 Brandon Heller
                   " in the root namespace.\n" )
863 d44a5843 Bob Lantz
            exit( 1 )
864 f7c2df25 Brandon Heller
865 14ff3ad3 Bob Lantz
    @classmethod
866
    def setup( cls ):
867 b055728f Brandon Heller
        "Ensure any dependencies are loaded; if not, try to load them."
868 f9654e56 Bob Lantz
        pathCheck( 'ovs-dpctl', 'ovs-openflowd',
869 edf60032 Brandon Heller
                   moduleName='Open vSwitch (openvswitch.org)')
870 f9654e56 Bob Lantz
        moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
871 b055728f Brandon Heller
872 80a8fa62 Bob Lantz
    def start( self, controllers ):
873
        "Start up kernel datapath."
874 f7c2df25 Brandon Heller
        ofplog = '/tmp/' + self.name + '-ofp.log'
875 80a8fa62 Bob Lantz
        quietRun( 'ifconfig lo up' )
876 f7c2df25 Brandon Heller
        # Delete local datapath if it exists;
877
        # then create a new one monitoring the given interfaces
878 8856d284 Bob Lantz
        self.cmd( 'ovs-dpctl del-dp ' + self.dp )
879 121eb449 Bob Lantz
        self.cmd( 'ovs-dpctl add-dp ' + self.dp )
880 14c19260 Bob Lantz
        intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
881 5cc80828 Bob Lantz
        self.cmd( 'ovs-dpctl', 'add-if', self.dp, ' '.join( intfs ) )
882 f7c2df25 Brandon Heller
        # Run protocol daemon
883 b69ef234 Bob Lantz
        clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
884 edf60032 Brandon Heller
                            for c in controllers ] )
885 121eb449 Bob Lantz
        self.cmd( 'ovs-openflowd ' + self.dp +
886 edf60032 Brandon Heller
                  ' ' + clist +
887
                  ' --fail=secure ' + self.opts +
888
                  ' --datapath-id=' + self.dpid +
889
                  ' 1>' + ofplog + ' 2>' + ofplog + '&' )
890 f7c2df25 Brandon Heller
        self.execed = False
891
892 80a8fa62 Bob Lantz
    def stop( self ):
893
        "Terminate kernel datapath."
894 121eb449 Bob Lantz
        quietRun( 'ovs-dpctl del-dp ' + self.dp )
895 75d72d96 Bob Lantz
        self.cmd( 'kill %ovs-openflowd' )
896 086ef80e Bob Lantz
        self.deleteIntfs()
897 f7c2df25 Brandon Heller
898
899 8a7d42db Bob Lantz
class OVSSwitch( Switch ):
900
    "Open vSwitch switch. Depends on ovs-vsctl."
901
902 153d598d Murphy McCauley
    def __init__( self, name, failMode='secure', datapath='kernel', **params ):
903 8a7d42db Bob Lantz
        """Init.
904
           name: name for switch
905 153d598d Murphy McCauley
           failMode: controller loss behavior (secure|open)
906
           datapath: userspace or kernel mode (kernel|user)"""
907 84a91a14 Bob Lantz
        Switch.__init__( self, name, **params )
908 92b601ab Bob Lantz
        self.failMode = failMode
909 153d598d Murphy McCauley
        self.datapath = datapath
910 14ff3ad3 Bob Lantz
911
    @classmethod
912
    def setup( cls ):
913 8a7d42db Bob Lantz
        "Make sure Open vSwitch is installed and working"
914 14ff3ad3 Bob Lantz
        pathCheck( 'ovs-vsctl',
915 edf60032 Brandon Heller
                   moduleName='Open vSwitch (openvswitch.org)')
916 28c2cdc2 Bob Lantz
        # This should no longer be needed, and it breaks
917
        # with OVS 1.7 which has renamed the kernel module:
918
        #  moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
919 8a7d42db Bob Lantz
        out, err, exitcode = errRun( 'ovs-vsctl -t 1 show' )
920
        if exitcode:
921 14ff3ad3 Bob Lantz
            error( out + err +
922 8a7d42db Bob Lantz
                   'ovs-vsctl exited with code %d\n' % exitcode +
923
                   '*** Error connecting to ovs-db with ovs-vsctl\n'
924
                   'Make sure that Open vSwitch is installed, '
925
                   'that ovsdb-server is running, and that\n'
926
                   '"ovs-vsctl show" works correctly.\n'
927 14ff3ad3 Bob Lantz
                   'You may wish to try '
928
                   '"service openvswitch-switch start".\n' )
929 8a7d42db Bob Lantz
            exit( 1 )
930
931 8856d284 Bob Lantz
    def dpctl( self, *args ):
932 229f112f Bob Lantz
        "Run ovs-ofctl command"
933
        return self.cmd( 'ovs-ofctl', args[ 0 ], self, *args[ 1: ] )
934 8856d284 Bob Lantz
935 1aec55d9 Bob Lantz
    @staticmethod
936
    def TCReapply( intf ):
937
        """Unfortunately OVS and Mininet are fighting
938
           over tc queuing disciplines. As a quick hack/
939
           workaround, we clear OVS's and reapply our own."""
940
        if type( intf ) is TCIntf:
941
            intf.config( **intf.params )
942
943 8856d284 Bob Lantz
    def attach( self, intf ):
944
        "Connect a data port"
945
        self.cmd( 'ovs-vsctl add-port', self, intf )
946
        self.cmd( 'ifconfig', intf, 'up' )
947 1aec55d9 Bob Lantz
        self.TCReapply( intf )
948 8856d284 Bob Lantz
949
    def detach( self, intf ):
950
        "Disconnect a data port"
951
        self.cmd( 'ovs-vsctl del-port', self, intf )
952
953 8a7d42db Bob Lantz
    def start( self, controllers ):
954
        "Start up a new OVS OpenFlow switch using ovs-vsctl"
955 14ff3ad3 Bob Lantz
        if self.inNamespace:
956 8856d284 Bob Lantz
            raise Exception(
957 14ff3ad3 Bob Lantz
                'OVS kernel switch does not work in a namespace' )
958 8856d284 Bob Lantz
        # We should probably call config instead, but this
959
        # requires some rethinking...
960
        self.cmd( 'ifconfig lo up' )
961 8a7d42db Bob Lantz
        # Annoyingly, --if-exists option seems not to work
962 8856d284 Bob Lantz
        self.cmd( 'ovs-vsctl del-br', self )
963
        self.cmd( 'ovs-vsctl add-br', self )
964 153d598d Murphy McCauley
        if self.datapath == 'user':
965
            self.cmd( 'ovs-vsctl set bridge', self,'datapath_type=netdev' )
966 b97c1dbd Bob Lantz
        self.cmd( 'ovs-vsctl -- set Bridge', self,
967
                  'other_config:datapath-id=' + self.dpid )
968 92b601ab Bob Lantz
        self.cmd( 'ovs-vsctl set-fail-mode', self, self.failMode )
969 8856d284 Bob Lantz
        for intf in self.intfList():
970
            if not intf.IP():
971
                self.attach( intf )
972 8a7d42db Bob Lantz
        # Add controllers
973 915c6d4a Brandon Heller
        clist = ' '.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
974 14ff3ad3 Bob Lantz
                            for c in controllers ] )
975 3744638e Brandon Heller
        if self.listenPort:
976
            clist += ' ptcp:%s' % self.listenPort
977 8856d284 Bob Lantz
        self.cmd( 'ovs-vsctl set-controller', self, clist )
978 3df07feb Murphy McCauley
        # Set controllers to reconnect quickly
979 bdd43bea Bob Lantz
        controllers = self.cmd( 'ovs-vsctl -- get Bridge', self,
980
                               'Controller' ).strip()
981
        if controllers.startswith( '[' ) and controllers.endswith( ']' ):
982
            controllers = controllers[ 1 : -1 ]
983
            uuids = [ c.strip() for c in controllers.split( ',' ) ]
984
            for uuid in uuids:
985 3df07feb Murphy McCauley
                if uuid.count('-') != 4:
986
                    # Doesn't look like a UUID
987
                    continue
988
                uuid = uuid.strip()
989
                self.cmd( 'ovs-vsctl set Controller', uuid,
990
                          'max_backoff=1000' )
991 8a7d42db Bob Lantz
992
    def stop( self ):
993
        "Terminate OVS switch."
994 8856d284 Bob Lantz
        self.cmd( 'ovs-vsctl del-br', self )
995 3f61ea71 Bob Lantz
        self.deleteIntfs()
996 8a7d42db Bob Lantz
997
OVSKernelSwitch = OVSSwitch
998
999 84a91a14 Bob Lantz
1000 80a8fa62 Bob Lantz
class Controller( Node ):
1001 7c371cf3 Bob Lantz
    """A Controller is a Node that is running (or has execed?) an
1002 80a8fa62 Bob Lantz
       OpenFlow controller."""
1003 1bb4412f Brandon Heller
1004 57fd19ef Bob Lantz
    def __init__( self, name, inNamespace=False, command='controller',
1005 edf60032 Brandon Heller
                  cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
1006
                  port=6633, **params ):
1007 57fd19ef Bob Lantz
        self.command = command
1008 1bb4412f Brandon Heller
        self.cargs = cargs
1009
        self.cdir = cdir
1010 84a91a14 Bob Lantz
        self.ip = ip
1011 60d9ead6 David Erickson
        self.port = port
1012 086ef80e Bob Lantz
        Node.__init__( self, name, inNamespace=inNamespace,
1013 edf60032 Brandon Heller
                       ip=ip, **params  )
1014 ec969b7f Bob Lantz
        self.cmd( 'ifconfig lo up' )  # Shouldn't be necessary
1015
        self.checkListening()
1016
1017
    def checkListening( self ):
1018
        "Make sure no controllers are running on our port"
1019 b453e006 Brandon Heller
        # Verify that Telnet is installed first:
1020 c8b85746 Bob Lantz
        out, _err, returnCode = errRun( "which telnet" )
1021 b453e006 Brandon Heller
        if 'telnet' not in out or returnCode != 0:
1022
            raise Exception( "Error running telnet to check for listening "
1023
                             "controllers; please check that it is "
1024
                             "installed." )
1025 ec969b7f Bob Lantz
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1026
                              ( self.ip, self.port ) )
1027
        if 'Unable' not in listening:
1028
            servers = self.cmd( 'netstat -atp' ).split( '\n' )
1029
            pstr = ':%d ' % self.port
1030 28c2cdc2 Bob Lantz
            clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ]
1031 ec969b7f Bob Lantz
            raise Exception( "Please shut down the controller which is"
1032
                             " running on port %d:\n" % self.port +
1033 28c2cdc2 Bob Lantz
                             '\n'.join( clist ) )
1034 1bb4412f Brandon Heller
1035 80a8fa62 Bob Lantz
    def start( self ):
1036
        """Start <controller> <args> on controller.
1037
           Log to /tmp/cN.log"""
1038 57fd19ef Bob Lantz
        pathCheck( self.command )
1039 1bb4412f Brandon Heller
        cout = '/tmp/' + self.name + '.log'
1040
        if self.cdir is not None:
1041 efc9a01c Bob Lantz
            self.cmd( 'cd ' + self.cdir )
1042 57fd19ef Bob Lantz
        self.cmd( self.command + ' ' + self.cargs % self.port +
1043 edf60032 Brandon Heller
                  ' 1>' + cout + ' 2>' + cout + '&' )
1044 723d068c Brandon Heller
        self.execed = False
1045 89bf3103 Brandon Heller
1046 80a8fa62 Bob Lantz
    def stop( self ):
1047
        "Stop controller."
1048 57fd19ef Bob Lantz
        self.cmd( 'kill %' + self.command )
1049 1bb4412f Brandon Heller
        self.terminate()
1050 89bf3103 Brandon Heller
1051 d44a5843 Bob Lantz
    def IP( self, intf=None ):
1052 80a8fa62 Bob Lantz
        "Return IP address of the Controller"
1053 a6bcad8f Bob Lantz
        if self.intfs:
1054
            ip = Node.IP( self, intf )
1055
        else:
1056 84a91a14 Bob Lantz
            ip = self.ip
1057 d44a5843 Bob Lantz
        return ip
1058 723d068c Brandon Heller
1059 8856d284 Bob Lantz
    def __repr__( self ):
1060
        "More informative string representation"
1061
        return '<%s %s: %s:%s pid=%s> ' % (
1062 c0095746 Brandon Heller
            self.__class__.__name__, self.name,
1063
            self.IP(), self.port, self.pid )
1064 89bf3103 Brandon Heller
1065 84a91a14 Bob Lantz
1066 9addfc13 Bob Lantz
class OVSController( Controller ):
1067
    "Open vSwitch controller"
1068
    def __init__( self, name, command='ovs-controller', **kwargs ):
1069
        Controller.__init__( self, name, command=command, **kwargs )
1070 80a8fa62 Bob Lantz
1071
1072
class NOX( Controller ):
1073
    "Controller to run a NOX application."
1074
1075 2db4268b Bob Lantz
    def __init__( self, name, *noxArgs, **kwargs ):
1076 80a8fa62 Bob Lantz
        """Init.
1077
           name: name to give controller
1078 2db4268b Bob Lantz
           noxArgs: arguments (strings) to pass to NOX"""
1079 80a8fa62 Bob Lantz
        if not noxArgs:
1080 2db4268b Bob Lantz
            warn( 'warning: no NOX modules specified; '
1081
                  'running packetdump only\n' )
1082 80a8fa62 Bob Lantz
            noxArgs = [ 'packetdump' ]
1083 2db4268b Bob Lantz
        elif type( noxArgs ) not in ( list, tuple ):
1084 f32a5468 Brandon Heller
            noxArgs = [ noxArgs ]
1085 137ec305 Bob Lantz
1086 4f4f1dd2 Brandon Heller
        if 'NOX_CORE_DIR' not in os.environ:
1087 137ec305 Bob Lantz
            exit( 'exiting; please set missing NOX_CORE_DIR env var' )
1088 80a8fa62 Bob Lantz
        noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
1089 4f4f1dd2 Brandon Heller
1090 80a8fa62 Bob Lantz
        Controller.__init__( self, name,
1091 edf60032 Brandon Heller
                             command=noxCoreDir + '/nox_core',
1092
                             cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
1093 2e089b5e Brandon Heller
                             ' '.join( noxArgs ),
1094 edf60032 Brandon Heller
                             cdir=noxCoreDir,
1095
                             **kwargs )
1096 80a8fa62 Bob Lantz
1097
1098
class RemoteController( Controller ):
1099
    "Controller running outside of Mininet's control."
1100
1101 0eba655d Bob Lantz
    def __init__( self, name, ip='127.0.0.1',
1102 edf60032 Brandon Heller
                  port=6633, **kwargs):
1103 80a8fa62 Bob Lantz
        """Init.
1104
           name: name to give controller
1105 2aafefc2 Bob Lantz
           ip: the IP address where the remote controller is
1106 80a8fa62 Bob Lantz
           listening
1107
           port: the port where the remote controller is listening"""
1108 edf60032 Brandon Heller
        Controller.__init__( self, name, ip=ip, port=port, **kwargs )
1109 80a8fa62 Bob Lantz
1110
    def start( self ):
1111
        "Overridden to do nothing."
1112 60d9ead6 David Erickson
        return
1113
1114 80a8fa62 Bob Lantz
    def stop( self ):
1115
        "Overridden to do nothing."
1116 60d9ead6 David Erickson
        return
1117 2b35a2ca James Page
1118
    def checkListening( self ):
1119 54c51c02 Bob Lantz
        "Warn if remote controller is not accessible"
1120 2b35a2ca James Page
        listening = self.cmd( "echo A | telnet -e A %s %d" %
1121
                              ( self.ip, self.port ) )
1122
        if 'Unable' in listening:
1123 54c51c02 Bob Lantz
            warn( "Unable to contact the remote controller"
1124 2e089b5e Brandon Heller
                  " at %s:%d\n" % ( self.ip, self.port ) )