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