blob: 84e0bc6dda1cd34692755f065a1eeb46e5bb8ece [file] [log] [blame]
carlosmscabralf40ecd12013-02-01 18:15:58 -02001"""
2Node objects for Mininet.
3
4Nodes provide a simple abstraction for interacting with hosts, switches
5and controllers. Local nodes are simply one or more processes on the local
6machine.
7
8Node: superclass for all (primarily local) network nodes.
9
10Host: 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
16CPULimitedHost: a virtual host whose CPU bandwidth is limited by
17 RT or CFS bandwidth limiting.
18
19Switch: superclass for switch nodes.
20
21UserSwitch: a switch using the user-space switch from the OpenFlow
22 reference implementation.
23
24KernelSwitch: a switch using the kernel switch from the OpenFlow reference
25 implementation.
26
27OVSSwitch: a switch using the OpenVSwitch OpenFlow-compatible switch
28 implementation (openvswitch.org).
29
30Controller: superclass for OpenFlow controllers. The default controller
31 is controller(8) from the reference implementation.
32
33NOXController: a controller node using NOX (noxrepo.org).
34
35RemoteController: a remote controller node, which may use any
36 arbitrary OpenFlow-compatible controller, and which is not
37 created or managed by mininet.
38
39Future 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"""
46
47import os
48import re
49import signal
50import select
51from subprocess import Popen, PIPE, STDOUT
52
53from mininet.log import info, error, warn, debug
54from mininet.util import ( quietRun, errRun, errFail, moveIntf, isShellBuiltin,
carlosmscabrale121a7b2013-02-18 18:14:53 -030055 numCores, retry, mountCgroups, run )
carlosmscabralf40ecd12013-02-01 18:15:58 -020056from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN
57from mininet.link import Link, Intf, TCIntf
Caio188d2f32015-01-22 23:35:08 -020058import pdb
carlosmscabralf40ecd12013-02-01 18:15:58 -020059
60class Node( object ):
61 """A virtual network node is simply a shell in a network namespace.
62 We communicate with it using pipes."""
63
64 portBase = 0 # Nodes always start with eth0/port0, even in OF 1.0
65
66 def __init__( self, name, inNamespace=True, **params ):
67 """name: name of node
68 inNamespace: in network namespace?
69 params: Node parameters (see config() for details)"""
70
71 # Make sure class actually works
72 self.checkSetup()
73
74 self.name = name
75 self.inNamespace = inNamespace
76
77 # Stash configuration parameters for future reference
78 self.params = params
79
80 self.intfs = {} # dict of port numbers to interfaces
81 self.ports = {} # dict of interfaces to port numbers
82 # replace with Port objects, eventually ?
83 self.nameToIntf = {} # dict of interface names to Intfs
84
85 # Make pylint happy
86 ( self.shell, self.execed, self.pid, self.stdin, self.stdout,
87 self.lastPid, self.lastCmd, self.pollOut ) = (
88 None, None, None, None, None, None, None, None )
89 self.waiting = False
90 self.readbuf = ''
91
92 # Start command interpreter shell
93 self.startShell()
94
95 # File descriptor to node mapping support
96 # Class variables and methods
97
98 inToNode = {} # mapping of input fds to nodes
99 outToNode = {} # mapping of output fds to nodes
100
101 @classmethod
102 def fdToNode( cls, fd ):
103 """Return node corresponding to given file descriptor.
104 fd: file descriptor
105 returns: node"""
106 node = cls.outToNode.get( fd )
107 return node or cls.inToNode.get( fd )
108
109 # Command support via shell process in namespace
110
111 def startShell( self ):
112 "Start a shell process for running commands"
113 if self.shell:
114 error( "%s: shell is already running" )
115 return
116 # mnexec: (c)lose descriptors, (d)etach from tty,
117 # (p)rint pid, and run in (n)amespace
118 opts = '-cdp'
119 if self.inNamespace:
120 opts += 'n'
121 # bash -m: enable job control
122 cmd = [ 'mnexec', opts, 'bash', '-m' ]
123 self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT,
124 close_fds=True )
125 self.stdin = self.shell.stdin
126 self.stdout = self.shell.stdout
127 self.pid = self.shell.pid
128 self.pollOut = select.poll()
129 self.pollOut.register( self.stdout )
130 # Maintain mapping between file descriptors and nodes
131 # This is useful for monitoring multiple nodes
132 # using select.poll()
133 self.outToNode[ self.stdout.fileno() ] = self
134 self.inToNode[ self.stdin.fileno() ] = self
135 self.execed = False
136 self.lastCmd = None
137 self.lastPid = None
138 self.readbuf = ''
139 self.waiting = False
140
141 def cleanup( self ):
142 "Help python collect its garbage."
143 if not self.inNamespace:
144 for intfName in self.intfNames():
145 if self.name in intfName:
146 quietRun( 'ip link del ' + intfName )
147 self.shell = None
148
149 # Subshell I/O, commands and control
150
151 def read( self, maxbytes=1024 ):
152 """Buffered read from node, non-blocking.
153 maxbytes: maximum number of bytes to return"""
154 count = len( self.readbuf )
155 if count < maxbytes:
156 data = os.read( self.stdout.fileno(), maxbytes - count )
157 self.readbuf += data
158 if maxbytes >= len( self.readbuf ):
159 result = self.readbuf
160 self.readbuf = ''
161 else:
162 result = self.readbuf[ :maxbytes ]
163 self.readbuf = self.readbuf[ maxbytes: ]
164 return result
165
166 def readline( self ):
167 """Buffered readline from node, non-blocking.
168 returns: line (minus newline) or None"""
169 self.readbuf += self.read( 1024 )
170 if '\n' not in self.readbuf:
171 return None
172 pos = self.readbuf.find( '\n' )
173 line = self.readbuf[ 0: pos ]
174 self.readbuf = self.readbuf[ pos + 1: ]
175 return line
176
177 def write( self, data ):
178 """Write data to node.
179 data: string"""
180 os.write( self.stdin.fileno(), data )
181
182 def terminate( self ):
183 "Send kill signal to Node and clean up after it."
184 os.kill( self.pid, signal.SIGKILL )
185 self.cleanup()
186
187 def stop( self ):
188 "Stop node."
189 self.terminate()
190
191 def waitReadable( self, timeoutms=None ):
192 """Wait until node's output is readable.
193 timeoutms: timeout in ms or None to wait indefinitely."""
194 if len( self.readbuf ) == 0:
195 self.pollOut.poll( timeoutms )
196
197 def sendCmd( self, *args, **kwargs ):
198 """Send a command, followed by a command to echo a sentinel,
199 and return without waiting for the command to complete.
200 args: command and arguments, or string
201 printPid: print command's PID?"""
202 assert not self.waiting
203 printPid = kwargs.get( 'printPid', True )
204 # Allow sendCmd( [ list ] )
205 if len( args ) == 1 and type( args[ 0 ] ) is list:
206 cmd = args[ 0 ]
207 # Allow sendCmd( cmd, arg1, arg2... )
208 elif len( args ) > 0:
209 cmd = args
210 # Convert to string
211 if not isinstance( cmd, str ):
212 cmd = ' '.join( [ str( c ) for c in cmd ] )
213 if not re.search( r'\w', cmd ):
214 # Replace empty commands with something harmless
215 cmd = 'echo -n'
216 self.lastCmd = cmd
217 printPid = printPid and not isShellBuiltin( cmd )
218 if len( cmd ) > 0 and cmd[ -1 ] == '&':
219 # print ^A{pid}\n{sentinel}
220 cmd += ' printf "\\001%d\n\\177" $! \n'
221 else:
222 # print sentinel
223 cmd += '; printf "\\177"'
224 if printPid and not isShellBuiltin( cmd ):
225 cmd = 'mnexec -p ' + cmd
226 self.write( cmd + '\n' )
227 self.lastPid = None
228 self.waiting = True
229
230 def sendInt( self, sig=signal.SIGINT ):
231 "Interrupt running command."
232 if self.lastPid:
233 try:
234 os.kill( self.lastPid, sig )
235 except OSError:
236 pass
237
238 def monitor( self, timeoutms=None ):
239 """Monitor and return the output of a command.
240 Set self.waiting to False if command has completed.
241 timeoutms: timeout in ms or None to wait indefinitely."""
242 self.waitReadable( timeoutms )
243 data = self.read( 1024 )
244 # Look for PID
245 marker = chr( 1 ) + r'\d+\n'
246 if chr( 1 ) in data:
247 markers = re.findall( marker, data )
248 if markers:
249 self.lastPid = int( markers[ 0 ][ 1: ] )
250 data = re.sub( marker, '', data )
251 # Look for sentinel/EOF
252 if len( data ) > 0 and data[ -1 ] == chr( 127 ):
253 self.waiting = False
254 data = data[ :-1 ]
255 elif chr( 127 ) in data:
256 self.waiting = False
257 data = data.replace( chr( 127 ), '' )
258 return data
259
260 def waitOutput( self, verbose=False ):
261 """Wait for a command to complete.
262 Completion is signaled by a sentinel character, ASCII(127)
263 appearing in the output stream. Wait for the sentinel and return
264 the output, including trailing newline.
265 verbose: print output interactively"""
266 log = info if verbose else debug
267 output = ''
268 while self.waiting:
269 data = self.monitor()
270 output += data
271 log( data )
272 return output
273
274 def cmd( self, *args, **kwargs ):
275 """Send a command, wait for output, and return it.
276 cmd: string"""
277 verbose = kwargs.get( 'verbose', False )
278 log = info if verbose else debug
279 log( '*** %s : %s\n' % ( self.name, args ) )
280 self.sendCmd( *args, **kwargs )
281 return self.waitOutput( verbose )
282
283 def cmdPrint( self, *args):
284 """Call cmd and printing its output
285 cmd: string"""
286 return self.cmd( *args, **{ 'verbose': True } )
287
288 def popen( self, *args, **kwargs ):
289 """Return a Popen() object in our namespace
290 args: Popen() args, single list, or string
291 kwargs: Popen() keyword args"""
292 defaults = { 'stdout': PIPE, 'stderr': PIPE,
293 'mncmd':
294 [ 'mnexec', '-a', str( self.pid ) ] }
295 defaults.update( kwargs )
296 if len( args ) == 1:
297 if type( args[ 0 ] ) is list:
298 # popen([cmd, arg1, arg2...])
299 cmd = args[ 0 ]
300 elif type( args[ 0 ] ) is str:
301 # popen("cmd arg1 arg2...")
302 cmd = args[ 0 ].split()
303 else:
304 raise Exception( 'popen() requires a string or list' )
305 elif len( args ) > 0:
306 # popen( cmd, arg1, arg2... )
307 cmd = list( args )
308 # Attach to our namespace using mnexec -a
309 mncmd = defaults[ 'mncmd' ]
310 del defaults[ 'mncmd' ]
311 cmd = mncmd + cmd
312 # Shell requires a string, not a list!
313 if defaults.get( 'shell', False ):
314 cmd = ' '.join( cmd )
315 return Popen( cmd, **defaults )
316
317 def pexec( self, *args, **kwargs ):
318 """Execute a command using popen
319 returns: out, err, exitcode"""
320 popen = self.popen( *args, **kwargs)
321 out, err = popen.communicate()
322 exitcode = popen.wait()
323 return out, err, exitcode
324
325 # Interface management, configuration, and routing
326
327 # BL notes: This might be a bit redundant or over-complicated.
328 # However, it does allow a bit of specialization, including
329 # changing the canonical interface names. It's also tricky since
330 # the real interfaces are created as veth pairs, so we can't
331 # make a single interface at a time.
332
333 def newPort( self ):
334 "Return the next port number to allocate."
335 if len( self.ports ) > 0:
336 return max( self.ports.values() ) + 1
337 return self.portBase
338
339 def addIntf( self, intf, port=None ):
340 """Add an interface.
341 intf: interface
342 port: port number (optional, typically OpenFlow port number)"""
343 if port is None:
344 port = self.newPort()
345 self.intfs[ port ] = intf
346 self.ports[ intf ] = port
347 self.nameToIntf[ intf.name ] = intf
348 debug( '\n' )
349 debug( 'added intf %s:%d to node %s\n' % ( intf, port, self.name ) )
350 if self.inNamespace:
351 debug( 'moving', intf, 'into namespace for', self.name, '\n' )
352 moveIntf( intf.name, self )
353
354 def defaultIntf( self ):
355 "Return interface for lowest port"
356 ports = self.intfs.keys()
357 if ports:
358 return self.intfs[ min( ports ) ]
359 else:
360 warn( '*** defaultIntf: warning:', self.name,
361 'has no interfaces\n' )
362
363 def intf( self, intf='' ):
364 """Return our interface object with given string name,
365 default intf if name is falsy (None, empty string, etc).
366 or the input intf arg.
367
368 Having this fcn return its arg for Intf objects makes it
369 easier to construct functions with flexible input args for
370 interfaces (those that accept both string names and Intf objects).
371 """
372 if not intf:
373 return self.defaultIntf()
374 elif type( intf) is str:
375 return self.nameToIntf[ intf ]
376 else:
377 return intf
378
379 def connectionsTo( self, node):
380 "Return [ intf1, intf2... ] for all intfs that connect self to node."
381 # We could optimize this if it is important
382 connections = []
383 for intf in self.intfList():
384 link = intf.link
385 if link:
386 node1, node2 = link.intf1.node, link.intf2.node
387 if node1 == self and node2 == node:
388 connections += [ ( intf, link.intf2 ) ]
389 elif node1 == node and node2 == self:
390 connections += [ ( intf, link.intf1 ) ]
391 return connections
392
393 def deleteIntfs( self ):
394 "Delete all of our interfaces."
395 # In theory the interfaces should go away after we shut down.
396 # However, this takes time, so we're better off removing them
397 # explicitly so that we won't get errors if we run before they
398 # have been removed by the kernel. Unfortunately this is very slow,
399 # at least with Linux kernels before 2.6.33
400 for intf in self.intfs.values():
401 intf.delete()
402 info( '.' )
403
404 # Routing support
405
406 def setARP( self, ip, mac ):
407 """Add an ARP entry.
408 ip: IP address as string
409 mac: MAC address as string"""
410 result = self.cmd( 'arp', '-s', ip, mac )
411 return result
412
413 def setHostRoute( self, ip, intf ):
414 """Add route to host.
415 ip: IP address as dotted decimal
416 intf: string, interface name"""
417 return self.cmd( 'route add -host', ip, 'dev', intf )
418
419 def setDefaultRoute( self, intf=None ):
420 """Set the default route to go through intf.
421 intf: string, interface name"""
422 if not intf:
423 intf = self.defaultIntf()
424 self.cmd( 'ip route flush root 0/0' )
425 return self.cmd( 'route add default %s' % intf )
426
427 # Convenience and configuration methods
428
429 def setMAC( self, mac, intf=None ):
430 """Set the MAC address for an interface.
431 intf: intf or intf name
432 mac: MAC address as string"""
433 return self.intf( intf ).setMAC( mac )
434
435 def setIP( self, ip, prefixLen=8, intf=None ):
436 """Set the IP address for an interface.
437 intf: intf or intf name
438 ip: IP address as a string
439 prefixLen: prefix length, e.g. 8 for /8 or 16M addrs"""
440 # This should probably be rethought
441 if '/' not in ip:
442 ip = '%s/%s' % ( ip, prefixLen )
443 return self.intf( intf ).setIP( ip )
444
445 def IP( self, intf=None ):
446 "Return IP address of a node or specific interface."
447 return self.intf( intf ).IP()
448
449 def MAC( self, intf=None ):
450 "Return MAC address of a node or specific interface."
451 return self.intf( intf ).MAC()
452
453 def intfIsUp( self, intf=None ):
454 "Check if an interface is up."
455 return self.intf( intf ).isUp()
456
457 # The reason why we configure things in this way is so
458 # That the parameters can be listed and documented in
459 # the config method.
460 # Dealing with subclasses and superclasses is slightly
461 # annoying, but at least the information is there!
462
463 def setParam( self, results, method, **param ):
464 """Internal method: configure a *single* parameter
465 results: dict of results to update
466 method: config method name
467 param: arg=value (ignore if value=None)
468 value may also be list or dict"""
469 name, value = param.items()[ 0 ]
470 f = getattr( self, method, None )
471 if not f or value is None:
472 return
473 if type( value ) is list:
474 result = f( *value )
475 elif type( value ) is dict:
476 result = f( **value )
477 else:
478 result = f( value )
479 results[ name ] = result
480 return result
481
482 def config( self, mac=None, ip=None,
483 defaultRoute=None, lo='up', **_params ):
484 """Configure Node according to (optional) parameters:
485 mac: MAC address for default interface
486 ip: IP address for default interface
487 ifconfig: arbitrary interface configuration
488 Subclasses should override this method and call
489 the parent class's config(**params)"""
490 # If we were overriding this method, we would call
491 # the superclass config method here as follows:
492 # r = Parent.config( **_params )
493 r = {}
494 self.setParam( r, 'setMAC', mac=mac )
495 self.setParam( r, 'setIP', ip=ip )
496 self.setParam( r, 'defaultRoute', defaultRoute=defaultRoute )
497 # This should be examined
498 self.cmd( 'ifconfig lo ' + lo )
499 return r
500
501 def configDefault( self, **moreParams ):
502 "Configure with default parameters"
503 self.params.update( moreParams )
504 self.config( **self.params )
505
506 # This is here for backward compatibility
507 def linkTo( self, node, link=Link ):
508 """(Deprecated) Link to another node
509 replace with Link( node1, node2)"""
510 return link( self, node )
511
512 # Other methods
513
514 def intfList( self ):
515 "List of our interfaces sorted by port number"
516 return [ self.intfs[ p ] for p in sorted( self.intfs.iterkeys() ) ]
517
518 def intfNames( self ):
519 "The names of our interfaces sorted by port number"
520 return [ str( i ) for i in self.intfList() ]
521
522 def __repr__( self ):
523 "More informative string representation"
524 intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
525 for i in self.intfList() ] ) )
526 return '<%s %s: %s pid=%s> ' % (
527 self.__class__.__name__, self.name, intfs, self.pid )
528
529 def __str__( self ):
530 "Abbreviated string representation"
531 return self.name
532
533 # Automatic class setup support
534
535 isSetup = False
536
537 @classmethod
538 def checkSetup( cls ):
539 "Make sure our class and superclasses are set up"
540 while cls and not getattr( cls, 'isSetup', True ):
541 cls.setup()
542 cls.isSetup = True
543 # Make pylint happy
544 cls = getattr( type( cls ), '__base__', None )
545
546 @classmethod
547 def setup( cls ):
548 "Make sure our class dependencies are available"
549 pathCheck( 'mnexec', 'ifconfig', moduleName='Mininet')
550
551
552class Host( Node ):
553 "A host is simply a Node"
554 pass
555
556
carlosmscabral6d3dd602013-03-23 11:12:34 -0300557
carlosmscabralf40ecd12013-02-01 18:15:58 -0200558class CPULimitedHost( Host ):
559
560 "CPU limited host"
561
562 def __init__( self, name, sched='cfs', **kwargs ):
563 Host.__init__( self, name, **kwargs )
564 # Initialize class if necessary
565 if not CPULimitedHost.inited:
566 CPULimitedHost.init()
567 # Create a cgroup and move shell into it
568 self.cgroup = 'cpu,cpuacct,cpuset:/' + self.name
569 errFail( 'cgcreate -g ' + self.cgroup )
570 # We don't add ourselves to a cpuset because you must
571 # specify the cpu and memory placement first
572 errFail( 'cgclassify -g cpu,cpuacct:/%s %s' % ( self.name, self.pid ) )
573 # BL: Setting the correct period/quota is tricky, particularly
574 # for RT. RT allows very small quotas, but the overhead
575 # seems to be high. CFS has a mininimum quota of 1 ms, but
576 # still does better with larger period values.
577 self.period_us = kwargs.get( 'period_us', 100000 )
578 self.sched = sched
579 self.rtprio = 20
580
581 def cgroupSet( self, param, value, resource='cpu' ):
582 "Set a cgroup parameter and return its value"
583 cmd = 'cgset -r %s.%s=%s /%s' % (
584 resource, param, value, self.name )
585 quietRun( cmd )
586 nvalue = int( self.cgroupGet( param, resource ) )
587 if nvalue != value:
588 error( '*** error: cgroupSet: %s set to %s instead of %s\n'
589 % ( param, nvalue, value ) )
590 return nvalue
591
592 def cgroupGet( self, param, resource='cpu' ):
593 "Return value of cgroup parameter"
594 cmd = 'cgget -r %s.%s /%s' % (
595 resource, param, self.name )
carlosmscabral6d3dd602013-03-23 11:12:34 -0300596
carlosmscabrale121a7b2013-02-18 18:14:53 -0300597 return int(quietRun( cmd ).split()[ -1 ] )
carlosmscabralf40ecd12013-02-01 18:15:58 -0200598
599 def cgroupDel( self ):
600 "Clean up our cgroup"
601 # info( '*** deleting cgroup', self.cgroup, '\n' )
602 _out, _err, exitcode = errRun( 'cgdelete -r ' + self.cgroup )
603 return exitcode != 0
604
605 def popen( self, *args, **kwargs ):
606 """Return a Popen() object in node's namespace
607 args: Popen() args, single list, or string
608 kwargs: Popen() keyword args"""
609 # Tell mnexec to execute command in our cgroup
610 mncmd = [ 'mnexec', '-a', str( self.pid ),
611 '-g', self.name ]
612 if self.sched == 'rt':
613 mncmd += [ '-r', str( self.rtprio ) ]
614 return Host.popen( self, *args, mncmd=mncmd, **kwargs )
615
616 def cleanup( self ):
617 "Clean up our cgroup"
618 retry( retries=3, delaySecs=1, fn=self.cgroupDel )
619
620 def chrt( self ):
621 "Set RT scheduling priority"
622 quietRun( 'chrt -p %s %s' % ( self.rtprio, self.pid ) )
623 result = quietRun( 'chrt -p %s' % self.pid )
624 firstline = result.split( '\n' )[ 0 ]
625 lastword = firstline.split( ' ' )[ -1 ]
626 if lastword != 'SCHED_RR':
627 error( '*** error: could not assign SCHED_RR to %s\n' % self.name )
628 return lastword
629
630 def rtInfo( self, f ):
631 "Internal method: return parameters for RT bandwidth"
632 pstr, qstr = 'rt_period_us', 'rt_runtime_us'
633 # RT uses wall clock time for period and quota
634 quota = int( self.period_us * f * numCores() )
635 return pstr, qstr, self.period_us, quota
636
637 def cfsInfo( self, f):
638 "Internal method: return parameters for CFS bandwidth"
639 pstr, qstr = 'cfs_period_us', 'cfs_quota_us'
640 # CFS uses wall clock time for period and CPU time for quota.
641 quota = int( self.period_us * f * numCores() )
642 period = self.period_us
643 if f > 0 and quota < 1000:
644 debug( '(cfsInfo: increasing default period) ' )
645 quota = 1000
646 period = int( quota / f / numCores() )
647 return pstr, qstr, period, quota
648
649 # BL comment:
650 # This may not be the right API,
651 # since it doesn't specify CPU bandwidth in "absolute"
652 # units the way link bandwidth is specified.
653 # We should use MIPS or SPECINT or something instead.
654 # Alternatively, we should change from system fraction
655 # to CPU seconds per second, essentially assuming that
656 # all CPUs are the same.
657
658 def setCPUFrac( self, f=-1, sched=None):
659 """Set overall CPU fraction for this host
660 f: CPU bandwidth limit (fraction)
661 sched: 'rt' or 'cfs'
662 Note 'cfs' requires CONFIG_CFS_BANDWIDTH"""
663 if not f:
664 return
665 if not sched:
666 sched = self.sched
667 if sched == 'rt':
668 pstr, qstr, period, quota = self.rtInfo( f )
669 elif sched == 'cfs':
670 pstr, qstr, period, quota = self.cfsInfo( f )
671 else:
672 return
673 if quota < 0:
674 # Reset to unlimited
675 quota = -1
676 # Set cgroup's period and quota
677 self.cgroupSet( pstr, period )
678 self.cgroupSet( qstr, quota )
679 if sched == 'rt':
680 # Set RT priority if necessary
681 self.chrt()
682 info( '(%s %d/%dus) ' % ( sched, quota, period ) )
683
684 def setCPUs( self, cores, mems=0 ):
685 "Specify (real) cores that our cgroup can run on"
686 if type( cores ) is list:
687 cores = ','.join( [ str( c ) for c in cores ] )
688 self.cgroupSet( resource='cpuset', param='cpus',
689 value=cores )
690 # Memory placement is probably not relevant, but we
691 # must specify it anyway
692 self.cgroupSet( resource='cpuset', param='mems',
693 value=mems)
694 # We have to do this here after we've specified
695 # cpus and mems
696 errFail( 'cgclassify -g cpuset:/%s %s' % (
697 self.name, self.pid ) )
698
699 def config( self, cpu=None, cores=None, **params ):
700 """cpu: desired overall system CPU fraction
701 cores: (real) core(s) this host can run on
702 params: parameters for Node.config()"""
703 r = Node.config( self, **params )
704 # Was considering cpu={'cpu': cpu , 'sched': sched}, but
705 # that seems redundant
706
707 self.setParam( r, 'setCPUFrac', cpu=cpu )
708 self.setParam( r, 'setCPUs', cores=cores )
709
710 return r
711
712 inited = False
713
714 @classmethod
715 def init( cls ):
716 "Initialization for CPULimitedHost class"
717 mountCgroups()
718 cls.inited = True
719
carlosmscabralf40ecd12013-02-01 18:15:58 -0200720# Some important things to note:
721#
722# The "IP" address which setIP() assigns to the switch is not
723# an "IP address for the switch" in the sense of IP routing.
724# Rather, it is the IP address for the control interface,
725# on the control network, and it is only relevant to the
726# controller. If you are running in the root namespace
727# (which is the only way to run OVS at the moment), the
728# control interface is the loopback interface, and you
729# normally never want to change its IP address!
730#
731# In general, you NEVER want to attempt to use Linux's
732# network stack (i.e. ifconfig) to "assign" an IP address or
733# MAC address to a switch data port. Instead, you "assign"
734# the IP and MAC addresses in the controller by specifying
735# packets that you want to receive or send. The "MAC" address
736# reported by ifconfig for a switch data port is essentially
737# meaningless. It is important to understand this if you
738# want to create a functional router using OpenFlow.
739
740class Switch( Node ):
741 """A Switch is a Node that is running (or has execed?)
742 an OpenFlow switch."""
743
744 portBase = 1 # Switches start with port 1 in OpenFlow
745 dpidLen = 16 # digits in dpid passed to switch
746
747 def __init__( self, name, dpid=None, opts='', listenPort=None, **params):
748 """dpid: dpid for switch (or None to derive from name, e.g. s1 -> 1)
749 opts: additional switch options
750 listenPort: port to listen on for dpctl connections"""
751 Node.__init__( self, name, **params )
752 self.dpid = dpid if dpid else self.defaultDpid()
753 self.opts = opts
754 self.listenPort = listenPort
755 if not self.inNamespace:
756 self.controlIntf = Intf( 'lo', self, port=0 )
757
758 def defaultDpid( self ):
759 "Derive dpid from switch name, s1 -> 1"
760 try:
761 dpid = int( re.findall( '\d+', self.name )[ 0 ] )
762 dpid = hex( dpid )[ 2: ]
763 dpid = '0' * ( self.dpidLen - len( dpid ) ) + dpid
764 return dpid
765 except IndexError:
766 raise Exception( 'Unable to derive default datapath ID - '
767 'please either specify a dpid or use a '
768 'canonical switch name such as s23.' )
769
770 def defaultIntf( self ):
771 "Return control interface"
772 if self.controlIntf:
773 return self.controlIntf
774 else:
775 return Node.defaultIntf( self )
776
777 def sendCmd( self, *cmd, **kwargs ):
778 """Send command to Node.
779 cmd: string"""
780 kwargs.setdefault( 'printPid', False )
781 if not self.execed:
782 return Node.sendCmd( self, *cmd, **kwargs )
783 else:
784 error( '*** Error: %s has execed and cannot accept commands' %
785 self.name )
786
787 def __repr__( self ):
788 "More informative string representation"
789 intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
790 for i in self.intfList() ] ) )
791 return '<%s %s: %s pid=%s> ' % (
792 self.__class__.__name__, self.name, intfs, self.pid )
793
794class UserSwitch( Switch ):
795 "User-space switch."
796
797 dpidLen = 12
798
799 def __init__( self, name, **kwargs ):
800 """Init.
801 name: name for the switch"""
802 Switch.__init__( self, name, **kwargs )
803 pathCheck( 'ofdatapath', 'ofprotocol',
804 moduleName='the OpenFlow reference user switch' +
805 '(openflow.org)' )
806 if self.listenPort:
807 self.opts += ' --listen=ptcp:%i ' % self.listenPort
808
809 @classmethod
810 def setup( cls ):
811 "Ensure any dependencies are loaded; if not, try to load them."
812 if not os.path.exists( '/dev/net/tun' ):
813 moduleDeps( add=TUN )
814
815 def dpctl( self, *args ):
816 "Run dpctl command"
817 if not self.listenPort:
818 return "can't run dpctl without passive listening port"
819 return self.cmd( 'dpctl ' + ' '.join( args ) +
820 ' tcp:127.0.0.1:%i' % self.listenPort )
821
822 def start( self, controllers ):
823 """Start OpenFlow reference user datapath.
824 Log to /tmp/sN-{ofd,ofp}.log.
825 controllers: list of controller objects"""
826 # Add controllers
827 clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
828 for c in controllers ] )
829 ofdlog = '/tmp/' + self.name + '-ofd.log'
830 ofplog = '/tmp/' + self.name + '-ofp.log'
831 self.cmd( 'ifconfig lo up' )
832 intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
833 self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
834 ' punix:/tmp/' + self.name + ' -d ' + self.dpid +
835 ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
836 self.cmd( 'ofprotocol unix:/tmp/' + self.name +
837 ' ' + clist +
838 ' --fail=closed ' + self.opts +
839 ' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
840
841 def stop( self ):
842 "Stop OpenFlow reference user datapath."
843 self.cmd( 'kill %ofdatapath' )
844 self.cmd( 'kill %ofprotocol' )
845 self.deleteIntfs()
846
847
848class OVSLegacyKernelSwitch( Switch ):
849 """Open VSwitch legacy kernel-space switch using ovs-openflowd.
850 Currently only works in the root namespace."""
851
852 def __init__( self, name, dp=None, **kwargs ):
853 """Init.
854 name: name for switch
855 dp: netlink id (0, 1, 2, ...)
856 defaultMAC: default MAC as unsigned int; random value if None"""
857 Switch.__init__( self, name, **kwargs )
858 self.dp = dp if dp else self.name
859 self.intf = self.dp
860 if self.inNamespace:
861 error( "OVSKernelSwitch currently only works"
862 " in the root namespace.\n" )
863 exit( 1 )
864
865 @classmethod
866 def setup( cls ):
867 "Ensure any dependencies are loaded; if not, try to load them."
868 pathCheck( 'ovs-dpctl', 'ovs-openflowd',
869 moduleName='Open vSwitch (openvswitch.org)')
870 moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
871
872 def start( self, controllers ):
873 "Start up kernel datapath."
874 ofplog = '/tmp/' + self.name + '-ofp.log'
875 quietRun( 'ifconfig lo up' )
876 # Delete local datapath if it exists;
877 # then create a new one monitoring the given interfaces
878 self.cmd( 'ovs-dpctl del-dp ' + self.dp )
879 self.cmd( 'ovs-dpctl add-dp ' + self.dp )
880 intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
881 self.cmd( 'ovs-dpctl', 'add-if', self.dp, ' '.join( intfs ) )
882 # Run protocol daemon
883 clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
884 for c in controllers ] )
885 self.cmd( 'ovs-openflowd ' + self.dp +
886 ' ' + clist +
887 ' --fail=secure ' + self.opts +
888 ' --datapath-id=' + self.dpid +
889 ' 1>' + ofplog + ' 2>' + ofplog + '&' )
890 self.execed = False
891
892 def stop( self ):
893 "Terminate kernel datapath."
894 quietRun( 'ovs-dpctl del-dp ' + self.dp )
895 self.cmd( 'kill %ovs-openflowd' )
896 self.deleteIntfs()
897
898
899class OVSSwitch( Switch ):
900 "Open vSwitch switch. Depends on ovs-vsctl."
901
902 def __init__( self, name, failMode='secure', **params ):
903 """Init.
904 name: name for switch
905 failMode: controller loss behavior (secure|open)"""
906 Switch.__init__( self, name, **params )
907 self.failMode = failMode
908
909 @classmethod
910 def setup( cls ):
911 "Make sure Open vSwitch is installed and working"
912 pathCheck( 'ovs-vsctl',
913 moduleName='Open vSwitch (openvswitch.org)')
914 # This should no longer be needed, and it breaks
915 # with OVS 1.7 which has renamed the kernel module:
916 # moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
917 out, err, exitcode = errRun( 'ovs-vsctl -t 1 show' )
918 if exitcode:
919 error( out + err +
920 'ovs-vsctl exited with code %d\n' % exitcode +
921 '*** Error connecting to ovs-db with ovs-vsctl\n'
922 'Make sure that Open vSwitch is installed, '
923 'that ovsdb-server is running, and that\n'
924 '"ovs-vsctl show" works correctly.\n'
925 'You may wish to try '
926 '"service openvswitch-switch start".\n' )
927 exit( 1 )
928
929 def dpctl( self, *args ):
930 "Run ovs-dpctl command"
931 return self.cmd( 'ovs-dpctl', args[ 0 ], self, *args[ 1: ] )
932
933 @staticmethod
934 def TCReapply( intf ):
935 """Unfortunately OVS and Mininet are fighting
936 over tc queuing disciplines. As a quick hack/
937 workaround, we clear OVS's and reapply our own."""
938 if type( intf ) is TCIntf:
939 intf.config( **intf.params )
940
941 def attach( self, intf ):
942 "Connect a data port"
943 self.cmd( 'ovs-vsctl add-port', self, intf )
944 self.cmd( 'ifconfig', intf, 'up' )
945 self.TCReapply( intf )
946
947 def detach( self, intf ):
948 "Disconnect a data port"
949 self.cmd( 'ovs-vsctl del-port', self, intf )
950
951 def start( self, controllers ):
952 "Start up a new OVS OpenFlow switch using ovs-vsctl"
953 if self.inNamespace:
954 raise Exception(
955 'OVS kernel switch does not work in a namespace' )
956 # We should probably call config instead, but this
957 # requires some rethinking...
958 self.cmd( 'ifconfig lo up' )
959 # Annoyingly, --if-exists option seems not to work
960 self.cmd( 'ovs-vsctl del-br', self )
961 self.cmd( 'ovs-vsctl add-br', self )
962 self.cmd( 'ovs-vsctl -- set Bridge', self,
963 'other_config:datapath-id=' + self.dpid )
964 self.cmd( 'ovs-vsctl set-fail-mode', self, self.failMode )
965 for intf in self.intfList():
966 if not intf.IP():
967 self.attach( intf )
968 # Add controllers
969 clist = ' '.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
970 for c in controllers ] )
971 if self.listenPort:
972 clist += ' ptcp:%s' % self.listenPort
973 self.cmd( 'ovs-vsctl set-controller', self, clist )
974
975 def stop( self ):
976 "Terminate OVS switch."
977 self.cmd( 'ovs-vsctl del-br', self )
978 self.deleteIntfs()
979
980OVSKernelSwitch = OVSSwitch
981
982
983class Controller( Node ):
984 """A Controller is a Node that is running (or has execed?) an
985 OpenFlow controller."""
986
987 def __init__( self, name, inNamespace=False, command='controller',
988 cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
989 port=6633, **params ):
990 self.command = command
991 self.cargs = cargs
992 self.cdir = cdir
993 self.ip = ip
994 self.port = port
995 Node.__init__( self, name, inNamespace=inNamespace,
996 ip=ip, **params )
997 self.cmd( 'ifconfig lo up' ) # Shouldn't be necessary
998 self.checkListening()
999
1000 def checkListening( self ):
1001 "Make sure no controllers are running on our port"
1002 # Verify that Telnet is installed first:
1003 out, _err, returnCode = errRun( "which telnet" )
1004 if 'telnet' not in out or returnCode != 0:
1005 raise Exception( "Error running telnet to check for listening "
1006 "controllers; please check that it is "
1007 "installed." )
1008 listening = self.cmd( "echo A | telnet -e A %s %d" %
1009 ( self.ip, self.port ) )
1010 if 'Unable' not in listening:
1011 servers = self.cmd( 'netstat -atp' ).split( '\n' )
1012 pstr = ':%d ' % self.port
1013 clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ]
1014 raise Exception( "Please shut down the controller which is"
1015 " running on port %d:\n" % self.port +
1016 '\n'.join( clist ) )
1017
1018 def start( self ):
1019 """Start <controller> <args> on controller.
1020 Log to /tmp/cN.log"""
1021 pathCheck( self.command )
1022 cout = '/tmp/' + self.name + '.log'
1023 if self.cdir is not None:
1024 self.cmd( 'cd ' + self.cdir )
1025 self.cmd( self.command + ' ' + self.cargs % self.port +
1026 ' 1>' + cout + ' 2>' + cout + '&' )
1027 self.execed = False
1028
1029 def stop( self ):
1030 "Stop controller."
1031 self.cmd( 'kill %' + self.command )
1032 self.terminate()
1033
1034 def IP( self, intf=None ):
1035 "Return IP address of the Controller"
1036 if self.intfs:
1037 ip = Node.IP( self, intf )
1038 else:
1039 ip = self.ip
1040 return ip
1041
1042 def __repr__( self ):
1043 "More informative string representation"
1044 return '<%s %s: %s:%s pid=%s> ' % (
1045 self.__class__.__name__, self.name,
1046 self.IP(), self.port, self.pid )
1047
1048
1049class OVSController( Controller ):
1050 "Open vSwitch controller"
1051 def __init__( self, name, command='ovs-controller', **kwargs ):
1052 Controller.__init__( self, name, command=command, **kwargs )
1053
1054
1055class NOX( Controller ):
1056 "Controller to run a NOX application."
1057
1058 def __init__( self, name, *noxArgs, **kwargs ):
1059 """Init.
1060 name: name to give controller
1061 noxArgs: arguments (strings) to pass to NOX"""
1062 if not noxArgs:
1063 warn( 'warning: no NOX modules specified; '
1064 'running packetdump only\n' )
1065 noxArgs = [ 'packetdump' ]
1066 elif type( noxArgs ) not in ( list, tuple ):
1067 noxArgs = [ noxArgs ]
1068
1069 if 'NOX_CORE_DIR' not in os.environ:
1070 exit( 'exiting; please set missing NOX_CORE_DIR env var' )
1071 noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
1072
1073 Controller.__init__( self, name,
1074 command=noxCoreDir + '/nox_core',
1075 cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
1076 ' '.join( noxArgs ),
1077 cdir=noxCoreDir,
1078 **kwargs )
1079
1080
1081class RemoteController( Controller ):
1082 "Controller running outside of Mininet's control."
1083
1084 def __init__( self, name, ip='127.0.0.1',
1085 port=6633, **kwargs):
1086 """Init.
1087 name: name to give controller
1088 ip: the IP address where the remote controller is
1089 listening
1090 port: the port where the remote controller is listening"""
1091 Controller.__init__( self, name, ip=ip, port=port, **kwargs )
1092
1093 def start( self ):
1094 "Overridden to do nothing."
1095 return
1096
1097 def stop( self ):
1098 "Overridden to do nothing."
1099 return
1100
1101 def checkListening( self ):
1102 "Warn if remote controller is not accessible"
1103 listening = self.cmd( "echo A | telnet -e A %s %d" %
1104 ( self.ip, self.port ) )
1105 if 'Unable' in listening:
1106 warn( "Unable to contact the remote controller"
1107 " at %s:%d\n" % ( self.ip, self.port ) )