mininet / mininet / node.py @ 7485b035
History | View | Annotate | Download (59.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 | 6e5ac34b | Bob Lantz | functionality are provided in the examples/ directory. By default,
|
15 | hosts share the root file system, but they may also specify private
|
||
16 | directories.
|
||
17 | 7d4b7b7f | Bob Lantz |
|
18 | 0dbfd3a6 | Bob Lantz | CPULimitedHost: a virtual host whose CPU bandwidth is limited by
|
19 | RT or CFS bandwidth limiting.
|
||
20 |
|
||
21 | 7d4b7b7f | Bob Lantz | Switch: superclass for switch nodes.
|
22 |
|
||
23 | UserSwitch: a switch using the user-space switch from the OpenFlow
|
||
24 | reference implementation.
|
||
25 |
|
||
26 | KernelSwitch: a switch using the kernel switch from the OpenFlow reference
|
||
27 | implementation.
|
||
28 |
|
||
29 | OVSSwitch: a switch using the OpenVSwitch OpenFlow-compatible switch
|
||
30 | implementation (openvswitch.org).
|
||
31 |
|
||
32 | Controller: superclass for OpenFlow controllers. The default controller
|
||
33 | is controller(8) from the reference implementation.
|
||
34 |
|
||
35 | NOXController: a controller node using NOX (noxrepo.org).
|
||
36 |
|
||
37 | RemoteController: a remote controller node, which may use any
|
||
38 | 7c371cf3 | Bob Lantz | arbitrary OpenFlow-compatible controller, and which is not
|
39 | created or managed by mininet.
|
||
40 | 31b43002 | Bob Lantz |
|
41 | 80be5642 | Bob Lantz | Future enhancements:
|
42 |
|
||
43 | - Possibly make Node, Switch and Controller more abstract so that
|
||
44 | they can be used for both local and remote nodes
|
||
45 |
|
||
46 | - Create proxy objects for remote nodes (Mininet: Cluster Edition)
|
||
47 | 7d4b7b7f | Bob Lantz | """
|
48 | 89bf3103 | Brandon Heller | |
49 | 723d068c | Brandon Heller | import os |
50 | 549f1ebc | Bob Lantz | import pty |
51 | 75d72d96 | Bob Lantz | import re |
52 | 723d068c | Brandon Heller | import signal |
53 | import select |
||
54 | 50774e40 | Bob Lantz | from subprocess import Popen, PIPE |
55 | 3236d33b | Andrew Ferguson | from time import sleep |
56 | 60d9ead6 | David Erickson | |
57 | 2db4268b | Bob Lantz | from mininet.log import info, error, warn, debug |
58 | 197b083f | Bob Lantz | from mininet.util import ( quietRun, errRun, errFail, moveIntf, isShellBuiltin, |
59 | edf60032 | Brandon Heller | numCores, retry, mountCgroups ) |
60 | f0010171 | Bob Lantz | from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN |
61 | c069542c | Bob Lantz | from mininet.link import Link, Intf, TCIntf, OVSIntf |
62 | f1e42ba5 | Cody | from re import findall |
63 | from distutils.version import StrictVersion |
||
64 | 2fffa0bb | Brandon Heller | |
65 | 80a8fa62 | Bob Lantz | class Node( object ): |
66 | """A virtual network node is simply a shell in a network namespace.
|
||
67 | We communicate with it using pipes."""
|
||
68 | 6d72a3cb | Bob Lantz | |
69 | dd159b4a | Bob Lantz | portBase = 0 # Nodes always start with eth0/port0, even in OF 1.0 |
70 | 54d026f6 | Bob Lantz | |
71 | 84a91a14 | Bob Lantz | def __init__( self, name, inNamespace=True, **params ): |
72 | 086ef80e | Bob Lantz | """name: name of node
|
73 | inNamespace: in network namespace?
|
||
74 | 736db20c | cody burkard | privateDirs: list of private directory strings or tuples
|
75 | 84a91a14 | Bob Lantz | params: Node parameters (see config() for details)"""
|
76 | |||
77 | # Make sure class actually works
|
||
78 | self.checkSetup()
|
||
79 | |||
80 | c265deed | Bob Lantz | self.name = params.get( 'name', name ) |
81 | 736db20c | cody burkard | self.privateDirs = params.get( 'privateDirs', [] ) |
82 | c265deed | Bob Lantz | self.inNamespace = params.get( 'inNamespace', inNamespace ) |
83 | 84a91a14 | Bob Lantz | |
84 | # Stash configuration parameters for future reference
|
||
85 | self.params = params
|
||
86 | |||
87 | self.intfs = {} # dict of port numbers to interfaces |
||
88 | self.ports = {} # dict of interfaces to port numbers |
||
89 | # replace with Port objects, eventually ?
|
||
90 | self.nameToIntf = {} # dict of interface names to Intfs |
||
91 | |||
92 | 14ff3ad3 | Bob Lantz | # Make pylint happy
|
93 | ( self.shell, self.execed, self.pid, self.stdin, self.stdout, |
||
94 | 33d548b4 | Brandon Heller | self.lastPid, self.lastCmd, self.pollOut ) = ( |
95 | None, None, None, None, None, None, None, None ) |
||
96 | 14ff3ad3 | Bob Lantz | self.waiting = False |
97 | self.readbuf = '' |
||
98 | |||
99 | 84a91a14 | Bob Lantz | # Start command interpreter shell
|
100 | self.startShell()
|
||
101 | 736db20c | cody burkard | self.mountPrivateDirs()
|
102 | 84a91a14 | Bob Lantz | |
103 | # File descriptor to node mapping support
|
||
104 | # Class variables and methods
|
||
105 | |||
106 | inToNode = {} # mapping of input fds to nodes
|
||
107 | outToNode = {} # mapping of output fds to nodes
|
||
108 | |||
109 | @classmethod
|
||
110 | def fdToNode( cls, fd ): |
||
111 | """Return node corresponding to given file descriptor.
|
||
112 | fd: file descriptor
|
||
113 | returns: node"""
|
||
114 | node = cls.outToNode.get( fd ) |
||
115 | return node or cls.inToNode.get( fd ) |
||
116 | |||
117 | # Command support via shell process in namespace
|
||
118 | c265deed | Bob Lantz | def startShell( self, mnopts=None ): |
119 | 84a91a14 | Bob Lantz | "Start a shell process for running commands"
|
120 | if self.shell: |
||
121 | c265deed | Bob Lantz | error( "%s: shell is already running\n" % self.name ) |
122 | 84a91a14 | Bob Lantz | return
|
123 | 216a4b7c | Bob Lantz | # mnexec: (c)lose descriptors, (d)etach from tty,
|
124 | 14ff3ad3 | Bob Lantz | # (p)rint pid, and run in (n)amespace
|
125 | c265deed | Bob Lantz | opts = '-cd' if mnopts is None else mnopts |
126 | 89bf3103 | Brandon Heller | if self.inNamespace: |
127 | b14b1ee4 | Bob Lantz | opts += 'n'
|
128 | 549f1ebc | Bob Lantz | # bash -m: enable job control, i: force interactive
|
129 | 1bf1a4d5 | Bob Lantz | # -s: pass $* to shell, and make process easy to find in ps
|
130 | 82e0e9f3 | Bob Lantz | # prompt is set to sentinel chr( 127 )
|
131 | 7a3159c9 | Bob Lantz | cmd = [ 'mnexec', opts, 'env', 'PS1=' + chr( 127 ), |
132 | c265deed | Bob Lantz | 'bash', '--norc', '-mis', 'mininet:' + self.name ] |
133 | 549f1ebc | Bob Lantz | # Spawn a shell subprocess in a pseudo-tty, to disable buffering
|
134 | # in the subprocess and insulate it from signals (e.g. SIGINT)
|
||
135 | # received by the parent
|
||
136 | master, slave = pty.openpty() |
||
137 | c265deed | Bob Lantz | self.shell = self._popen( cmd, stdin=slave, stdout=slave, stderr=slave, |
138 | 549f1ebc | Bob Lantz | close_fds=False )
|
139 | c265deed | Bob Lantz | self.stdin = os.fdopen( master, 'rw' ) |
140 | 549f1ebc | Bob Lantz | self.stdout = self.stdin |
141 | 121eb449 | Bob Lantz | self.pid = self.shell.pid |
142 | 89bf3103 | Brandon Heller | self.pollOut = select.poll()
|
143 | 80a8fa62 | Bob Lantz | self.pollOut.register( self.stdout ) |
144 | 89bf3103 | Brandon Heller | # Maintain mapping between file descriptors and nodes
|
145 | bcacfc05 | Bob Lantz | # This is useful for monitoring multiple nodes
|
146 | 89bf3103 | Brandon Heller | # using select.poll()
|
147 | 80a8fa62 | Bob Lantz | self.outToNode[ self.stdout.fileno() ] = self |
148 | self.inToNode[ self.stdin.fileno() ] = self |
||
149 | 89bf3103 | Brandon Heller | self.execed = False |
150 | bcacfc05 | Bob Lantz | self.lastCmd = None |
151 | self.lastPid = None |
||
152 | ec7b211c | Bob Lantz | self.readbuf = '' |
153 | 549f1ebc | Bob Lantz | # Wait for prompt
|
154 | while True: |
||
155 | data = self.read( 1024 ) |
||
156 | 771850b9 | Bob Lantz | if data[ -1 ] == chr( 127 ): |
157 | 549f1ebc | Bob Lantz | break
|
158 | self.pollOut.poll()
|
||
159 | self.waiting = False |
||
160 | self.cmd( 'stty -echo' ) |
||
161 | c11d5773 | cody burkard | self.cmd( 'set +m' ) |
162 | 89bf3103 | Brandon Heller | |
163 | 736db20c | cody burkard | def mountPrivateDirs( self ): |
164 | "mount private directories"
|
||
165 | for directory in self.privateDirs: |
||
166 | if isinstance( directory, tuple ): |
||
167 | # mount given private directory
|
||
168 | a562ca1b | Bob Lantz | privateDir = directory[ 1 ] % self.__dict__ |
169 | 736db20c | cody burkard | mountPoint = directory[ 0 ]
|
170 | self.cmd( 'mkdir -p %s' % privateDir ) |
||
171 | self.cmd( 'mkdir -p %s' % mountPoint ) |
||
172 | self.cmd( 'mount --bind %s %s' % |
||
173 | ( privateDir, mountPoint ) ) |
||
174 | else:
|
||
175 | # mount temporary filesystem on directory
|
||
176 | a562ca1b | Bob Lantz | self.cmd( 'mkdir -p %s' % directory ) |
177 | 736db20c | cody burkard | self.cmd( 'mount -n -t tmpfs tmpfs %s' % directory ) |
178 | |||
179 | 6a363f65 | cody burkard | def unmountPrivateDirs( self ): |
180 | "mount private directories"
|
||
181 | for directory in self.privateDirs: |
||
182 | if isinstance( directory, tuple ): |
||
183 | self.cmd( 'umount ', directory[ 0 ] ) |
||
184 | else:
|
||
185 | self.cmd( 'umount ', directory ) |
||
186 | |||
187 | c265deed | Bob Lantz | def _popen( self, cmd, **params ): |
188 | """Internal method: spawn and return a process
|
||
189 | cmd: command to run (list)
|
||
190 | params: parameters to Popen()"""
|
||
191 | b1ec912d | Bob Lantz | # Leave this is as an instance method for now
|
192 | assert self |
||
193 | c265deed | Bob Lantz | return Popen( cmd, **params )
|
194 | |||
195 | 80a8fa62 | Bob Lantz | def cleanup( self ): |
196 | "Help python collect its garbage."
|
||
197 | 10be691b | Bob Lantz | # Intfs may end up in root NS
|
198 | for intfName in self.intfNames(): |
||
199 | if self.name in intfName: |
||
200 | quietRun( 'ip link del ' + intfName )
|
||
201 | 89bf3103 | Brandon Heller | self.shell = None |
202 | |||
203 | # Subshell I/O, commands and control
|
||
204 | 163a6cf3 | Bob Lantz | |
205 | 28f46c8d | Bob Lantz | def read( self, maxbytes=1024 ): |
206 | ec7b211c | Bob Lantz | """Buffered read from node, non-blocking.
|
207 | 28f46c8d | Bob Lantz | maxbytes: maximum number of bytes to return"""
|
208 | ec7b211c | Bob Lantz | count = len( self.readbuf ) |
209 | 28f46c8d | Bob Lantz | if count < maxbytes:
|
210 | data = os.read( self.stdout.fileno(), maxbytes - count )
|
||
211 | ec7b211c | Bob Lantz | self.readbuf += data
|
212 | 28f46c8d | Bob Lantz | if maxbytes >= len( self.readbuf ): |
213 | ec7b211c | Bob Lantz | result = self.readbuf
|
214 | self.readbuf = '' |
||
215 | else:
|
||
216 | 28f46c8d | Bob Lantz | result = self.readbuf[ :maxbytes ]
|
217 | self.readbuf = self.readbuf[ maxbytes: ] |
||
218 | ec7b211c | Bob Lantz | return result
|
219 | |||
220 | def readline( self ): |
||
221 | """Buffered readline from node, non-blocking.
|
||
222 | returns: line (minus newline) or None"""
|
||
223 | self.readbuf += self.read( 1024 ) |
||
224 | if '\n' not in self.readbuf: |
||
225 | return None |
||
226 | pos = self.readbuf.find( '\n' ) |
||
227 | 0bd5c651 | Brandon Heller | line = self.readbuf[ 0: pos ] |
228 | ec7b211c | Bob Lantz | self.readbuf = self.readbuf[ pos + 1: ] |
229 | return line
|
||
230 | 80a8fa62 | Bob Lantz | |
231 | def write( self, data ): |
||
232 | """Write data to node.
|
||
233 | data: string"""
|
||
234 | os.write( self.stdin.fileno(), data )
|
||
235 | |||
236 | def terminate( self ): |
||
237 | 6d72a3cb | Bob Lantz | "Send kill signal to Node and clean up after it."
|
238 | 6a363f65 | cody burkard | self.unmountPrivateDirs()
|
239 | 15146d90 | Brian O'Connor | if self.shell: |
240 | c265deed | Bob Lantz | if self.shell.poll() is None: |
241 | os.killpg( self.shell.pid, signal.SIGHUP )
|
||
242 | 89bf3103 | Brandon Heller | self.cleanup()
|
243 | |||
244 | b1ec912d | Bob Lantz | def stop( self, deleteIntfs=False ): |
245 | """Stop node.
|
||
246 | deleteIntfs: delete interfaces? (False)"""
|
||
247 | if deleteIntfs:
|
||
248 | self.deleteIntfs()
|
||
249 | 89bf3103 | Brandon Heller | self.terminate()
|
250 | |||
251 | f24e70a4 | Bob Lantz | def waitReadable( self, timeoutms=None ): |
252 | """Wait until node's output is readable.
|
||
253 | timeoutms: timeout in ms or None to wait indefinitely."""
|
||
254 | ec7b211c | Bob Lantz | if len( self.readbuf ) == 0: |
255 | f24e70a4 | Bob Lantz | self.pollOut.poll( timeoutms )
|
256 | 89bf3103 | Brandon Heller | |
257 | 121eb449 | Bob Lantz | def sendCmd( self, *args, **kwargs ): |
258 | 80a8fa62 | Bob Lantz | """Send a command, followed by a command to echo a sentinel,
|
259 | 121eb449 | Bob Lantz | and return without waiting for the command to complete.
|
260 | args: command and arguments, or string
|
||
261 | printPid: print command's PID?"""
|
||
262 | 9db6cdc2 | Bob Lantz | assert self.shell and not self.waiting |
263 | c49b216c | Bob Lantz | printPid = kwargs.get( 'printPid', True ) |
264 | 318ae55e | Bob Lantz | # Allow sendCmd( [ list ] )
|
265 | c273f490 | Bob Lantz | if len( args ) == 1 and isinstance( args[ 0 ], list ): |
266 | 318ae55e | Bob Lantz | cmd = args[ 0 ]
|
267 | # Allow sendCmd( cmd, arg1, arg2... )
|
||
268 | elif len( args ) > 0: |
||
269 | 121eb449 | Bob Lantz | cmd = args |
270 | 318ae55e | Bob Lantz | # Convert to string
|
271 | 121eb449 | Bob Lantz | if not isinstance( cmd, str ): |
272 | a6bcad8f | Bob Lantz | cmd = ' '.join( [ str( c ) for c in cmd ] ) |
273 | f24e70a4 | Bob Lantz | if not re.search( r'\w', cmd ): |
274 | # Replace empty commands with something harmless
|
||
275 | cmd = 'echo -n'
|
||
276 | d1b29d58 | Bob Lantz | self.lastCmd = cmd
|
277 | ce167380 | cody burkard | # if a builtin command is backgrounded, it still yields a PID
|
278 | if len( cmd ) > 0 and cmd[ -1 ] == '&': |
||
279 | # print ^A{pid}\n so monitor() can set lastPid
|
||
280 | cmd += ' printf "\\001%d\\012" $! '
|
||
281 | elif printPid and not isShellBuiltin( cmd ): |
||
282 | cmd = 'mnexec -p ' + cmd
|
||
283 | 612b21cb | Bob Lantz | self.write( cmd + '\n' ) |
284 | bcacfc05 | Bob Lantz | self.lastPid = None |
285 | 89bf3103 | Brandon Heller | self.waiting = True |
286 | |||
287 | 549f1ebc | Bob Lantz | def sendInt( self, intr=chr( 3 ) ): |
288 | bcacfc05 | Bob Lantz | "Interrupt running command."
|
289 | c265deed | Bob Lantz | debug( 'sendInt: writing chr(%d)\n' % ord( intr ) ) |
290 | 549f1ebc | Bob Lantz | self.write( intr )
|
291 | 4065511a | Bob Lantz | |
292 | 549f1ebc | Bob Lantz | def monitor( self, timeoutms=None, findPid=True ): |
293 | e4c82e52 | Bob Lantz | """Monitor and return the output of a command.
|
294 | f24e70a4 | Bob Lantz | Set self.waiting to False if command has completed.
|
295 | c265deed | Bob Lantz | timeoutms: timeout in ms or None to wait indefinitely
|
296 | findPid: look for PID from mnexec -p"""
|
||
297 | f24e70a4 | Bob Lantz | self.waitReadable( timeoutms )
|
298 | 80a8fa62 | Bob Lantz | data = self.read( 1024 ) |
299 | c11d5773 | cody burkard | pidre = r'\[\d+\] \d+\r\n'
|
300 | bcacfc05 | Bob Lantz | # Look for PID
|
301 | e9013d76 | Bob Lantz | marker = chr( 1 ) + r'\d+\r\n' |
302 | 549f1ebc | Bob Lantz | if findPid and chr( 1 ) in data: |
303 | c11d5773 | cody burkard | # suppress the job and PID of a backgrounded command
|
304 | if re.findall( pidre, data ):
|
||
305 | data = re.sub( pidre, '', data )
|
||
306 | 161e7997 | Brian O'Connor | # Marker can be read in chunks; continue until all of it is read
|
307 | while not re.findall( marker, data ): |
||
308 | data += self.read( 1024 ) |
||
309 | bcacfc05 | Bob Lantz | markers = re.findall( marker, data ) |
310 | if markers:
|
||
311 | self.lastPid = int( markers[ 0 ][ 1: ] ) |
||
312 | data = re.sub( marker, '', data )
|
||
313 | # Look for sentinel/EOF
|
||
314 | 4065511a | Bob Lantz | if len( data ) > 0 and data[ -1 ] == chr( 127 ): |
315 | 89bf3103 | Brandon Heller | self.waiting = False |
316 | e4c82e52 | Bob Lantz | data = data[ :-1 ]
|
317 | 4065511a | Bob Lantz | elif chr( 127 ) in data: |
318 | bcacfc05 | Bob Lantz | self.waiting = False |
319 | e4c82e52 | Bob Lantz | data = data.replace( chr( 127 ), '' ) |
320 | return data
|
||
321 | 723d068c | Brandon Heller | |
322 | c265deed | Bob Lantz | def waitOutput( self, verbose=False, findPid=True ): |
323 | 80a8fa62 | Bob Lantz | """Wait for a command to complete.
|
324 | 7c371cf3 | Bob Lantz | Completion is signaled by a sentinel character, ASCII(127)
|
325 | 80a8fa62 | Bob Lantz | appearing in the output stream. Wait for the sentinel and return
|
326 | efc9a01c | Bob Lantz | the output, including trailing newline.
|
327 | verbose: print output interactively"""
|
||
328 | log = info if verbose else debug |
||
329 | 89bf3103 | Brandon Heller | output = ''
|
330 | e4c82e52 | Bob Lantz | while self.waiting: |
331 | b1ec912d | Bob Lantz | data = self.monitor( findPid=findPid )
|
332 | 4065511a | Bob Lantz | output += data |
333 | log( data ) |
||
334 | 89bf3103 | Brandon Heller | return output
|
335 | |||
336 | 121eb449 | Bob Lantz | def cmd( self, *args, **kwargs ): |
337 | 80a8fa62 | Bob Lantz | """Send a command, wait for output, and return it.
|
338 | cmd: string"""
|
||
339 | 121eb449 | Bob Lantz | verbose = kwargs.get( 'verbose', False ) |
340 | efc9a01c | Bob Lantz | log = info if verbose else debug |
341 | 121eb449 | Bob Lantz | log( '*** %s : %s\n' % ( self.name, args ) ) |
342 | 9db6cdc2 | Bob Lantz | if self.shell: |
343 | self.sendCmd( *args, **kwargs )
|
||
344 | return self.waitOutput( verbose ) |
||
345 | else:
|
||
346 | warn( '(%s exited - ignoring cmd%s)\n' % ( self, args ) ) |
||
347 | 89bf3103 | Brandon Heller | |
348 | 121eb449 | Bob Lantz | def cmdPrint( self, *args): |
349 | 80a8fa62 | Bob Lantz | """Call cmd and printing its output
|
350 | cmd: string"""
|
||
351 | 121eb449 | Bob Lantz | return self.cmd( *args, **{ 'verbose': True } ) |
352 | 89bf3103 | Brandon Heller | |
353 | 089e8130 | Bob Lantz | def popen( self, *args, **kwargs ): |
354 | """Return a Popen() object in our namespace
|
||
355 | args: Popen() args, single list, or string
|
||
356 | kwargs: Popen() keyword args"""
|
||
357 | defaults = { 'stdout': PIPE, 'stderr': PIPE, |
||
358 | 5ca91f9c | Bob Lantz | 'mncmd':
|
359 | e5a15ced | Bob Lantz | [ 'mnexec', '-da', str( self.pid ) ] } |
360 | 089e8130 | Bob Lantz | defaults.update( kwargs ) |
361 | if len( args ) == 1: |
||
362 | c273f490 | Bob Lantz | if isinstance( args[ 0 ], list ): |
363 | 089e8130 | Bob Lantz | # popen([cmd, arg1, arg2...])
|
364 | cmd = args[ 0 ]
|
||
365 | 9a8bdfd7 | Bob Lantz | elif isinstance( args[ 0 ], basestring ): |
366 | 089e8130 | Bob Lantz | # popen("cmd arg1 arg2...")
|
367 | cmd = args[ 0 ].split()
|
||
368 | b9100834 | Bob Lantz | else:
|
369 | raise Exception( 'popen() requires a string or list' ) |
||
370 | 089e8130 | Bob Lantz | elif len( args ) > 0: |
371 | # popen( cmd, arg1, arg2... )
|
||
372 | 06f7408c | Bob Lantz | cmd = list( args )
|
373 | 089e8130 | Bob Lantz | # Attach to our namespace using mnexec -a
|
374 | c265deed | Bob Lantz | cmd = defaults.pop( 'mncmd' ) + cmd
|
375 | b9100834 | Bob Lantz | # Shell requires a string, not a list!
|
376 | if defaults.get( 'shell', False ): |
||
377 | cmd = ' '.join( cmd )
|
||
378 | c265deed | Bob Lantz | popen = self._popen( cmd, **defaults )
|
379 | return popen
|
||
380 | 089e8130 | Bob Lantz | |
381 | def pexec( self, *args, **kwargs ): |
||
382 | """Execute a command using popen
|
||
383 | returns: out, err, exitcode"""
|
||
384 | c265deed | Bob Lantz | popen = self.popen( *args, stdin=PIPE, stdout=PIPE, stderr=PIPE,
|
385 | 7a3159c9 | Bob Lantz | **kwargs ) |
386 | c265deed | Bob Lantz | # Warning: this can fail with large numbers of fds!
|
387 | 089e8130 | Bob Lantz | out, err = popen.communicate() |
388 | exitcode = popen.wait() |
||
389 | return out, err, exitcode
|
||
390 | |||
391 | 89bf3103 | Brandon Heller | # Interface management, configuration, and routing
|
392 | efc9a01c | Bob Lantz | |
393 | # BL notes: This might be a bit redundant or over-complicated.
|
||
394 | # However, it does allow a bit of specialization, including
|
||
395 | # changing the canonical interface names. It's also tricky since
|
||
396 | # the real interfaces are created as veth pairs, so we can't
|
||
397 | # make a single interface at a time.
|
||
398 | |||
399 | d44a5843 | Bob Lantz | def newPort( self ): |
400 | "Return the next port number to allocate."
|
||
401 | if len( self.ports ) > 0: |
||
402 | return max( self.ports.values() ) + 1 |
||
403 | dd159b4a | Bob Lantz | return self.portBase |
404 | 89bf3103 | Brandon Heller | |
405 | c265deed | Bob Lantz | def addIntf( self, intf, port=None, moveIntfFn=moveIntf ): |
406 | efc9a01c | Bob Lantz | """Add an interface.
|
407 | a6bcad8f | Bob Lantz | intf: interface
|
408 | c265deed | Bob Lantz | port: port number (optional, typically OpenFlow port number)
|
409 | moveIntfFn: function to move interface (optional)"""
|
||
410 | 121eb449 | Bob Lantz | if port is None: |
411 | port = self.newPort()
|
||
412 | efc9a01c | Bob Lantz | self.intfs[ port ] = intf
|
413 | self.ports[ intf ] = port
|
||
414 | a6bcad8f | Bob Lantz | self.nameToIntf[ intf.name ] = intf
|
415 | 84a91a14 | Bob Lantz | debug( '\n' )
|
416 | c265deed | Bob Lantz | debug( 'added intf %s (%d) to node %s\n' % (
|
417 | intf, port, self.name ) )
|
||
418 | efc9a01c | Bob Lantz | if self.inNamespace: |
419 | 84a91a14 | Bob Lantz | debug( 'moving', intf, 'into namespace for', self.name, '\n' ) |
420 | c265deed | Bob Lantz | moveIntfFn( intf.name, self )
|
421 | efc9a01c | Bob Lantz | |
422 | a6bcad8f | Bob Lantz | def defaultIntf( self ): |
423 | "Return interface for lowest port"
|
||
424 | ports = self.intfs.keys()
|
||
425 | if ports:
|
||
426 | return self.intfs[ min( ports ) ] |
||
427 | efc99154 | Bob Lantz | else:
|
428 | warn( '*** defaultIntf: warning:', self.name, |
||
429 | edf60032 | Brandon Heller | 'has no interfaces\n' )
|
430 | efc9a01c | Bob Lantz | |
431 | b1ec912d | Bob Lantz | def intf( self, intf=None ): |
432 | bf208cde | Brandon Heller | """Return our interface object with given string name,
|
433 | default intf if name is falsy (None, empty string, etc).
|
||
434 | or the input intf arg.
|
||
435 |
|
||
436 | Having this fcn return its arg for Intf objects makes it
|
||
437 | easier to construct functions with flexible input args for
|
||
438 | interfaces (those that accept both string names and Intf objects).
|
||
439 | """
|
||
440 | a6bcad8f | Bob Lantz | if not intf: |
441 | return self.defaultIntf() |
||
442 | 9a8bdfd7 | Bob Lantz | elif isinstance( intf, basestring): |
443 | a6bcad8f | Bob Lantz | return self.nameToIntf[ intf ] |
444 | else:
|
||
445 | bf208cde | Brandon Heller | return intf
|
446 | efc9a01c | Bob Lantz | |
447 | fb2f6523 | Bob Lantz | def connectionsTo( self, node): |
448 | 8856d284 | Bob Lantz | "Return [ intf1, intf2... ] for all intfs that connect self to node."
|
449 | fb2f6523 | Bob Lantz | # We could optimize this if it is important
|
450 | connections = [] |
||
451 | 8856d284 | Bob Lantz | for intf in self.intfList(): |
452 | a6bcad8f | Bob Lantz | link = intf.link |
453 | 8856d284 | Bob Lantz | if link:
|
454 | node1, node2 = link.intf1.node, link.intf2.node |
||
455 | if node1 == self and node2 == node: |
||
456 | connections += [ ( intf, link.intf2 ) ] |
||
457 | elif node1 == node and node2 == self: |
||
458 | connections += [ ( intf, link.intf1 ) ] |
||
459 | fb2f6523 | Bob Lantz | return connections
|
460 | 35341142 | Bob Lantz | |
461 | 10be691b | Bob Lantz | def deleteIntfs( self, checkName=True ): |
462 | """Delete all of our interfaces.
|
||
463 | checkName: only delete interfaces that contain our name"""
|
||
464 | 086ef80e | Bob Lantz | # In theory the interfaces should go away after we shut down.
|
465 | # However, this takes time, so we're better off removing them
|
||
466 | # explicitly so that we won't get errors if we run before they
|
||
467 | d44a5843 | Bob Lantz | # have been removed by the kernel. Unfortunately this is very slow,
|
468 | # at least with Linux kernels before 2.6.33
|
||
469 | 086ef80e | Bob Lantz | for intf in self.intfs.values(): |
470 | 10be691b | Bob Lantz | # Protect against deleting hardware interfaces
|
471 | if ( self.name in intf.name ) or ( not checkName ): |
||
472 | intf.delete() |
||
473 | info( '.' )
|
||
474 | 086ef80e | Bob Lantz | |
475 | a6bcad8f | Bob Lantz | # Routing support
|
476 | 376bcba4 | Brandon Heller | |
477 | 80a8fa62 | Bob Lantz | def setARP( self, ip, mac ): |
478 | """Add an ARP entry.
|
||
479 | 019bff82 | Bob Lantz | ip: IP address as string
|
480 | mac: MAC address as string"""
|
||
481 | 121eb449 | Bob Lantz | result = self.cmd( 'arp', '-s', ip, mac ) |
482 | 376bcba4 | Brandon Heller | return result
|
483 | |||
484 | 80a8fa62 | Bob Lantz | def setHostRoute( self, ip, intf ): |
485 | """Add route to host.
|
||
486 | ip: IP address as dotted decimal
|
||
487 | intf: string, interface name"""
|
||
488 | 14ff3ad3 | Bob Lantz | return self.cmd( 'route add -host', ip, 'dev', intf ) |
489 | 89bf3103 | Brandon Heller | |
490 | 84a91a14 | Bob Lantz | def setDefaultRoute( self, intf=None ): |
491 | 80a8fa62 | Bob Lantz | """Set the default route to go through intf.
|
492 | dd21df3c | Bob Lantz | intf: Intf or {dev <intfname> via <gw-ip> ...}"""
|
493 | # Note setParam won't call us if intf is none
|
||
494 | 9a8bdfd7 | Bob Lantz | if isinstance( intf, basestring ) and ' ' in intf: |
495 | dd21df3c | Bob Lantz | params = intf |
496 | else:
|
||
497 | 12758046 | Bob Lantz | params = 'dev %s' % intf
|
498 | c5d9e0e0 | Bob Lantz | # Do this in one line in case we're messing with the root namespace
|
499 | self.cmd( 'ip route del default; ip route add default', params ) |
||
500 | 89bf3103 | Brandon Heller | |
501 | 84a91a14 | Bob Lantz | # Convenience and configuration methods
|
502 | 54d026f6 | Bob Lantz | |
503 | b684ff78 | Bob Lantz | def setMAC( self, mac, intf=None ): |
504 | a6bcad8f | Bob Lantz | """Set the MAC address for an interface.
|
505 | intf: intf or intf name
|
||
506 | mac: MAC address as string"""
|
||
507 | return self.intf( intf ).setMAC( mac ) |
||
508 | |||
509 | b684ff78 | Bob Lantz | def setIP( self, ip, prefixLen=8, intf=None ): |
510 | a6bcad8f | Bob Lantz | """Set the IP address for an interface.
|
511 | bf208cde | Brandon Heller | intf: intf or intf name
|
512 | a6bcad8f | Bob Lantz | ip: IP address as a string
|
513 | prefixLen: prefix length, e.g. 8 for /8 or 16M addrs"""
|
||
514 | a49c85a6 | Bob Lantz | # This should probably be rethought
|
515 | if '/' not in ip: |
||
516 | ip = '%s/%s' % ( ip, prefixLen )
|
||
517 | return self.intf( intf ).setIP( ip ) |
||
518 | 54d026f6 | Bob Lantz | |
519 | d44a5843 | Bob Lantz | def IP( self, intf=None ): |
520 | "Return IP address of a node or specific interface."
|
||
521 | b684ff78 | Bob Lantz | return self.intf( intf ).IP() |
522 | d44a5843 | Bob Lantz | |
523 | def MAC( self, intf=None ): |
||
524 | "Return MAC address of a node or specific interface."
|
||
525 | 0aefb0e0 | Bob Lantz | return self.intf( intf ).MAC() |
526 | 54d026f6 | Bob Lantz | |
527 | a6bcad8f | Bob Lantz | def intfIsUp( self, intf=None ): |
528 | d44a5843 | Bob Lantz | "Check if an interface is up."
|
529 | a6bcad8f | Bob Lantz | return self.intf( intf ).isUp() |
530 | |||
531 | 84a91a14 | Bob Lantz | # The reason why we configure things in this way is so
|
532 | # That the parameters can be listed and documented in
|
||
533 | # the config method.
|
||
534 | # Dealing with subclasses and superclasses is slightly
|
||
535 | # annoying, but at least the information is there!
|
||
536 | |||
537 | def setParam( self, results, method, **param ): |
||
538 | 216a4b7c | Bob Lantz | """Internal method: configure a *single* parameter
|
539 | results: dict of results to update
|
||
540 | method: config method name
|
||
541 | param: arg=value (ignore if value=None)
|
||
542 | value may also be list or dict"""
|
||
543 | 84a91a14 | Bob Lantz | name, value = param.items()[ 0 ]
|
544 | a64f8c28 | Bob Lantz | if value is None: |
545 | return
|
||
546 | 84a91a14 | Bob Lantz | f = getattr( self, method, None ) |
547 | 08d611f4 | Cody Burkard | if not f: |
548 | 84a91a14 | Bob Lantz | return
|
549 | c273f490 | Bob Lantz | if isinstance( value, list ): |
550 | 84a91a14 | Bob Lantz | result = f( *value ) |
551 | 273c4e94 | Bob Lantz | elif isinstance( value, dict ): |
552 | 84a91a14 | Bob Lantz | result = f( **value ) |
553 | 54d026f6 | Bob Lantz | else:
|
554 | 84a91a14 | Bob Lantz | result = f( value ) |
555 | results[ name ] = result |
||
556 | 216a4b7c | Bob Lantz | return result
|
557 | 54d026f6 | Bob Lantz | |
558 | 8856d284 | Bob Lantz | def config( self, mac=None, ip=None, |
559 | defaultRoute=None, lo='up', **_params ): |
||
560 | 84a91a14 | Bob Lantz | """Configure Node according to (optional) parameters:
|
561 | mac: MAC address for default interface
|
||
562 | ip: IP address for default interface
|
||
563 | ifconfig: arbitrary interface configuration
|
||
564 | Subclasses should override this method and call
|
||
565 | the parent class's config(**params)"""
|
||
566 | # If we were overriding this method, we would call
|
||
567 | # the superclass config method here as follows:
|
||
568 | 14ff3ad3 | Bob Lantz | # r = Parent.config( **_params )
|
569 | 84a91a14 | Bob Lantz | r = {} |
570 | self.setParam( r, 'setMAC', mac=mac ) |
||
571 | self.setParam( r, 'setIP', ip=ip ) |
||
572 | e5754ae9 | Shaun Crampton | self.setParam( r, 'setDefaultRoute', defaultRoute=defaultRoute ) |
573 | 8856d284 | Bob Lantz | # This should be examined
|
574 | self.cmd( 'ifconfig lo ' + lo ) |
||
575 | 84a91a14 | Bob Lantz | return r
|
576 | |||
577 | def configDefault( self, **moreParams ): |
||
578 | "Configure with default parameters"
|
||
579 | self.params.update( moreParams )
|
||
580 | self.config( **self.params ) |
||
581 | |||
582 | a6bcad8f | Bob Lantz | # This is here for backward compatibility
|
583 | def linkTo( self, node, link=Link ): |
||
584 | """(Deprecated) Link to another node
|
||
585 | replace with Link( node1, node2)"""
|
||
586 | return link( self, node ) |
||
587 | 89bf3103 | Brandon Heller | |
588 | # Other methods
|
||
589 | a6bcad8f | Bob Lantz | |
590 | 03dd914e | Bob Lantz | def intfList( self ): |
591 | "List of our interfaces sorted by port number"
|
||
592 | return [ self.intfs[ p ] for p in sorted( self.intfs.iterkeys() ) ] |
||
593 | |||
594 | a6bcad8f | Bob Lantz | def intfNames( self ): |
595 | 03dd914e | Bob Lantz | "The names of our interfaces sorted by port number"
|
596 | return [ str( i ) for i in self.intfList() ] |
||
597 | a6bcad8f | Bob Lantz | |
598 | 8856d284 | Bob Lantz | def __repr__( self ): |
599 | "More informative string representation"
|
||
600 | intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() ) |
||
601 | edf60032 | Brandon Heller | for i in self.intfList() ] ) ) |
602 | 8856d284 | Bob Lantz | return '<%s %s: %s pid=%s> ' % ( |
603 | c0095746 | Brandon Heller | self.__class__.__name__, self.name, intfs, self.pid ) |
604 | 8856d284 | Bob Lantz | |
605 | 80a8fa62 | Bob Lantz | def __str__( self ): |
606 | 8856d284 | Bob Lantz | "Abbreviated string representation"
|
607 | return self.name |
||
608 | 89bf3103 | Brandon Heller | |
609 | 14ff3ad3 | Bob Lantz | # Automatic class setup support
|
610 | |||
611 | isSetup = False
|
||
612 | |||
613 | @classmethod
|
||
614 | def checkSetup( cls ): |
||
615 | "Make sure our class and superclasses are set up"
|
||
616 | while cls and not getattr( cls, 'isSetup', True ): |
||
617 | cls.setup() |
||
618 | cls.isSetup = True
|
||
619 | # Make pylint happy
|
||
620 | cls = getattr( type( cls ), '__base__', None ) |
||
621 | |||
622 | @classmethod
|
||
623 | def setup( cls ): |
||
624 | "Make sure our class dependencies are available"
|
||
625 | pathCheck( 'mnexec', 'ifconfig', moduleName='Mininet') |
||
626 | 89bf3103 | Brandon Heller | |
627 | 80a8fa62 | Bob Lantz | class Host( Node ): |
628 | 216a4b7c | Bob Lantz | "A host is simply a Node"
|
629 | pass
|
||
630 | |||
631 | class CPULimitedHost( Host ): |
||
632 | 84a91a14 | Bob Lantz | |
633 | "CPU limited host"
|
||
634 | |||
635 | 59542784 | Bob Lantz | def __init__( self, name, sched='cfs', **kwargs ): |
636 | Host.__init__( self, name, **kwargs )
|
||
637 | 197b083f | Bob Lantz | # Initialize class if necessary
|
638 | if not CPULimitedHost.inited: |
||
639 | CPULimitedHost.init() |
||
640 | 84a91a14 | Bob Lantz | # Create a cgroup and move shell into it
|
641 | 197b083f | Bob Lantz | self.cgroup = 'cpu,cpuacct,cpuset:/' + self.name |
642 | 8a622c3a | Bob Lantz | errFail( 'cgcreate -g ' + self.cgroup ) |
643 | 197b083f | Bob Lantz | # We don't add ourselves to a cpuset because you must
|
644 | # specify the cpu and memory placement first
|
||
645 | errFail( 'cgclassify -g cpu,cpuacct:/%s %s' % ( self.name, self.pid ) ) |
||
646 | 44af37bc | Bob Lantz | # BL: Setting the correct period/quota is tricky, particularly
|
647 | # for RT. RT allows very small quotas, but the overhead
|
||
648 | # seems to be high. CFS has a mininimum quota of 1 ms, but
|
||
649 | # still does better with larger period values.
|
||
650 | self.period_us = kwargs.get( 'period_us', 100000 ) |
||
651 | 59542784 | Bob Lantz | self.sched = sched
|
652 | a562ca1b | Bob Lantz | if sched == 'rt': |
653 | self.checkRtGroupSched()
|
||
654 | self.rtprio = 20 |
||
655 | 84a91a14 | Bob Lantz | |
656 | def cgroupSet( self, param, value, resource='cpu' ): |
||
657 | "Set a cgroup parameter and return its value"
|
||
658 | cmd = 'cgset -r %s.%s=%s /%s' % (
|
||
659 | resource, param, value, self.name )
|
||
660 | 8e3699ec | Bob Lantz | quietRun( cmd ) |
661 | 8a622c3a | Bob Lantz | nvalue = int( self.cgroupGet( param, resource ) ) |
662 | if nvalue != value:
|
||
663 | error( '*** error: cgroupSet: %s set to %s instead of %s\n'
|
||
664 | % ( param, nvalue, value ) ) |
||
665 | return nvalue
|
||
666 | 84a91a14 | Bob Lantz | |
667 | def cgroupGet( self, param, resource='cpu' ): |
||
668 | 14ff3ad3 | Bob Lantz | "Return value of cgroup parameter"
|
669 | 84a91a14 | Bob Lantz | cmd = 'cgget -r %s.%s /%s' % (
|
670 | resource, param, self.name )
|
||
671 | 197b083f | Bob Lantz | return int( quietRun( cmd ).split()[ -1 ] ) |
672 | 84a91a14 | Bob Lantz | |
673 | 28833d86 | Bob Lantz | def cgroupDel( self ): |
674 | "Clean up our cgroup"
|
||
675 | # info( '*** deleting cgroup', self.cgroup, '\n' )
|
||
676 | 612b21cb | Bob Lantz | _out, _err, exitcode = errRun( 'cgdelete -r ' + self.cgroup ) |
677 | ef59cd88 | Bob Lantz | return exitcode == 0 # success condition |
678 | 28833d86 | Bob Lantz | |
679 | 089e8130 | Bob Lantz | def popen( self, *args, **kwargs ): |
680 | """Return a Popen() object in node's namespace
|
||
681 | args: Popen() args, single list, or string
|
||
682 | kwargs: Popen() keyword args"""
|
||
683 | # Tell mnexec to execute command in our cgroup
|
||
684 | e16c5fe9 | cody burkard | mncmd = [ 'mnexec', '-g', self.name, |
685 | '-da', str( self.pid ) ] |
||
686 | 08d611f4 | Cody Burkard | # if our cgroup is not given any cpu time,
|
687 | # we cannot assign the RR Scheduler.
|
||
688 | 4f8aa1d8 | Bob Lantz | if self.sched == 'rt': |
689 | if int( self.cgroupGet( 'rt_runtime_us', 'cpu' ) ) <= 0: |
||
690 | mncmd += [ '-r', str( self.rtprio ) ] |
||
691 | else:
|
||
692 | b1ec912d | Bob Lantz | debug( '*** error: not enough cpu time available for %s.' %
|
693 | self.name, 'Using cfs scheduler for subprocess\n' ) |
||
694 | 089e8130 | Bob Lantz | return Host.popen( self, *args, mncmd=mncmd, **kwargs ) |
695 | |||
696 | 28833d86 | Bob Lantz | def cleanup( self ): |
697 | 59eeeadb | Brian O'Connor | "Clean up Node, then clean up our cgroup"
|
698 | super( CPULimitedHost, self ).cleanup() |
||
699 | 28833d86 | Bob Lantz | retry( retries=3, delaySecs=1, fn=self.cgroupDel ) |
700 | |||
701 | 820c3be7 | Bob Lantz | _rtGroupSched = False # internal class var: Is CONFIG_RT_GROUP_SCHED set? |
702 | 5a530af1 | Bob Lantz | |
703 | 820c3be7 | Bob Lantz | @classmethod
|
704 | def checkRtGroupSched( cls ): |
||
705 | "Check (Ubuntu,Debian) kernel config for CONFIG_RT_GROUP_SCHED for RT"
|
||
706 | if not cls._rtGroupSched: |
||
707 | release = quietRun( 'uname -r' ).strip('\r\n') |
||
708 | b1ec912d | Bob Lantz | output = quietRun( 'grep CONFIG_RT_GROUP_SCHED /boot/config-%s' %
|
709 | release ) |
||
710 | 820c3be7 | Bob Lantz | if output == '# CONFIG_RT_GROUP_SCHED is not set\n': |
711 | 4d55ef11 | Bob Lantz | error( '\n*** error: please enable RT_GROUP_SCHED '
|
712 | b1ec912d | Bob Lantz | 'in your kernel\n' )
|
713 | 820c3be7 | Bob Lantz | exit( 1 ) |
714 | cls._rtGroupSched = True
|
||
715 | |||
716 | 089e8130 | Bob Lantz | def chrt( self ): |
717 | 84a91a14 | Bob Lantz | "Set RT scheduling priority"
|
718 | 089e8130 | Bob Lantz | quietRun( 'chrt -p %s %s' % ( self.rtprio, self.pid ) ) |
719 | 84a91a14 | Bob Lantz | result = quietRun( 'chrt -p %s' % self.pid ) |
720 | firstline = result.split( '\n' )[ 0 ] |
||
721 | lastword = firstline.split( ' ' )[ -1 ] |
||
722 | 8a622c3a | Bob Lantz | if lastword != 'SCHED_RR': |
723 | error( '*** error: could not assign SCHED_RR to %s\n' % self.name ) |
||
724 | 84a91a14 | Bob Lantz | return lastword
|
725 | |||
726 | 8a622c3a | Bob Lantz | def rtInfo( self, f ): |
727 | "Internal method: return parameters for RT bandwidth"
|
||
728 | pstr, qstr = 'rt_period_us', 'rt_runtime_us' |
||
729 | # RT uses wall clock time for period and quota
|
||
730 | 04c1c098 | cody burkard | quota = int( self.period_us * f ) |
731 | 8a622c3a | Bob Lantz | return pstr, qstr, self.period_us, quota |
732 | |||
733 | 820c3be7 | Bob Lantz | def cfsInfo( self, f ): |
734 | 8a622c3a | Bob Lantz | "Internal method: return parameters for CFS bandwidth"
|
735 | pstr, qstr = 'cfs_period_us', 'cfs_quota_us' |
||
736 | # CFS uses wall clock time for period and CPU time for quota.
|
||
737 | quota = int( self.period_us * f * numCores() ) |
||
738 | period = self.period_us
|
||
739 | if f > 0 and quota < 1000: |
||
740 | debug( '(cfsInfo: increasing default period) ' )
|
||
741 | quota = 1000
|
||
742 | period = int( quota / f / numCores() )
|
||
743 | 820c3be7 | Bob Lantz | # Reset to unlimited on negative quota
|
744 | if quota < 0: |
||
745 | quota = -1
|
||
746 | 8a622c3a | Bob Lantz | return pstr, qstr, period, quota
|
747 | |||
748 | 216a4b7c | Bob Lantz | # BL comment:
|
749 | 14ff3ad3 | Bob Lantz | # This may not be the right API,
|
750 | 216a4b7c | Bob Lantz | # since it doesn't specify CPU bandwidth in "absolute"
|
751 | # units the way link bandwidth is specified.
|
||
752 | # We should use MIPS or SPECINT or something instead.
|
||
753 | # Alternatively, we should change from system fraction
|
||
754 | # to CPU seconds per second, essentially assuming that
|
||
755 | # all CPUs are the same.
|
||
756 | 8a622c3a | Bob Lantz | |
757 | 820c3be7 | Bob Lantz | def setCPUFrac( self, f, sched=None ): |
758 | 216a4b7c | Bob Lantz | """Set overall CPU fraction for this host
|
759 | 820c3be7 | Bob Lantz | f: CPU bandwidth limit (positive fraction, or -1 for cfs unlimited)
|
760 | 216a4b7c | Bob Lantz | sched: 'rt' or 'cfs'
|
761 | 5a530af1 | Bob Lantz | Note 'cfs' requires CONFIG_CFS_BANDWIDTH,
|
762 | 820c3be7 | Bob Lantz | and 'rt' requires CONFIG_RT_GROUP_SCHED"""
|
763 | 216a4b7c | Bob Lantz | if not sched: |
764 | sched = self.sched
|
||
765 | if sched == 'rt': |
||
766 | 820c3be7 | Bob Lantz | if not f or f < 0: |
767 | b1ec912d | Bob Lantz | raise Exception( 'Please set a positive CPU fraction' |
768 | ' for sched=rt\n' )
|
||
769 | 8a622c3a | Bob Lantz | pstr, qstr, period, quota = self.rtInfo( f )
|
770 | 216a4b7c | Bob Lantz | elif sched == 'cfs': |
771 | 8a622c3a | Bob Lantz | pstr, qstr, period, quota = self.cfsInfo( f )
|
772 | 216a4b7c | Bob Lantz | else:
|
773 | return
|
||
774 | # Set cgroup's period and quota
|
||
775 | 08d611f4 | Cody Burkard | setPeriod = self.cgroupSet( pstr, period )
|
776 | setQuota = self.cgroupSet( qstr, quota )
|
||
777 | 216a4b7c | Bob Lantz | if sched == 'rt': |
778 | # Set RT priority if necessary
|
||
779 | 08d611f4 | Cody Burkard | sched = self.chrt()
|
780 | info( '(%s %d/%dus) ' % ( sched, setQuota, setPeriod ) )
|
||
781 | 84a91a14 | Bob Lantz | |
782 | 669e420c | Bob Lantz | def setCPUs( self, cores, mems=0 ): |
783 | 197b083f | Bob Lantz | "Specify (real) cores that our cgroup can run on"
|
784 | 08d611f4 | Cody Burkard | if not cores: |
785 | return
|
||
786 | c273f490 | Bob Lantz | if isinstance( cores, list ): |
787 | 197b083f | Bob Lantz | cores = ','.join( [ str( c ) for c in cores ] ) |
788 | self.cgroupSet( resource='cpuset', param='cpus', |
||
789 | 669e420c | Bob Lantz | value=cores ) |
790 | 197b083f | Bob Lantz | # Memory placement is probably not relevant, but we
|
791 | # must specify it anyway
|
||
792 | self.cgroupSet( resource='cpuset', param='mems', |
||
793 | 669e420c | Bob Lantz | value=mems) |
794 | 197b083f | Bob Lantz | # We have to do this here after we've specified
|
795 | # cpus and mems
|
||
796 | errFail( 'cgclassify -g cpuset:/%s %s' % (
|
||
797 | c0095746 | Brandon Heller | self.name, self.pid ) ) |
798 | 197b083f | Bob Lantz | |
799 | 08d611f4 | Cody Burkard | def config( self, cpu=-1, cores=None, **params ): |
800 | 84a91a14 | Bob Lantz | """cpu: desired overall system CPU fraction
|
801 | 197b083f | Bob Lantz | cores: (real) core(s) this host can run on
|
802 | 84a91a14 | Bob Lantz | params: parameters for Node.config()"""
|
803 | r = Node.config( self, **params )
|
||
804 | 216a4b7c | Bob Lantz | # Was considering cpu={'cpu': cpu , 'sched': sched}, but
|
805 | # that seems redundant
|
||
806 | 84a91a14 | Bob Lantz | self.setParam( r, 'setCPUFrac', cpu=cpu ) |
807 | 197b083f | Bob Lantz | self.setParam( r, 'setCPUs', cores=cores ) |
808 | 84a91a14 | Bob Lantz | return r
|
809 | |||
810 | 197b083f | Bob Lantz | inited = False
|
811 | 89bf3103 | Brandon Heller | |
812 | 197b083f | Bob Lantz | @classmethod
|
813 | def init( cls ): |
||
814 | "Initialization for CPULimitedHost class"
|
||
815 | mountCgroups() |
||
816 | cls.inited = True
|
||
817 | |||
818 | |||
819 | 84a91a14 | Bob Lantz | # Some important things to note:
|
820 | #
|
||
821 | bf9c6ab7 | Bob Lantz | # The "IP" address which setIP() assigns to the switch is not
|
822 | 84a91a14 | Bob Lantz | # an "IP address for the switch" in the sense of IP routing.
|
823 | bf9c6ab7 | Bob Lantz | # Rather, it is the IP address for the control interface,
|
824 | # on the control network, and it is only relevant to the
|
||
825 | # controller. If you are running in the root namespace
|
||
826 | # (which is the only way to run OVS at the moment), the
|
||
827 | # control interface is the loopback interface, and you
|
||
828 | # normally never want to change its IP address!
|
||
829 | 84a91a14 | Bob Lantz | #
|
830 | # In general, you NEVER want to attempt to use Linux's
|
||
831 | # network stack (i.e. ifconfig) to "assign" an IP address or
|
||
832 | # MAC address to a switch data port. Instead, you "assign"
|
||
833 | # the IP and MAC addresses in the controller by specifying
|
||
834 | # packets that you want to receive or send. The "MAC" address
|
||
835 | # reported by ifconfig for a switch data port is essentially
|
||
836 | bf9c6ab7 | Bob Lantz | # meaningless. It is important to understand this if you
|
837 | # want to create a functional router using OpenFlow.
|
||
838 | 89bf3103 | Brandon Heller | |
839 | 80a8fa62 | Bob Lantz | class Switch( Node ): |
840 | 6d72a3cb | Bob Lantz | """A Switch is a Node that is running (or has execed?)
|
841 | 80a8fa62 | Bob Lantz | an OpenFlow switch."""
|
842 | 89bf3103 | Brandon Heller | |
843 | 8a622c3a | Bob Lantz | portBase = 1 # Switches start with port 1 in OpenFlow |
844 | 0d94548a | Bob Lantz | dpidLen = 16 # digits in dpid passed to switch |
845 | dd159b4a | Bob Lantz | |
846 | 84a91a14 | Bob Lantz | def __init__( self, name, dpid=None, opts='', listenPort=None, **params): |
847 | 0b5609f5 | Bob Lantz | """dpid: dpid hex string (or None to derive from name, e.g. s1 -> 1)
|
848 | 84a91a14 | Bob Lantz | opts: additional switch options
|
849 | listenPort: port to listen on for dpctl connections"""
|
||
850 | Node.__init__( self, name, **params )
|
||
851 | 0b5609f5 | Bob Lantz | self.dpid = self.defaultDpid( dpid ) |
852 | 121eb449 | Bob Lantz | self.opts = opts
|
853 | ccca871a | Brandon Heller | self.listenPort = listenPort
|
854 | 8856d284 | Bob Lantz | if not self.inNamespace: |
855 | 14c19260 | Bob Lantz | self.controlIntf = Intf( 'lo', self, port=0 ) |
856 | 84a91a14 | Bob Lantz | |
857 | 0b5609f5 | Bob Lantz | def defaultDpid( self, dpid=None ): |
858 | "Return correctly formatted dpid from dpid or switch name (s1 -> 1)"
|
||
859 | if dpid:
|
||
860 | # Remove any colons and make sure it's a good hex number
|
||
861 | dpid = dpid.translate( None, ':' ) |
||
862 | assert len( dpid ) <= self.dpidLen and int( dpid, 16 ) >= 0 |
||
863 | else:
|
||
864 | # Use hex of the first number in the switch name
|
||
865 | nums = re.findall( r'\d+', self.name ) |
||
866 | if nums:
|
||
867 | dpid = hex( int( nums[ 0 ] ) )[ 2: ] |
||
868 | else:
|
||
869 | raise Exception( 'Unable to derive default datapath ID - ' |
||
870 | 'please either specify a dpid or use a '
|
||
871 | 'canonical switch name such as s23.' )
|
||
872 | 74c71bc8 | Bob Lantz | return '0' * ( self.dpidLen - len( dpid ) ) + dpid |
873 | 121eb449 | Bob Lantz | |
874 | 9802686b | Bob Lantz | def defaultIntf( self ): |
875 | 8856d284 | Bob Lantz | "Return control interface"
|
876 | if self.controlIntf: |
||
877 | return self.controlIntf |
||
878 | else:
|
||
879 | return Node.defaultIntf( self ) |
||
880 | 121eb449 | Bob Lantz | |
881 | def sendCmd( self, *cmd, **kwargs ): |
||
882 | 80a8fa62 | Bob Lantz | """Send command to Node.
|
883 | cmd: string"""
|
||
884 | 121eb449 | Bob Lantz | kwargs.setdefault( 'printPid', False ) |
885 | 1bb4412f | Brandon Heller | if not self.execed: |
886 | 121eb449 | Bob Lantz | return Node.sendCmd( self, *cmd, **kwargs ) |
887 | 1bb4412f | Brandon Heller | else:
|
888 | 6d72a3cb | Bob Lantz | error( '*** Error: %s has execed and cannot accept commands' %
|
889 | 2e089b5e | Brandon Heller | self.name )
|
890 | 89bf3103 | Brandon Heller | |
891 | 538a856c | Bob Lantz | def connected( self ): |
892 | "Is the switch connected to a controller? (override this method)"
|
||
893 | 554abdd5 | Bob Lantz | # Assume that we are connected by default to whatever we need to
|
894 | # be connected to. This should be overridden by any OpenFlow
|
||
895 | # switch, but not by a standalone bridge.
|
||
896 | debug( 'Assuming', repr( self ), 'is connected to a controller\n' ) |
||
897 | 18aab5b7 | Bob Lantz | return True |
898 | 538a856c | Bob Lantz | |
899 | 8856d284 | Bob Lantz | def __repr__( self ): |
900 | "More informative string representation"
|
||
901 | intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() ) |
||
902 | edf60032 | Brandon Heller | for i in self.intfList() ] ) ) |
903 | 8856d284 | Bob Lantz | return '<%s %s: %s pid=%s> ' % ( |
904 | c0095746 | Brandon Heller | self.__class__.__name__, self.name, intfs, self.pid ) |
905 | 89bf3103 | Brandon Heller | |
906 | b1ec912d | Bob Lantz | |
907 | 80a8fa62 | Bob Lantz | class UserSwitch( Switch ): |
908 | d44a5843 | Bob Lantz | "User-space switch."
|
909 | 89bf3103 | Brandon Heller | |
910 | 0d94548a | Bob Lantz | dpidLen = 12
|
911 | |||
912 | aa554d98 | Bob Lantz | def __init__( self, name, dpopts='--no-slicing', **kwargs ): |
913 | 80a8fa62 | Bob Lantz | """Init.
|
914 | aa554d98 | Bob Lantz | name: name for the switch
|
915 | dpopts: additional arguments to ofdatapath (--no-slicing)"""
|
||
916 | d44a5843 | Bob Lantz | Switch.__init__( self, name, **kwargs )
|
917 | 57fd19ef | Bob Lantz | pathCheck( 'ofdatapath', 'ofprotocol', |
918 | edf60032 | Brandon Heller | moduleName='the OpenFlow reference user switch' +
|
919 | '(openflow.org)' )
|
||
920 | 8856d284 | Bob Lantz | if self.listenPort: |
921 | self.opts += ' --listen=ptcp:%i ' % self.listenPort |
||
922 | 191df1cb | Brian O'Connor | else:
|
923 | self.opts += ' --listen=punix:/tmp/%s.listen' % self.name |
||
924 | 804c4bbf | Bob Lantz | self.dpopts = dpopts
|
925 | 89bf3103 | Brandon Heller | |
926 | 14ff3ad3 | Bob Lantz | @classmethod
|
927 | def setup( cls ): |
||
928 | b055728f | Brandon Heller | "Ensure any dependencies are loaded; if not, try to load them."
|
929 | e900a16c | Bob Lantz | if not os.path.exists( '/dev/net/tun' ): |
930 | moduleDeps( add=TUN ) |
||
931 | b055728f | Brandon Heller | |
932 | 8856d284 | Bob Lantz | def dpctl( self, *args ): |
933 | "Run dpctl command"
|
||
934 | 0dd96ebc | Andrew Ferguson | listenAddr = None
|
935 | 8856d284 | Bob Lantz | if not self.listenPort: |
936 | 191df1cb | Brian O'Connor | listenAddr = 'unix:/tmp/%s.listen' % self.name |
937 | 0dd96ebc | Andrew Ferguson | else:
|
938 | listenAddr = 'tcp:127.0.0.1:%i' % self.listenPort |
||
939 | 14c19260 | Bob Lantz | return self.cmd( 'dpctl ' + ' '.join( args ) + |
940 | 0dd96ebc | Andrew Ferguson | ' ' + listenAddr )
|
941 | 8856d284 | Bob Lantz | |
942 | 538a856c | Bob Lantz | def connected( self ): |
943 | "Is the switch connected to a controller?"
|
||
944 | c75ff7ec | cody burkard | status = self.dpctl( 'status' ) |
945 | return ( 'remote.is-connected=true' in status and |
||
946 | 'local.is-connected=true' in status ) |
||
947 | 538a856c | Bob Lantz | |
948 | 3236d33b | Andrew Ferguson | @staticmethod
|
949 | def TCReapply( intf ): |
||
950 | """Unfortunately user switch and Mininet are fighting
|
||
951 | over tc queuing disciplines. To resolve the conflict,
|
||
952 | we re-create the user switch's configuration, but as a
|
||
953 | leaf of the TCIntf-created configuration."""
|
||
954 | c273f490 | Bob Lantz | if isinstance( intf, TCIntf ): |
955 | 7a3159c9 | Bob Lantz | ifspeed = 10000000000 # 10 Gbps |
956 | 3236d33b | Andrew Ferguson | minspeed = ifspeed * 0.001
|
957 | |||
958 | res = intf.config( **intf.params ) |
||
959 | 28454708 | Andrew Ferguson | |
960 | 7a3159c9 | Bob Lantz | if res is None: # link may not have TC parameters |
961 | 28454708 | Andrew Ferguson | return
|
962 | 3236d33b | Andrew Ferguson | |
963 | # Re-add qdisc, root, and default classes user switch created, but
|
||
964 | # with new parent, as setup by Mininet's TCIntf
|
||
965 | 28454708 | Andrew Ferguson | parent = res['parent']
|
966 | 3236d33b | Andrew Ferguson | intf.tc( "%s qdisc add dev %s " + parent +
|
967 | " handle 1: htb default 0xfffe" )
|
||
968 | intf.tc( "%s class add dev %s classid 1:0xffff parent 1: htb rate "
|
||
969 | + str(ifspeed) )
|
||
970 | intf.tc( "%s class add dev %s classid 1:0xfffe parent 1:0xffff " +
|
||
971 | "htb rate " + str(minspeed) + " ceil " + str(ifspeed) ) |
||
972 | |||
973 | 80a8fa62 | Bob Lantz | def start( self, controllers ): |
974 | """Start OpenFlow reference user datapath.
|
||
975 | 6d72a3cb | Bob Lantz | Log to /tmp/sN-{ofd,ofp}.log.
|
976 | efc9a01c | Bob Lantz | controllers: list of controller objects"""
|
977 | b69ef234 | Bob Lantz | # Add controllers
|
978 | clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port ) |
||
979 | edf60032 | Brandon Heller | for c in controllers ] ) |
980 | 89bf3103 | Brandon Heller | ofdlog = '/tmp/' + self.name + '-ofd.log' |
981 | ofplog = '/tmp/' + self.name + '-ofp.log' |
||
982 | 14c19260 | Bob Lantz | intfs = [ str( i ) for i in self.intfList() if not i.IP() ] |
983 | efc9a01c | Bob Lantz | self.cmd( 'ofdatapath -i ' + ','.join( intfs ) + |
984 | 804c4bbf | Bob Lantz | ' punix:/tmp/' + self.name + ' -d %s ' % self.dpid + |
985 | self.dpopts +
|
||
986 | edf60032 | Brandon Heller | ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' ) |
987 | efc9a01c | Bob Lantz | self.cmd( 'ofprotocol unix:/tmp/' + self.name + |
988 | edf60032 | Brandon Heller | ' ' + clist +
|
989 | ' --fail=closed ' + self.opts + |
||
990 | ' 1> ' + ofplog + ' 2>' + ofplog + ' &' ) |
||
991 | be13072f | Brian O'Connor | if "no-slicing" not in self.dpopts: |
992 | # Only TCReapply if slicing is enable
|
||
993 | 7a3159c9 | Bob Lantz | sleep(1) # Allow ofdatapath to start before re-arranging qdisc's |
994 | be13072f | Brian O'Connor | for intf in self.intfList(): |
995 | if not intf.IP(): |
||
996 | self.TCReapply( intf )
|
||
997 | 89bf3103 | Brandon Heller | |
998 | b57e5d93 | Brian O'Connor | def stop( self, deleteIntfs=True ): |
999 | b1ec912d | Bob Lantz | """Stop OpenFlow reference user datapath.
|
1000 | deleteIntfs: delete interfaces? (True)"""
|
||
1001 | 80a8fa62 | Bob Lantz | self.cmd( 'kill %ofdatapath' ) |
1002 | self.cmd( 'kill %ofprotocol' ) |
||
1003 | d754a7ce | Bob Lantz | super( UserSwitch, self ).stop( deleteIntfs ) |
1004 | 82b72072 | Bob Lantz | |
1005 | 8a7d42db | Bob Lantz | class OVSLegacyKernelSwitch( Switch ): |
1006 | """Open VSwitch legacy kernel-space switch using ovs-openflowd.
|
||
1007 | 80a8fa62 | Bob Lantz | Currently only works in the root namespace."""
|
1008 | f7c2df25 | Brandon Heller | |
1009 | 086ef80e | Bob Lantz | def __init__( self, name, dp=None, **kwargs ): |
1010 | 80a8fa62 | Bob Lantz | """Init.
|
1011 | e3a2ef01 | Bob Lantz | name: name for switch
|
1012 | 6d72a3cb | Bob Lantz | dp: netlink id (0, 1, 2, ...)
|
1013 | d44a5843 | Bob Lantz | defaultMAC: default MAC as unsigned int; random value if None"""
|
1014 | Switch.__init__( self, name, **kwargs )
|
||
1015 | 612b21cb | Bob Lantz | self.dp = dp if dp else self.name |
1016 | 121eb449 | Bob Lantz | self.intf = self.dp |
1017 | d44a5843 | Bob Lantz | if self.inNamespace: |
1018 | error( "OVSKernelSwitch currently only works"
|
||
1019 | edf60032 | Brandon Heller | " in the root namespace.\n" )
|
1020 | d44a5843 | Bob Lantz | exit( 1 ) |
1021 | f7c2df25 | Brandon Heller | |
1022 | 14ff3ad3 | Bob Lantz | @classmethod
|
1023 | def setup( cls ): |
||
1024 | b055728f | Brandon Heller | "Ensure any dependencies are loaded; if not, try to load them."
|
1025 | f9654e56 | Bob Lantz | pathCheck( 'ovs-dpctl', 'ovs-openflowd', |
1026 | edf60032 | Brandon Heller | moduleName='Open vSwitch (openvswitch.org)')
|
1027 | f9654e56 | Bob Lantz | moduleDeps( subtract=OF_KMOD, add=OVS_KMOD ) |
1028 | b055728f | Brandon Heller | |
1029 | 80a8fa62 | Bob Lantz | def start( self, controllers ): |
1030 | "Start up kernel datapath."
|
||
1031 | f7c2df25 | Brandon Heller | ofplog = '/tmp/' + self.name + '-ofp.log' |
1032 | # Delete local datapath if it exists;
|
||
1033 | # then create a new one monitoring the given interfaces
|
||
1034 | 8856d284 | Bob Lantz | self.cmd( 'ovs-dpctl del-dp ' + self.dp ) |
1035 | 121eb449 | Bob Lantz | self.cmd( 'ovs-dpctl add-dp ' + self.dp ) |
1036 | 14c19260 | Bob Lantz | intfs = [ str( i ) for i in self.intfList() if not i.IP() ] |
1037 | 5cc80828 | Bob Lantz | self.cmd( 'ovs-dpctl', 'add-if', self.dp, ' '.join( intfs ) ) |
1038 | f7c2df25 | Brandon Heller | # Run protocol daemon
|
1039 | b69ef234 | Bob Lantz | clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port ) |
1040 | edf60032 | Brandon Heller | for c in controllers ] ) |
1041 | 121eb449 | Bob Lantz | self.cmd( 'ovs-openflowd ' + self.dp + |
1042 | edf60032 | Brandon Heller | ' ' + clist +
|
1043 | ' --fail=secure ' + self.opts + |
||
1044 | ' --datapath-id=' + self.dpid + |
||
1045 | ' 1>' + ofplog + ' 2>' + ofplog + '&' ) |
||
1046 | f7c2df25 | Brandon Heller | self.execed = False |
1047 | |||
1048 | b57e5d93 | Brian O'Connor | def stop( self, deleteIntfs=True ): |
1049 | b1ec912d | Bob Lantz | """Terminate kernel datapath."
|
1050 | deleteIntfs: delete interfaces? (True)"""
|
||
1051 | 121eb449 | Bob Lantz | quietRun( 'ovs-dpctl del-dp ' + self.dp ) |
1052 | 75d72d96 | Bob Lantz | self.cmd( 'kill %ovs-openflowd' ) |
1053 | d754a7ce | Bob Lantz | super( OVSLegacyKernelSwitch, self ).stop( deleteIntfs ) |
1054 | f7c2df25 | Brandon Heller | |
1055 | |||
1056 | 8a7d42db | Bob Lantz | class OVSSwitch( Switch ): |
1057 | "Open vSwitch switch. Depends on ovs-vsctl."
|
||
1058 | |||
1059 | 3e2eb713 | Bob Lantz | def __init__( self, name, failMode='secure', datapath='kernel', |
1060 | d4be9271 | Bob Lantz | inband=False, protocols=None, |
1061 | a8cc243a | Bob Lantz | reconnectms=1000, stp=False, **params ): |
1062 | d4be9271 | Bob Lantz | """name: name for switch
|
1063 | 153d598d | Murphy McCauley | failMode: controller loss behavior (secure|open)
|
1064 | 3e2eb713 | Bob Lantz | datapath: userspace or kernel mode (kernel|user)
|
1065 | 171e8151 | Bob Lantz | inband: use in-band control (False)
|
1066 | 127f35a9 | Bob Lantz | protocols: use specific OpenFlow version(s) (e.g. OpenFlow13)
|
1067 | d4be9271 | Bob Lantz | Unspecified (or old OVS version) uses OVS default
|
1068 | a8cc243a | Bob Lantz | reconnectms: max reconnect timeout in ms (0/None for default)
|
1069 | stp: enable STP (False, requires failMode=standalone)"""
|
||
1070 | 84a91a14 | Bob Lantz | Switch.__init__( self, name, **params )
|
1071 | 92b601ab | Bob Lantz | self.failMode = failMode
|
1072 | 153d598d | Murphy McCauley | self.datapath = datapath
|
1073 | 3e2eb713 | Bob Lantz | self.inband = inband
|
1074 | 84ce84f5 | Gregory Gee | self.protocols = protocols
|
1075 | d4be9271 | Bob Lantz | self.reconnectms = reconnectms
|
1076 | a8cc243a | Bob Lantz | self.stp = stp
|
1077 | 05dbf82e | Bob Lantz | self._uuids = [] # controller UUIDs |
1078 | 14ff3ad3 | Bob Lantz | |
1079 | @classmethod
|
||
1080 | def setup( cls ): |
||
1081 | 8a7d42db | Bob Lantz | "Make sure Open vSwitch is installed and working"
|
1082 | 14ff3ad3 | Bob Lantz | pathCheck( 'ovs-vsctl',
|
1083 | edf60032 | Brandon Heller | moduleName='Open vSwitch (openvswitch.org)')
|
1084 | 28c2cdc2 | Bob Lantz | # This should no longer be needed, and it breaks
|
1085 | # with OVS 1.7 which has renamed the kernel module:
|
||
1086 | # moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
|
||
1087 | 8a7d42db | Bob Lantz | out, err, exitcode = errRun( 'ovs-vsctl -t 1 show' )
|
1088 | if exitcode:
|
||
1089 | 14ff3ad3 | Bob Lantz | error( out + err + |
1090 | 8a7d42db | Bob Lantz | 'ovs-vsctl exited with code %d\n' % exitcode +
|
1091 | '*** Error connecting to ovs-db with ovs-vsctl\n'
|
||
1092 | 'Make sure that Open vSwitch is installed, '
|
||
1093 | 'that ovsdb-server is running, and that\n'
|
||
1094 | '"ovs-vsctl show" works correctly.\n'
|
||
1095 | 14ff3ad3 | Bob Lantz | 'You may wish to try '
|
1096 | '"service openvswitch-switch start".\n' )
|
||
1097 | 8a7d42db | Bob Lantz | exit( 1 ) |
1098 | b1ec912d | Bob Lantz | version = quietRun( 'ovs-vsctl --version' )
|
1099 | 7a3159c9 | Bob Lantz | cls.OVSVersion = findall( r'\d+\.\d+', version )[ 0 ] |
1100 | f1e42ba5 | Cody | |
1101 | @classmethod
|
||
1102 | def isOldOVS( cls ): |
||
1103 | b1ec912d | Bob Lantz | "Is OVS ersion < 1.10?"
|
1104 | f1e42ba5 | Cody | return ( StrictVersion( cls.OVSVersion ) <
|
1105 | 7a3159c9 | Bob Lantz | StrictVersion( '1.10' ) )
|
1106 | 8a7d42db | Bob Lantz | |
1107 | 06115a04 | Bob Lantz | @classmethod
|
1108 | def batchShutdown( cls, switches ): |
||
1109 | 9bda9848 | Bob Lantz | "Shut down a list of OVS switches"
|
1110 | 957fe1db | Bob Lantz | delcmd = 'del-br %s'
|
1111 | if not cls.isOldOVS(): |
||
1112 | delcmd = '--if-exists ' + delcmd
|
||
1113 | 9bda9848 | Bob Lantz | # First, delete them all from ovsdb
|
1114 | 06115a04 | Bob Lantz | quietRun( 'ovs-vsctl ' +
|
1115 | 957fe1db | Bob Lantz | ' -- '.join( delcmd % s for s in switches ) ) |
1116 | 9bda9848 | Bob Lantz | # Next, shut down all of the processes
|
1117 | pids = ' '.join( str( switch.pid ) for switch in switches ) |
||
1118 | quietRun( 'kill -HUP ' + pids )
|
||
1119 | for switch in switches: |
||
1120 | switch.shell = None
|
||
1121 | c1b48fb5 | Bob Lantz | return True |
1122 | 06115a04 | Bob Lantz | |
1123 | 8856d284 | Bob Lantz | def dpctl( self, *args ): |
1124 | 229f112f | Bob Lantz | "Run ovs-ofctl command"
|
1125 | return self.cmd( 'ovs-ofctl', args[ 0 ], self, *args[ 1: ] ) |
||
1126 | 8856d284 | Bob Lantz | |
1127 | 9bda9848 | Bob Lantz | def vsctl( self, *args, **kwargs ): |
1128 | "Run ovs-vsctl command"
|
||
1129 | return self.cmd( 'ovs-vsctl', *args, **kwargs ) |
||
1130 | |||
1131 | 1aec55d9 | Bob Lantz | @staticmethod
|
1132 | def TCReapply( intf ): |
||
1133 | """Unfortunately OVS and Mininet are fighting
|
||
1134 | over tc queuing disciplines. As a quick hack/
|
||
1135 | workaround, we clear OVS's and reapply our own."""
|
||
1136 | c273f490 | Bob Lantz | if isinstance( intf, TCIntf ): |
1137 | 1aec55d9 | Bob Lantz | intf.config( **intf.params ) |
1138 | |||
1139 | 8856d284 | Bob Lantz | def attach( self, intf ): |
1140 | "Connect a data port"
|
||
1141 | 9bda9848 | Bob Lantz | self.vsctl( 'add-port', self, intf ) |
1142 | 8856d284 | Bob Lantz | self.cmd( 'ifconfig', intf, 'up' ) |
1143 | 1aec55d9 | Bob Lantz | self.TCReapply( intf )
|
1144 | 8856d284 | Bob Lantz | |
1145 | def detach( self, intf ): |
||
1146 | "Disconnect a data port"
|
||
1147 | 9bda9848 | Bob Lantz | self.vsctl( 'del-port', self, intf ) |
1148 | 8856d284 | Bob Lantz | |
1149 | 05dbf82e | Bob Lantz | def controllerUUIDs( self, update=False ): |
1150 | """Return ovsdb UUIDs for our controllers
|
||
1151 | update: update cached value"""
|
||
1152 | if not self._uuids or update: |
||
1153 | bec34e72 | Bob Lantz | controllers = self.cmd( 'ovs-vsctl -- get Bridge', self, |
1154 | 3ac5cafe | Bob Lantz | 'Controller' ).strip()
|
1155 | 05dbf82e | Bob Lantz | if controllers.startswith( '[' ) and controllers.endswith( ']' ): |
1156 | controllers = controllers[ 1 : -1 ] |
||
1157 | if controllers:
|
||
1158 | 3ac5cafe | Bob Lantz | self._uuids = [ c.strip()
|
1159 | for c in controllers.split( ',' ) ] |
||
1160 | 05dbf82e | Bob Lantz | return self._uuids |
1161 | 538a856c | Bob Lantz | |
1162 | def connected( self ): |
||
1163 | "Are we connected to at least one of our controllers?"
|
||
1164 | 05dbf82e | Bob Lantz | for uuid in self.controllerUUIDs(): |
1165 | 9bda9848 | Bob Lantz | if 'true' in self.vsctl( '-- get Controller', |
1166 | uuid, 'is_connected' ):
|
||
1167 | 05dbf82e | Bob Lantz | return True |
1168 | return self.failMode == 'standalone' |
||
1169 | 538a856c | Bob Lantz | |
1170 | 957fe1db | Bob Lantz | def intfOpts( self, intf ): |
1171 | "Return OVS interface options for intf"
|
||
1172 | opts = ''
|
||
1173 | if not self.isOldOVS(): |
||
1174 | bec34e72 | Bob Lantz | # ofport_request is not supported on old OVS
|
1175 | 957fe1db | Bob Lantz | opts += ' ofport_request=%s' % self.ports[ intf ] |
1176 | # Patch ports don't work well with old OVS
|
||
1177 | if isinstance( intf, OVSIntf ): |
||
1178 | intf1, intf2 = intf.link.intf1, intf.link.intf2 |
||
1179 | peer = intf1 if intf1 != intf else intf2 |
||
1180 | opts += ' type=patch options:peer=%s' % peer
|
||
1181 | return '' if not opts else ' -- set Interface %s' % intf + opts |
||
1182 | |||
1183 | def bridgeOpts( self ): |
||
1184 | "Return OVS bridge options"
|
||
1185 | bec34e72 | Bob Lantz | opts = ( ' other_config:datapath-id=%s' % self.dpid + |
1186 | ' fail_mode=%s' % self.failMode ) |
||
1187 | 957fe1db | Bob Lantz | if not self.inband: |
1188 | opts += ' other-config:disable-in-band=true'
|
||
1189 | if self.datapath == 'user': |
||
1190 | opts += ' datapath_type=netdev' % self |
||
1191 | if self.protocols and not self.isOldOVS(): |
||
1192 | opts += ' protocols=%s' % ( self, self.protocols ) |
||
1193 | if self.stp and self.failMode == 'standalone': |
||
1194 | opts += ' stp_enable=true' % self |
||
1195 | return opts
|
||
1196 | c069542c | Bob Lantz | |
1197 | 8a7d42db | Bob Lantz | def start( self, controllers ): |
1198 | "Start up a new OVS OpenFlow switch using ovs-vsctl"
|
||
1199 | 14ff3ad3 | Bob Lantz | if self.inNamespace: |
1200 | 8856d284 | Bob Lantz | raise Exception( |
1201 | 14ff3ad3 | Bob Lantz | 'OVS kernel switch does not work in a namespace' )
|
1202 | 7a3159c9 | Bob Lantz | int( self.dpid, 16 ) # DPID must be a hex string |
1203 | bec34e72 | Bob Lantz | # Command to add interfaces
|
1204 | 957fe1db | Bob Lantz | intfs = ''.join( ' -- add-port %s %s' % ( self, intf ) + |
1205 | self.intfOpts( intf )
|
||
1206 | for intf in self.intfList() |
||
1207 | if self.ports[ intf ] and not intf.IP() ) |
||
1208 | bec34e72 | Bob Lantz | # Command to create controller entries
|
1209 | 3b4738c2 | Bob Lantz | clist = [ ( self.name + c.name, '%s:%s:%d' % |
1210 | ( c.protocol, c.IP(), c.port ) ) |
||
1211 | for c in controllers ] |
||
1212 | if self.listenPort: |
||
1213 | clist.append( ( self.name + '-listen', |
||
1214 | 'ptcp:%s' % self.listenPort ) ) |
||
1215 | 957fe1db | Bob Lantz | ccmd = '-- --id=@%s create Controller target=\\"%s\\"'
|
1216 | if self.reconnectms: |
||
1217 | ccmd += ' max_backoff=%d' % self.reconnectms |
||
1218 | cargs = ' '.join( ccmd % ( name, target )
|
||
1219 | for name, target in clist ) |
||
1220 | bec34e72 | Bob Lantz | # Controller ID list
|
1221 | 957fe1db | Bob Lantz | cids = ','.join( '@%s' % name for name, _target in clist ) |
1222 | bec34e72 | Bob Lantz | # Try to delete any existing bridges with the same name
|
1223 | ba43451b | Cody | if not self.isOldOVS(): |
1224 | 957fe1db | Bob Lantz | cargs += ' -- --if-exists del-br %s' % self |
1225 | bec34e72 | Bob Lantz | # One ovs-vsctl command to rule them all!
|
1226 | self.vsctl( cargs +
|
||
1227 | ' -- add-br %s' % self + |
||
1228 | ' -- set bridge %s controller=[%s]' % ( self, cids ) + |
||
1229 | self.bridgeOpts() +
|
||
1230 | intfs ) |
||
1231 | # XXX BROKEN - need to fix this!!
|
||
1232 | 05dbf82e | Bob Lantz | # If necessary, restore TC config overwritten by OVS
|
1233 | bec34e72 | Bob Lantz | # for intf in self.intfList():
|
1234 | # self.TCReapply( intf )
|
||
1235 | 2e19ceb0 | Bob Lantz | |
1236 | b57e5d93 | Brian O'Connor | def stop( self, deleteIntfs=True ): |
1237 | b1ec912d | Bob Lantz | """Terminate OVS switch.
|
1238 | deleteIntfs: delete interfaces? (True)"""
|
||
1239 | 8856d284 | Bob Lantz | self.cmd( 'ovs-vsctl del-br', self ) |
1240 | 765d126e | Bob Lantz | if self.datapath == 'user': |
1241 | self.cmd( 'ip link del', self ) |
||
1242 | d754a7ce | Bob Lantz | super( OVSSwitch, self ).stop( deleteIntfs ) |
1243 | 8a7d42db | Bob Lantz | |
1244 | 1b69ea13 | Bob Lantz | |
1245 | 8a7d42db | Bob Lantz | OVSKernelSwitch = OVSSwitch |
1246 | |||
1247 | 84a91a14 | Bob Lantz | |
1248 | 1b69ea13 | Bob Lantz | class OVSBridge( OVSSwitch ): |
1249 | "OVSBridge is an OVSSwitch in standalone/bridge mode"
|
||
1250 | 5a530af1 | Bob Lantz | |
1251 | 1b69ea13 | Bob Lantz | def __init__( self, args, **kwargs ): |
1252 | kwargs.update( failMode='standalone' )
|
||
1253 | OVSSwitch.__init__( self, args, **kwargs )
|
||
1254 | 5a530af1 | Bob Lantz | |
1255 | 1b69ea13 | Bob Lantz | def start( self, controllers ): |
1256 | OVSSwitch.start( self, controllers=[] )
|
||
1257 | |||
1258 | a8cc243a | Bob Lantz | def connected( self ): |
1259 | "Are we forwarding yet?"
|
||
1260 | if self.stp: |
||
1261 | status = self.dpctl( 'show' ) |
||
1262 | return 'STP_FORWARD' in status and not 'STP_LEARN' in status |
||
1263 | else:
|
||
1264 | return True |
||
1265 | |||
1266 | 1b69ea13 | Bob Lantz | |
1267 | 9bda9848 | Bob Lantz | class OVSBatch( OVSSwitch ): |
1268 | "Experiment: batch startup of OVS switches"
|
||
1269 | |||
1270 | 957fe1db | Bob Lantz | # This should be ~ int( quietRun( 'getconf ARG_MAX' ) ),
|
1271 | # but the real limit seems to be much lower
|
||
1272 | argmax = 128000
|
||
1273 | |||
1274 | 9bda9848 | Bob Lantz | def __init__( self, *args, **kwargs ): |
1275 | self.commands = []
|
||
1276 | self.started = False |
||
1277 | super( OVSBatch, self ).__init__( *args, **kwargs ) |
||
1278 | |||
1279 | @classmethod
|
||
1280 | def batchStartup( cls, switches ): |
||
1281 | "Batch startup for OVS"
|
||
1282 | info( '...' )
|
||
1283 | 7485b035 | Bob Lantz | cmds = 'ovs-vsctl'
|
1284 | 9bda9848 | Bob Lantz | for switch in switches: |
1285 | 957fe1db | Bob Lantz | if cls.isOldOVS():
|
1286 | quietRun( 'ovs-vsctl del-br %s' % switch )
|
||
1287 | 9bda9848 | Bob Lantz | for cmd in switch.commands: |
1288 | 957fe1db | Bob Lantz | cmd = cmd.strip() |
1289 | # Don't exceed ARG_MAX
|
||
1290 | if len( cmds ) + len( cmd ) >= cls.argmax: |
||
1291 | errRun( cmds, shell=True )
|
||
1292 | cmds = 'ovs-vsctl'
|
||
1293 | cmds += ' ' + cmd
|
||
1294 | 9bda9848 | Bob Lantz | switch.started = True
|
1295 | if cmds:
|
||
1296 | 957fe1db | Bob Lantz | errRun( cmds, shell=True )
|
1297 | 9bda9848 | Bob Lantz | return True |
1298 | |||
1299 | def vsctl( self, *args, **kwargs ): |
||
1300 | "Append ovs-vsctl command to list for later execution"
|
||
1301 | if self.started: |
||
1302 | 8014a702 | Bob Lantz | return super( OVSBatch, self).vsctl( *args, **kwargs ) |
1303 | 957fe1db | Bob Lantz | cmd = ' '.join( str( arg ).strip() for arg in args ) |
1304 | 9bda9848 | Bob Lantz | self.commands.append( cmd )
|
1305 | |||
1306 | 9ca63226 | Bob Lantz | def start( self, *args, **kwargs ): |
1307 | super( OVSBatch, self ).start( *args, **kwargs ) |
||
1308 | self.started = True |
||
1309 | |||
1310 | def stop( self, *args, **kwargs ): |
||
1311 | super( OVSBatch, self ).stop( *args, **kwargs ) |
||
1312 | self.started = False |
||
1313 | |||
1314 | 9bda9848 | Bob Lantz | def cleanup( self): |
1315 | "Don't bother to clean up"
|
||
1316 | return
|
||
1317 | |||
1318 | 957fe1db | Bob Lantz | |
1319 | b57e5d93 | Brian O'Connor | class IVSSwitch( Switch ): |
1320 | 554abdd5 | Bob Lantz | "Indigo Virtual Switch"
|
1321 | 27da832d | Rich Lane | |
1322 | f5164f86 | Rich Lane | def __init__( self, name, verbose=False, **kwargs ): |
1323 | 27da832d | Rich Lane | Switch.__init__( self, name, **kwargs )
|
1324 | 163a66c6 | Rich Lane | self.verbose = verbose
|
1325 | 27da832d | Rich Lane | |
1326 | @classmethod
|
||
1327 | def setup( cls ): |
||
1328 | "Make sure IVS is installed"
|
||
1329 | pathCheck( 'ivs-ctl', 'ivs', |
||
1330 | moduleName="Indigo Virtual Switch (projectfloodlight.org)" )
|
||
1331 | out, err, exitcode = errRun( 'ivs-ctl show' )
|
||
1332 | if exitcode:
|
||
1333 | error( out + err + |
||
1334 | 'ivs-ctl exited with code %d\n' % exitcode +
|
||
1335 | '*** The openvswitch kernel module might '
|
||
1336 | 'not be loaded. Try modprobe openvswitch.\n' )
|
||
1337 | exit( 1 ) |
||
1338 | |||
1339 | 93cd5583 | Rich Lane | @classmethod
|
1340 | def batchShutdown( cls, switches ): |
||
1341 | "Kill each IVS switch, to be waited on later in stop()"
|
||
1342 | for switch in switches: |
||
1343 | 876e66e5 | Rich Lane | switch.cmd( 'kill %ivs' )
|
1344 | 93cd5583 | Rich Lane | |
1345 | 27da832d | Rich Lane | def start( self, controllers ): |
1346 | "Start up a new IVS switch"
|
||
1347 | args = ['ivs']
|
||
1348 | args.extend( ['--name', self.name] ) |
||
1349 | args.extend( ['--dpid', self.dpid] ) |
||
1350 | 163a66c6 | Rich Lane | if self.verbose: |
1351 | args.extend( ['--verbose'] )
|
||
1352 | 27da832d | Rich Lane | for intf in self.intfs.values(): |
1353 | if not intf.IP(): |
||
1354 | args.extend( ['-i', intf.name] )
|
||
1355 | for c in controllers: |
||
1356 | args.extend( ['-c', '%s:%d' % (c.IP(), c.port)] ) |
||
1357 | 91261b27 | Rich Lane | if self.listenPort: |
1358 | args.extend( ['--listen', '127.0.0.1:%i' % self.listenPort] ) |
||
1359 | d4fabc04 | Rich Lane | args.append( self.opts )
|
1360 | 27da832d | Rich Lane | |
1361 | 0a543602 | Rich Lane | logfile = '/tmp/ivs.%s.log' % self.name |
1362 | |||
1363 | self.cmd( ' '.join(args) + ' >' + logfile + ' 2>&1 </dev/null &' ) |
||
1364 | 27da832d | Rich Lane | |
1365 | b57e5d93 | Brian O'Connor | def stop( self, deleteIntfs=True ): |
1366 | b1ec912d | Bob Lantz | """Terminate IVS switch.
|
1367 | deleteIntfs: delete interfaces? (True)"""
|
||
1368 | 0a543602 | Rich Lane | self.cmd( 'kill %ivs' ) |
1369 | a7eb5576 | Rich Lane | self.cmd( 'wait' ) |
1370 | d754a7ce | Bob Lantz | super( IVSSwitch, self ).stop( deleteIntfs ) |
1371 | 27da832d | Rich Lane | |
1372 | def attach( self, intf ): |
||
1373 | "Connect a data port"
|
||
1374 | self.cmd( 'ivs-ctl', 'add-port', '--datapath', self.name, intf ) |
||
1375 | |||
1376 | def detach( self, intf ): |
||
1377 | "Disconnect a data port"
|
||
1378 | self.cmd( 'ivs-ctl', 'del-port', '--datapath', self.name, intf ) |
||
1379 | |||
1380 | def dpctl( self, *args ): |
||
1381 | "Run dpctl command"
|
||
1382 | 91261b27 | Rich Lane | if not self.listenPort: |
1383 | return "can't run dpctl without passive listening port" |
||
1384 | 803c0a6e | Rich Lane | return self.cmd( 'ovs-ofctl ' + ' '.join( args ) + |
1385 | 91261b27 | Rich Lane | ' tcp:127.0.0.1:%i' % self.listenPort ) |
1386 | 27da832d | Rich Lane | |
1387 | |||
1388 | 80a8fa62 | Bob Lantz | class Controller( Node ): |
1389 | 7c371cf3 | Bob Lantz | """A Controller is a Node that is running (or has execed?) an
|
1390 | 80a8fa62 | Bob Lantz | OpenFlow controller."""
|
1391 | 1bb4412f | Brandon Heller | |
1392 | 57fd19ef | Bob Lantz | def __init__( self, name, inNamespace=False, command='controller', |
1393 | edf60032 | Brandon Heller | cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1", |
1394 | 5cb4a542 | Gregory Gee | port=6633, protocol='tcp', **params ): |
1395 | 57fd19ef | Bob Lantz | self.command = command
|
1396 | 1bb4412f | Brandon Heller | self.cargs = cargs
|
1397 | self.cdir = cdir
|
||
1398 | 84a91a14 | Bob Lantz | self.ip = ip
|
1399 | 60d9ead6 | David Erickson | self.port = port
|
1400 | 5cb4a542 | Gregory Gee | self.protocol = protocol
|
1401 | 086ef80e | Bob Lantz | Node.__init__( self, name, inNamespace=inNamespace,
|
1402 | edf60032 | Brandon Heller | ip=ip, **params ) |
1403 | ec969b7f | Bob Lantz | self.checkListening()
|
1404 | |||
1405 | def checkListening( self ): |
||
1406 | "Make sure no controllers are running on our port"
|
||
1407 | b453e006 | Brandon Heller | # Verify that Telnet is installed first:
|
1408 | c8b85746 | Bob Lantz | out, _err, returnCode = errRun( "which telnet" )
|
1409 | b453e006 | Brandon Heller | if 'telnet' not in out or returnCode != 0: |
1410 | raise Exception( "Error running telnet to check for listening " |
||
1411 | "controllers; please check that it is "
|
||
1412 | "installed." )
|
||
1413 | ec969b7f | Bob Lantz | listening = self.cmd( "echo A | telnet -e A %s %d" % |
1414 | ( self.ip, self.port ) ) |
||
1415 | b5580601 | Bob Lantz | if 'Connected' in listening: |
1416 | c34a000e | backb1 | servers = self.cmd( 'netstat -natp' ).split( '\n' ) |
1417 | ec969b7f | Bob Lantz | pstr = ':%d ' % self.port |
1418 | 28c2cdc2 | Bob Lantz | clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ] |
1419 | ec969b7f | Bob Lantz | raise Exception( "Please shut down the controller which is" |
1420 | " running on port %d:\n" % self.port + |
||
1421 | 28c2cdc2 | Bob Lantz | '\n'.join( clist ) )
|
1422 | 1bb4412f | Brandon Heller | |
1423 | 80a8fa62 | Bob Lantz | def start( self ): |
1424 | """Start <controller> <args> on controller.
|
||
1425 | Log to /tmp/cN.log"""
|
||
1426 | 57fd19ef | Bob Lantz | pathCheck( self.command )
|
1427 | 1bb4412f | Brandon Heller | cout = '/tmp/' + self.name + '.log' |
1428 | if self.cdir is not None: |
||
1429 | efc9a01c | Bob Lantz | self.cmd( 'cd ' + self.cdir ) |
1430 | 57fd19ef | Bob Lantz | self.cmd( self.command + ' ' + self.cargs % self.port + |
1431 | c11d5773 | cody burkard | ' 1>' + cout + ' 2>' + cout + ' &' ) |
1432 | 723d068c | Brandon Heller | self.execed = False |
1433 | 89bf3103 | Brandon Heller | |
1434 | b1ec912d | Bob Lantz | def stop( self, *args, **kwargs ): |
1435 | 80a8fa62 | Bob Lantz | "Stop controller."
|
1436 | 57fd19ef | Bob Lantz | self.cmd( 'kill %' + self.command ) |
1437 | 098bede0 | Bob Lantz | self.cmd( 'wait %' + self.command ) |
1438 | 3b4738c2 | Bob Lantz | kwargs.update( deleteIntfs=False )
|
1439 | b1ec912d | Bob Lantz | super( Controller, self ).stop( *args, **kwargs ) |
1440 | 89bf3103 | Brandon Heller | |
1441 | d44a5843 | Bob Lantz | def IP( self, intf=None ): |
1442 | 80a8fa62 | Bob Lantz | "Return IP address of the Controller"
|
1443 | a6bcad8f | Bob Lantz | if self.intfs: |
1444 | ip = Node.IP( self, intf )
|
||
1445 | else:
|
||
1446 | 84a91a14 | Bob Lantz | ip = self.ip
|
1447 | d44a5843 | Bob Lantz | return ip
|
1448 | 723d068c | Brandon Heller | |
1449 | 8856d284 | Bob Lantz | def __repr__( self ): |
1450 | "More informative string representation"
|
||
1451 | return '<%s %s: %s:%s pid=%s> ' % ( |
||
1452 | c0095746 | Brandon Heller | self.__class__.__name__, self.name, |
1453 | self.IP(), self.port, self.pid ) |
||
1454 | 7a3159c9 | Bob Lantz | |
1455 | 5ac3cde2 | Cody Burkard | @classmethod
|
1456 | b1ec912d | Bob Lantz | def isAvailable( cls ): |
1457 | "Is controller available?"
|
||
1458 | 5ac3cde2 | Cody Burkard | return quietRun( 'which controller' ) |
1459 | 84a91a14 | Bob Lantz | |
1460 | 7a3159c9 | Bob Lantz | |
1461 | 9addfc13 | Bob Lantz | class OVSController( Controller ): |
1462 | "Open vSwitch controller"
|
||
1463 | def __init__( self, name, command='ovs-controller', **kwargs ): |
||
1464 | 5ac3cde2 | Cody Burkard | if quietRun( 'which test-controller' ): |
1465 | command = 'test-controller'
|
||
1466 | 9addfc13 | Bob Lantz | Controller.__init__( self, name, command=command, **kwargs )
|
1467 | 7a3159c9 | Bob Lantz | |
1468 | 5ac3cde2 | Cody Burkard | @classmethod
|
1469 | b1ec912d | Bob Lantz | def isAvailable( cls ): |
1470 | return ( quietRun( 'which ovs-controller' ) or |
||
1471 | quietRun( 'which test-controller' ) )
|
||
1472 | 80a8fa62 | Bob Lantz | |
1473 | class NOX( Controller ): |
||
1474 | "Controller to run a NOX application."
|
||
1475 | |||
1476 | 2db4268b | Bob Lantz | def __init__( self, name, *noxArgs, **kwargs ): |
1477 | 80a8fa62 | Bob Lantz | """Init.
|
1478 | name: name to give controller
|
||
1479 | 2db4268b | Bob Lantz | noxArgs: arguments (strings) to pass to NOX"""
|
1480 | 80a8fa62 | Bob Lantz | if not noxArgs: |
1481 | 2db4268b | Bob Lantz | warn( 'warning: no NOX modules specified; '
|
1482 | 'running packetdump only\n' )
|
||
1483 | 80a8fa62 | Bob Lantz | noxArgs = [ 'packetdump' ]
|
1484 | 2db4268b | Bob Lantz | elif type( noxArgs ) not in ( list, tuple ): |
1485 | f32a5468 | Brandon Heller | noxArgs = [ noxArgs ] |
1486 | 137ec305 | Bob Lantz | |
1487 | 4f4f1dd2 | Brandon Heller | if 'NOX_CORE_DIR' not in os.environ: |
1488 | 137ec305 | Bob Lantz | exit( 'exiting; please set missing NOX_CORE_DIR env var' ) |
1489 | 80a8fa62 | Bob Lantz | noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
|
1490 | 4f4f1dd2 | Brandon Heller | |
1491 | 80a8fa62 | Bob Lantz | Controller.__init__( self, name,
|
1492 | edf60032 | Brandon Heller | command=noxCoreDir + '/nox_core',
|
1493 | cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
|
||
1494 | 2e089b5e | Brandon Heller | ' '.join( noxArgs ),
|
1495 | edf60032 | Brandon Heller | cdir=noxCoreDir, |
1496 | **kwargs ) |
||
1497 | 80a8fa62 | Bob Lantz | |
1498 | 686a9993 | cody burkard | class RYU( Controller ): |
1499 | "Controller to run Ryu application"
|
||
1500 | def __init__( self, name, *ryuArgs, **kwargs ): |
||
1501 | """Init.
|
||
1502 | name: name to give controller.
|
||
1503 | ryuArgs: arguments and modules to pass to Ryu"""
|
||
1504 | homeDir = quietRun( 'printenv HOME' ).strip( '\r\n' ) |
||
1505 | ryuCoreDir = '%s/ryu/ryu/app/' % homeDir
|
||
1506 | if not ryuArgs: |
||
1507 | warn( 'warning: no Ryu modules specified; '
|
||
1508 | 'running simple_switch only\n' )
|
||
1509 | ryuArgs = [ ryuCoreDir + 'simple_switch.py' ]
|
||
1510 | elif type( ryuArgs ) not in ( list, tuple ): |
||
1511 | ryuArgs = [ ryuArgs ] |
||
1512 | |||
1513 | Controller.__init__( self, name,
|
||
1514 | 7a3159c9 | Bob Lantz | command='ryu-manager',
|
1515 | cargs='--ofp-tcp-listen-port %s ' +
|
||
1516 | ' '.join( ryuArgs ),
|
||
1517 | cdir=ryuCoreDir, |
||
1518 | **kwargs ) |
||
1519 | 80a8fa62 | Bob Lantz | |
1520 | class RemoteController( Controller ): |
||
1521 | "Controller running outside of Mininet's control."
|
||
1522 | |||
1523 | 0eba655d | Bob Lantz | def __init__( self, name, ip='127.0.0.1', |
1524 | edf60032 | Brandon Heller | port=6633, **kwargs):
|
1525 | 80a8fa62 | Bob Lantz | """Init.
|
1526 | name: name to give controller
|
||
1527 | 2aafefc2 | Bob Lantz | ip: the IP address where the remote controller is
|
1528 | 80a8fa62 | Bob Lantz | listening
|
1529 | port: the port where the remote controller is listening"""
|
||
1530 | edf60032 | Brandon Heller | Controller.__init__( self, name, ip=ip, port=port, **kwargs )
|
1531 | 80a8fa62 | Bob Lantz | |
1532 | def start( self ): |
||
1533 | "Overridden to do nothing."
|
||
1534 | 60d9ead6 | David Erickson | return
|
1535 | |||
1536 | 80a8fa62 | Bob Lantz | def stop( self ): |
1537 | "Overridden to do nothing."
|
||
1538 | 60d9ead6 | David Erickson | return
|
1539 | 2b35a2ca | James Page | |
1540 | def checkListening( self ): |
||
1541 | 54c51c02 | Bob Lantz | "Warn if remote controller is not accessible"
|
1542 | 2b35a2ca | James Page | listening = self.cmd( "echo A | telnet -e A %s %d" % |
1543 | ( self.ip, self.port ) ) |
||
1544 | b5580601 | Bob Lantz | if 'Connected' not in listening: |
1545 | 54c51c02 | Bob Lantz | warn( "Unable to contact the remote controller"
|
1546 | 2e089b5e | Brandon Heller | " at %s:%d\n" % ( self.ip, self.port ) ) |
1547 | 5ac3cde2 | Cody Burkard | |
1548 | |||
1549 | b1ec912d | Bob Lantz | DefaultControllers = ( Controller, OVSController ) |
1550 | 39a3b73f | Tomasz Buchert | |
1551 | 1b69ea13 | Bob Lantz | def findController( controllers=DefaultControllers ): |
1552 | "Return first available controller from list, if any"
|
||
1553 | for controller in controllers: |
||
1554 | 5ac3cde2 | Cody Burkard | if controller.isAvailable():
|
1555 | f51eddef | Bob Lantz | return controller
|
1556 | 1b69ea13 | Bob Lantz | |
1557 | def DefaultController( name, controllers=DefaultControllers, **kwargs ): |
||
1558 | "Find a controller that is available and instantiate it"
|
||
1559 | ccd3276d | Bob Lantz | controller = findController( controllers ) |
1560 | if not controller: |
||
1561 | raise Exception( 'Could not find a default OpenFlow controller' ) |
||
1562 | return controller( name, **kwargs ) |