blob: 99d2cc5774fe3dbd601ac504478b5f4031b34704 [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,
55 numCores, retry, mountCgroups )
56from 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 )
595 return int( quietRun( cmd ).split()[ -1 ] )
596
597 def cgroupDel( self ):
598 "Clean up our cgroup"
599 # info( '*** deleting cgroup', self.cgroup, '\n' )
600 _out, _err, exitcode = errRun( 'cgdelete -r ' + self.cgroup )
601 return exitcode != 0
602
603 def popen( self, *args, **kwargs ):
604 """Return a Popen() object in node's namespace
605 args: Popen() args, single list, or string
606 kwargs: Popen() keyword args"""
607 # Tell mnexec to execute command in our cgroup
608 mncmd = [ 'mnexec', '-a', str( self.pid ),
609 '-g', self.name ]
610 if self.sched == 'rt':
611 mncmd += [ '-r', str( self.rtprio ) ]
612 return Host.popen( self, *args, mncmd=mncmd, **kwargs )
613
614 def cleanup( self ):
615 "Clean up our cgroup"
616 retry( retries=3, delaySecs=1, fn=self.cgroupDel )
617
618 def chrt( self ):
619 "Set RT scheduling priority"
620 quietRun( 'chrt -p %s %s' % ( self.rtprio, self.pid ) )
621 result = quietRun( 'chrt -p %s' % self.pid )
622 firstline = result.split( '\n' )[ 0 ]
623 lastword = firstline.split( ' ' )[ -1 ]
624 if lastword != 'SCHED_RR':
625 error( '*** error: could not assign SCHED_RR to %s\n' % self.name )
626 return lastword
627
628 def rtInfo( self, f ):
629 "Internal method: return parameters for RT bandwidth"
630 pstr, qstr = 'rt_period_us', 'rt_runtime_us'
631 # RT uses wall clock time for period and quota
632 quota = int( self.period_us * f * numCores() )
633 return pstr, qstr, self.period_us, quota
634
635 def cfsInfo( self, f):
636 "Internal method: return parameters for CFS bandwidth"
637 pstr, qstr = 'cfs_period_us', 'cfs_quota_us'
638 # CFS uses wall clock time for period and CPU time for quota.
639 quota = int( self.period_us * f * numCores() )
640 period = self.period_us
641 if f > 0 and quota < 1000:
642 debug( '(cfsInfo: increasing default period) ' )
643 quota = 1000
644 period = int( quota / f / numCores() )
645 return pstr, qstr, period, quota
646
647 # BL comment:
648 # This may not be the right API,
649 # since it doesn't specify CPU bandwidth in "absolute"
650 # units the way link bandwidth is specified.
651 # We should use MIPS or SPECINT or something instead.
652 # Alternatively, we should change from system fraction
653 # to CPU seconds per second, essentially assuming that
654 # all CPUs are the same.
655
656 def setCPUFrac( self, f=-1, sched=None):
657 """Set overall CPU fraction for this host
658 f: CPU bandwidth limit (fraction)
659 sched: 'rt' or 'cfs'
660 Note 'cfs' requires CONFIG_CFS_BANDWIDTH"""
661 if not f:
662 return
663 if not sched:
664 sched = self.sched
665 if sched == 'rt':
666 pstr, qstr, period, quota = self.rtInfo( f )
667 elif sched == 'cfs':
668 pstr, qstr, period, quota = self.cfsInfo( f )
669 else:
670 return
671 if quota < 0:
672 # Reset to unlimited
673 quota = -1
674 # Set cgroup's period and quota
675 self.cgroupSet( pstr, period )
676 self.cgroupSet( qstr, quota )
677 if sched == 'rt':
678 # Set RT priority if necessary
679 self.chrt()
680 info( '(%s %d/%dus) ' % ( sched, quota, period ) )
681
682 def setCPUs( self, cores, mems=0 ):
683 "Specify (real) cores that our cgroup can run on"
684 if type( cores ) is list:
685 cores = ','.join( [ str( c ) for c in cores ] )
686 self.cgroupSet( resource='cpuset', param='cpus',
687 value=cores )
688 # Memory placement is probably not relevant, but we
689 # must specify it anyway
690 self.cgroupSet( resource='cpuset', param='mems',
691 value=mems)
692 # We have to do this here after we've specified
693 # cpus and mems
694 errFail( 'cgclassify -g cpuset:/%s %s' % (
695 self.name, self.pid ) )
696
697 def config( self, cpu=None, cores=None, **params ):
698 """cpu: desired overall system CPU fraction
699 cores: (real) core(s) this host can run on
700 params: parameters for Node.config()"""
701 r = Node.config( self, **params )
702 # Was considering cpu={'cpu': cpu , 'sched': sched}, but
703 # that seems redundant
704
705 self.setParam( r, 'setCPUFrac', cpu=cpu )
706 self.setParam( r, 'setCPUs', cores=cores )
707
708 return r
709
710 inited = False
711
712 @classmethod
713 def init( cls ):
714 "Initialization for CPULimitedHost class"
715 mountCgroups()
716 cls.inited = True
717
718class CCNHost( Host ):
719 "CCNHost is a Host that always runs the ccnd daemon"
720
721 def __init__( self, name, **kwargs ):
722
723
724 Host.__init__( self, name, **kwargs )
725 if not CCNHost.inited:
726 CCNHost.init()
727
728 self.cmd("export CCN_LOCAL_SOCKNAME=/tmp/.sock.ccnx.{0}".format(self.name))
729 self.cmd("ccndstart")
730 self.peerList = {}
731
732 def config( self, fib=None, app=None, **params ):
733
734 r = Node.config( self, **params )
735
736 self.setParam( r, 'app', fib=fib )
737 self.setParam( r, 'fib', app=app)
738
739 return r
740
741 def configCCN(self):
742
743 self.buildPeerIP()
744 self.setFIB()
745
746 def buildPeerIP(self):
747 for iface in self.intfList():
748 link = iface.link
749 if link:
750 node1, node2 = link.intf1.node, link.intf2.node
751 if node1 == self:
752 self.peerList[node2.name] = link.intf2.node.IP(link.intf2)
753 else:
754 self.peerList[node1.name] = link.intf1.node.IP(link.intf1)
755
756
757 def setFIB(self):
758
759 for name in self.params['fib']:
760 if not name:
761 pass
762 else:
763 self.insert_fib(name[0],self.peerList[name[1]])
764
765
766 def insert_fib(self, uri, host):
767 self.cmd('ccndc add {0} tcp {1}'.format(uri,host))
768
769 def terminate( self ):
770 "Stop node."
771 self.cmd('ccndstop')
772 Host.terminate(self)
773
774 inited = False
775
776
777 @classmethod
778 def init( cls ):
779 "Initialization for CCNHost class"
780 cls.inited = True
781
782class CPULimitedCCNHost( CPULimitedHost ):
783 '''CPULimitedCCNHost is a Host that always runs the ccnd daemon and extends CPULimitedHost.
784 It should be used when one wants to limit the resources of CCN routers and hosts '''
785
786
787 def __init__( self, name, sched='cfs', **kwargs ):
788
789 CPULimitedHost.__init__( self, name, sched, **kwargs )
790 if not CCNHost.inited:
791 CCNHost.init()
792
793 self.cmd("export CCN_LOCAL_SOCKNAME=/tmp/.sock.ccnx.{0}".format(self.name))
794 self.cmd("ccndstart")
795 self.peerList = {}
796
797 def config( self, fib=None, app=None, cpu=None, cores=None, **params):
798
799 r = CPULimitedHost.config(self,cpu,cores, **params)
800
carlosmscabralf40ecd12013-02-01 18:15:58 -0200801 self.setParam( r, 'app', fib=fib )
802 self.setParam( r, 'fib', app=app)
803
804 return r
805
806 def configCCN(self):
807
808 self.buildPeerIP()
809 self.setFIB()
810
811 def buildPeerIP(self):
812 for iface in self.intfList():
813 link = iface.link
814 if link:
815 node1, node2 = link.intf1.node, link.intf2.node
816 if node1 == self:
817 self.peerList[node2.name] = link.intf2.node.IP(link.intf2)
818 else:
819 self.peerList[node1.name] = link.intf1.node.IP(link.intf1)
820
821
822 def setFIB(self):
823
824 for name in self.params['fib']:
825 if not name:
826 pass
827 else:
828 self.insert_fib(name[0],self.peerList[name[1]])
829
830
831 def insert_fib(self, uri, host):
832 self.cmd('ccndc add {0} tcp {1}'.format(uri,host))
833
834 def terminate( self ):
835 "Stop node."
836 self.cmd('ccndstop')
837 Host.terminate(self)
838
839 inited = False
840
841
842 @classmethod
843 def init( cls ):
844 "Initialization for CCNHost class"
845 cls.inited = True
846
847# Some important things to note:
848#
849# The "IP" address which setIP() assigns to the switch is not
850# an "IP address for the switch" in the sense of IP routing.
851# Rather, it is the IP address for the control interface,
852# on the control network, and it is only relevant to the
853# controller. If you are running in the root namespace
854# (which is the only way to run OVS at the moment), the
855# control interface is the loopback interface, and you
856# normally never want to change its IP address!
857#
858# In general, you NEVER want to attempt to use Linux's
859# network stack (i.e. ifconfig) to "assign" an IP address or
860# MAC address to a switch data port. Instead, you "assign"
861# the IP and MAC addresses in the controller by specifying
862# packets that you want to receive or send. The "MAC" address
863# reported by ifconfig for a switch data port is essentially
864# meaningless. It is important to understand this if you
865# want to create a functional router using OpenFlow.
866
867class Switch( Node ):
868 """A Switch is a Node that is running (or has execed?)
869 an OpenFlow switch."""
870
871 portBase = 1 # Switches start with port 1 in OpenFlow
872 dpidLen = 16 # digits in dpid passed to switch
873
874 def __init__( self, name, dpid=None, opts='', listenPort=None, **params):
875 """dpid: dpid for switch (or None to derive from name, e.g. s1 -> 1)
876 opts: additional switch options
877 listenPort: port to listen on for dpctl connections"""
878 Node.__init__( self, name, **params )
879 self.dpid = dpid if dpid else self.defaultDpid()
880 self.opts = opts
881 self.listenPort = listenPort
882 if not self.inNamespace:
883 self.controlIntf = Intf( 'lo', self, port=0 )
884
885 def defaultDpid( self ):
886 "Derive dpid from switch name, s1 -> 1"
887 try:
888 dpid = int( re.findall( '\d+', self.name )[ 0 ] )
889 dpid = hex( dpid )[ 2: ]
890 dpid = '0' * ( self.dpidLen - len( dpid ) ) + dpid
891 return dpid
892 except IndexError:
893 raise Exception( 'Unable to derive default datapath ID - '
894 'please either specify a dpid or use a '
895 'canonical switch name such as s23.' )
896
897 def defaultIntf( self ):
898 "Return control interface"
899 if self.controlIntf:
900 return self.controlIntf
901 else:
902 return Node.defaultIntf( self )
903
904 def sendCmd( self, *cmd, **kwargs ):
905 """Send command to Node.
906 cmd: string"""
907 kwargs.setdefault( 'printPid', False )
908 if not self.execed:
909 return Node.sendCmd( self, *cmd, **kwargs )
910 else:
911 error( '*** Error: %s has execed and cannot accept commands' %
912 self.name )
913
914 def __repr__( self ):
915 "More informative string representation"
916 intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
917 for i in self.intfList() ] ) )
918 return '<%s %s: %s pid=%s> ' % (
919 self.__class__.__name__, self.name, intfs, self.pid )
920
921class UserSwitch( Switch ):
922 "User-space switch."
923
924 dpidLen = 12
925
926 def __init__( self, name, **kwargs ):
927 """Init.
928 name: name for the switch"""
929 Switch.__init__( self, name, **kwargs )
930 pathCheck( 'ofdatapath', 'ofprotocol',
931 moduleName='the OpenFlow reference user switch' +
932 '(openflow.org)' )
933 if self.listenPort:
934 self.opts += ' --listen=ptcp:%i ' % self.listenPort
935
936 @classmethod
937 def setup( cls ):
938 "Ensure any dependencies are loaded; if not, try to load them."
939 if not os.path.exists( '/dev/net/tun' ):
940 moduleDeps( add=TUN )
941
942 def dpctl( self, *args ):
943 "Run dpctl command"
944 if not self.listenPort:
945 return "can't run dpctl without passive listening port"
946 return self.cmd( 'dpctl ' + ' '.join( args ) +
947 ' tcp:127.0.0.1:%i' % self.listenPort )
948
949 def start( self, controllers ):
950 """Start OpenFlow reference user datapath.
951 Log to /tmp/sN-{ofd,ofp}.log.
952 controllers: list of controller objects"""
953 # Add controllers
954 clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
955 for c in controllers ] )
956 ofdlog = '/tmp/' + self.name + '-ofd.log'
957 ofplog = '/tmp/' + self.name + '-ofp.log'
958 self.cmd( 'ifconfig lo up' )
959 intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
960 self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
961 ' punix:/tmp/' + self.name + ' -d ' + self.dpid +
962 ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
963 self.cmd( 'ofprotocol unix:/tmp/' + self.name +
964 ' ' + clist +
965 ' --fail=closed ' + self.opts +
966 ' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
967
968 def stop( self ):
969 "Stop OpenFlow reference user datapath."
970 self.cmd( 'kill %ofdatapath' )
971 self.cmd( 'kill %ofprotocol' )
972 self.deleteIntfs()
973
974
975class OVSLegacyKernelSwitch( Switch ):
976 """Open VSwitch legacy kernel-space switch using ovs-openflowd.
977 Currently only works in the root namespace."""
978
979 def __init__( self, name, dp=None, **kwargs ):
980 """Init.
981 name: name for switch
982 dp: netlink id (0, 1, 2, ...)
983 defaultMAC: default MAC as unsigned int; random value if None"""
984 Switch.__init__( self, name, **kwargs )
985 self.dp = dp if dp else self.name
986 self.intf = self.dp
987 if self.inNamespace:
988 error( "OVSKernelSwitch currently only works"
989 " in the root namespace.\n" )
990 exit( 1 )
991
992 @classmethod
993 def setup( cls ):
994 "Ensure any dependencies are loaded; if not, try to load them."
995 pathCheck( 'ovs-dpctl', 'ovs-openflowd',
996 moduleName='Open vSwitch (openvswitch.org)')
997 moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
998
999 def start( self, controllers ):
1000 "Start up kernel datapath."
1001 ofplog = '/tmp/' + self.name + '-ofp.log'
1002 quietRun( 'ifconfig lo up' )
1003 # Delete local datapath if it exists;
1004 # then create a new one monitoring the given interfaces
1005 self.cmd( 'ovs-dpctl del-dp ' + self.dp )
1006 self.cmd( 'ovs-dpctl add-dp ' + self.dp )
1007 intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
1008 self.cmd( 'ovs-dpctl', 'add-if', self.dp, ' '.join( intfs ) )
1009 # Run protocol daemon
1010 clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
1011 for c in controllers ] )
1012 self.cmd( 'ovs-openflowd ' + self.dp +
1013 ' ' + clist +
1014 ' --fail=secure ' + self.opts +
1015 ' --datapath-id=' + self.dpid +
1016 ' 1>' + ofplog + ' 2>' + ofplog + '&' )
1017 self.execed = False
1018
1019 def stop( self ):
1020 "Terminate kernel datapath."
1021 quietRun( 'ovs-dpctl del-dp ' + self.dp )
1022 self.cmd( 'kill %ovs-openflowd' )
1023 self.deleteIntfs()
1024
1025
1026class OVSSwitch( Switch ):
1027 "Open vSwitch switch. Depends on ovs-vsctl."
1028
1029 def __init__( self, name, failMode='secure', **params ):
1030 """Init.
1031 name: name for switch
1032 failMode: controller loss behavior (secure|open)"""
1033 Switch.__init__( self, name, **params )
1034 self.failMode = failMode
1035
1036 @classmethod
1037 def setup( cls ):
1038 "Make sure Open vSwitch is installed and working"
1039 pathCheck( 'ovs-vsctl',
1040 moduleName='Open vSwitch (openvswitch.org)')
1041 # This should no longer be needed, and it breaks
1042 # with OVS 1.7 which has renamed the kernel module:
1043 # moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
1044 out, err, exitcode = errRun( 'ovs-vsctl -t 1 show' )
1045 if exitcode:
1046 error( out + err +
1047 'ovs-vsctl exited with code %d\n' % exitcode +
1048 '*** Error connecting to ovs-db with ovs-vsctl\n'
1049 'Make sure that Open vSwitch is installed, '
1050 'that ovsdb-server is running, and that\n'
1051 '"ovs-vsctl show" works correctly.\n'
1052 'You may wish to try '
1053 '"service openvswitch-switch start".\n' )
1054 exit( 1 )
1055
1056 def dpctl( self, *args ):
1057 "Run ovs-dpctl command"
1058 return self.cmd( 'ovs-dpctl', args[ 0 ], self, *args[ 1: ] )
1059
1060 @staticmethod
1061 def TCReapply( intf ):
1062 """Unfortunately OVS and Mininet are fighting
1063 over tc queuing disciplines. As a quick hack/
1064 workaround, we clear OVS's and reapply our own."""
1065 if type( intf ) is TCIntf:
1066 intf.config( **intf.params )
1067
1068 def attach( self, intf ):
1069 "Connect a data port"
1070 self.cmd( 'ovs-vsctl add-port', self, intf )
1071 self.cmd( 'ifconfig', intf, 'up' )
1072 self.TCReapply( intf )
1073
1074 def detach( self, intf ):
1075 "Disconnect a data port"
1076 self.cmd( 'ovs-vsctl del-port', self, intf )
1077
1078 def start( self, controllers ):
1079 "Start up a new OVS OpenFlow switch using ovs-vsctl"
1080 if self.inNamespace:
1081 raise Exception(
1082 'OVS kernel switch does not work in a namespace' )
1083 # We should probably call config instead, but this
1084 # requires some rethinking...
1085 self.cmd( 'ifconfig lo up' )
1086 # Annoyingly, --if-exists option seems not to work
1087 self.cmd( 'ovs-vsctl del-br', self )
1088 self.cmd( 'ovs-vsctl add-br', self )
1089 self.cmd( 'ovs-vsctl -- set Bridge', self,
1090 'other_config:datapath-id=' + self.dpid )
1091 self.cmd( 'ovs-vsctl set-fail-mode', self, self.failMode )
1092 for intf in self.intfList():
1093 if not intf.IP():
1094 self.attach( intf )
1095 # Add controllers
1096 clist = ' '.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
1097 for c in controllers ] )
1098 if self.listenPort:
1099 clist += ' ptcp:%s' % self.listenPort
1100 self.cmd( 'ovs-vsctl set-controller', self, clist )
1101
1102 def stop( self ):
1103 "Terminate OVS switch."
1104 self.cmd( 'ovs-vsctl del-br', self )
1105 self.deleteIntfs()
1106
1107OVSKernelSwitch = OVSSwitch
1108
1109
1110class Controller( Node ):
1111 """A Controller is a Node that is running (or has execed?) an
1112 OpenFlow controller."""
1113
1114 def __init__( self, name, inNamespace=False, command='controller',
1115 cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
1116 port=6633, **params ):
1117 self.command = command
1118 self.cargs = cargs
1119 self.cdir = cdir
1120 self.ip = ip
1121 self.port = port
1122 Node.__init__( self, name, inNamespace=inNamespace,
1123 ip=ip, **params )
1124 self.cmd( 'ifconfig lo up' ) # Shouldn't be necessary
1125 self.checkListening()
1126
1127 def checkListening( self ):
1128 "Make sure no controllers are running on our port"
1129 # Verify that Telnet is installed first:
1130 out, _err, returnCode = errRun( "which telnet" )
1131 if 'telnet' not in out or returnCode != 0:
1132 raise Exception( "Error running telnet to check for listening "
1133 "controllers; please check that it is "
1134 "installed." )
1135 listening = self.cmd( "echo A | telnet -e A %s %d" %
1136 ( self.ip, self.port ) )
1137 if 'Unable' not in listening:
1138 servers = self.cmd( 'netstat -atp' ).split( '\n' )
1139 pstr = ':%d ' % self.port
1140 clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ]
1141 raise Exception( "Please shut down the controller which is"
1142 " running on port %d:\n" % self.port +
1143 '\n'.join( clist ) )
1144
1145 def start( self ):
1146 """Start <controller> <args> on controller.
1147 Log to /tmp/cN.log"""
1148 pathCheck( self.command )
1149 cout = '/tmp/' + self.name + '.log'
1150 if self.cdir is not None:
1151 self.cmd( 'cd ' + self.cdir )
1152 self.cmd( self.command + ' ' + self.cargs % self.port +
1153 ' 1>' + cout + ' 2>' + cout + '&' )
1154 self.execed = False
1155
1156 def stop( self ):
1157 "Stop controller."
1158 self.cmd( 'kill %' + self.command )
1159 self.terminate()
1160
1161 def IP( self, intf=None ):
1162 "Return IP address of the Controller"
1163 if self.intfs:
1164 ip = Node.IP( self, intf )
1165 else:
1166 ip = self.ip
1167 return ip
1168
1169 def __repr__( self ):
1170 "More informative string representation"
1171 return '<%s %s: %s:%s pid=%s> ' % (
1172 self.__class__.__name__, self.name,
1173 self.IP(), self.port, self.pid )
1174
1175
1176class OVSController( Controller ):
1177 "Open vSwitch controller"
1178 def __init__( self, name, command='ovs-controller', **kwargs ):
1179 Controller.__init__( self, name, command=command, **kwargs )
1180
1181
1182class NOX( Controller ):
1183 "Controller to run a NOX application."
1184
1185 def __init__( self, name, *noxArgs, **kwargs ):
1186 """Init.
1187 name: name to give controller
1188 noxArgs: arguments (strings) to pass to NOX"""
1189 if not noxArgs:
1190 warn( 'warning: no NOX modules specified; '
1191 'running packetdump only\n' )
1192 noxArgs = [ 'packetdump' ]
1193 elif type( noxArgs ) not in ( list, tuple ):
1194 noxArgs = [ noxArgs ]
1195
1196 if 'NOX_CORE_DIR' not in os.environ:
1197 exit( 'exiting; please set missing NOX_CORE_DIR env var' )
1198 noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
1199
1200 Controller.__init__( self, name,
1201 command=noxCoreDir + '/nox_core',
1202 cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
1203 ' '.join( noxArgs ),
1204 cdir=noxCoreDir,
1205 **kwargs )
1206
1207
1208class RemoteController( Controller ):
1209 "Controller running outside of Mininet's control."
1210
1211 def __init__( self, name, ip='127.0.0.1',
1212 port=6633, **kwargs):
1213 """Init.
1214 name: name to give controller
1215 ip: the IP address where the remote controller is
1216 listening
1217 port: the port where the remote controller is listening"""
1218 Controller.__init__( self, name, ip=ip, port=port, **kwargs )
1219
1220 def start( self ):
1221 "Overridden to do nothing."
1222 return
1223
1224 def stop( self ):
1225 "Overridden to do nothing."
1226 return
1227
1228 def checkListening( self ):
1229 "Warn if remote controller is not accessible"
1230 listening = self.cmd( "echo A | telnet -e A %s %d" %
1231 ( self.ip, self.port ) )
1232 if 'Unable' in listening:
1233 warn( "Unable to contact the remote controller"
1234 " at %s:%d\n" % ( self.ip, self.port ) )