Statistics
| Branch: | Tag: | Revision:

mininet / mininet / node.py @ 163a66c6

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

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