blob: 281839a0703646777fb6208fdee3f87faf081482 [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
carlosmscabral6d3dd602013-03-23 11:12:34 -0300556
carlosmscabralf40ecd12013-02-01 18:15:58 -0200557class 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 )
carlosmscabral6d3dd602013-03-23 11:12:34 -0300595
carlosmscabrale121a7b2013-02-18 18:14:53 -0300596 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
carlosmscabral6d3dd602013-03-23 11:12:34 -0300724
carlosmscabralf40ecd12013-02-01 18:15:58 -0200725 Host.__init__( self, name, **kwargs )
726 if not CCNHost.inited:
727 CCNHost.init()
carlosmscabral6d3dd602013-03-23 11:12:34 -0300728
carlosmscabralb9d85b72013-07-15 15:24:33 -0300729 self.cmd("export CCND_DEBUG=6")
730 self.cmd("export CCND_LOG=./log.{0}".format(self.name))
731# print self.params['cache']
732 if self.params['cache'] != None:
733 self.cmd("export CCND_CAP={0}".format(self.params['cache']))
734
carlosmscabralf40ecd12013-02-01 18:15:58 -0200735 self.cmd("export CCN_LOCAL_SOCKNAME=/tmp/.sock.ccnx.{0}".format(self.name))
736 self.cmd("ccndstart")
737 self.peerList = {}
738
carlosmscabralb9d85b72013-07-15 15:24:33 -0300739 def config( self, fib=None, app=None, cache=None, **params ):
carlosmscabral6d3dd602013-03-23 11:12:34 -0300740
carlosmscabralf40ecd12013-02-01 18:15:58 -0200741 r = Node.config( self, **params )
carlosmscabral6d3dd602013-03-23 11:12:34 -0300742
carlosmscabralf40ecd12013-02-01 18:15:58 -0200743 self.setParam( r, 'app', fib=fib )
744 self.setParam( r, 'fib', app=app)
carlosmscabralb9d85b72013-07-15 15:24:33 -0300745 self.setParam( r, 'cache', cache=cache )
carlosmscabral6d3dd602013-03-23 11:12:34 -0300746
carlosmscabralf40ecd12013-02-01 18:15:58 -0200747 return r
carlosmscabral6d3dd602013-03-23 11:12:34 -0300748
carlosmscabralf40ecd12013-02-01 18:15:58 -0200749 def configCCN(self):
carlosmscabral6d3dd602013-03-23 11:12:34 -0300750
carlosmscabralf40ecd12013-02-01 18:15:58 -0200751 self.buildPeerIP()
752 self.setFIB()
carlosmscabral6d3dd602013-03-23 11:12:34 -0300753
carlosmscabralf40ecd12013-02-01 18:15:58 -0200754 def buildPeerIP(self):
755 for iface in self.intfList():
756 link = iface.link
757 if link:
758 node1, node2 = link.intf1.node, link.intf2.node
759 if node1 == self:
760 self.peerList[node2.name] = link.intf2.node.IP(link.intf2)
761 else:
762 self.peerList[node1.name] = link.intf1.node.IP(link.intf1)
carlosmscabral6d3dd602013-03-23 11:12:34 -0300763
764
carlosmscabralf40ecd12013-02-01 18:15:58 -0200765 def setFIB(self):
766
767 for name in self.params['fib']:
768 if not name:
769 pass
770 else:
771 self.insert_fib(name[0],self.peerList[name[1]])
carlosmscabral6d3dd602013-03-23 11:12:34 -0300772
773
carlosmscabralf40ecd12013-02-01 18:15:58 -0200774 def insert_fib(self, uri, host):
775 self.cmd('ccndc add {0} tcp {1}'.format(uri,host))
carlosmscabral6d3dd602013-03-23 11:12:34 -0300776 # self.cmd('ccndc add {0} udp {1}'.format(uri,host))
777
carlosmscabralf40ecd12013-02-01 18:15:58 -0200778 def terminate( self ):
779 "Stop node."
780 self.cmd('ccndstop')
carlosmscabral6d3dd602013-03-23 11:12:34 -0300781 self.cmd('killall -r zebra ospf')
carlosmscabralf40ecd12013-02-01 18:15:58 -0200782 Host.terminate(self)
carlosmscabral6d3dd602013-03-23 11:12:34 -0300783
carlosmscabralf40ecd12013-02-01 18:15:58 -0200784 inited = False
785
carlosmscabral6d3dd602013-03-23 11:12:34 -0300786
carlosmscabralf40ecd12013-02-01 18:15:58 -0200787 @classmethod
788 def init( cls ):
789 "Initialization for CCNHost class"
790 cls.inited = True
carlosmscabral6d3dd602013-03-23 11:12:34 -0300791
carlosmscabralf40ecd12013-02-01 18:15:58 -0200792class CPULimitedCCNHost( CPULimitedHost ):
793 '''CPULimitedCCNHost is a Host that always runs the ccnd daemon and extends CPULimitedHost.
794 It should be used when one wants to limit the resources of CCN routers and hosts '''
carlosmscabral6d3dd602013-03-23 11:12:34 -0300795
carlosmscabralf40ecd12013-02-01 18:15:58 -0200796
797 def __init__( self, name, sched='cfs', **kwargs ):
carlosmscabral6d3dd602013-03-23 11:12:34 -0300798
carlosmscabralf40ecd12013-02-01 18:15:58 -0200799 CPULimitedHost.__init__( self, name, sched, **kwargs )
800 if not CCNHost.inited:
801 CCNHost.init()
carlosmscabral6d3dd602013-03-23 11:12:34 -0300802
carlosmscabralb9d85b72013-07-15 15:24:33 -0300803 self.cmd("export CCND_DEBUG=6")
804 self.cmd("export CCND_LOG=./log.{0}".format(self.name))
805 if self.params['cache'] != None:
806 self.cmd("export CCND_CAP={0}".format(self.params['cache']))
807
carlosmscabral6d3dd602013-03-23 11:12:34 -0300808 self.cmd("export CCN_LOCAL_SOCKNAME=/tmp/.sock.ccnx.{0}".format(self.name))
carlosmscabralf40ecd12013-02-01 18:15:58 -0200809 self.cmd("ccndstart")
810 self.peerList = {}
811
carlosmscabralb9d85b72013-07-15 15:24:33 -0300812 def config( self, fib=None, app=None, cpu=None, cores=None, cache=None, **params):
carlosmscabralf40ecd12013-02-01 18:15:58 -0200813
814 r = CPULimitedHost.config(self,cpu,cores, **params)
carlosmscabral6d3dd602013-03-23 11:12:34 -0300815
carlosmscabralf40ecd12013-02-01 18:15:58 -0200816 self.setParam( r, 'app', fib=fib )
817 self.setParam( r, 'fib', app=app)
carlosmscabralb9d85b72013-07-15 15:24:33 -0300818 self.setParam( r, 'cache', cache=cache)
carlosmscabral6d3dd602013-03-23 11:12:34 -0300819
carlosmscabralf40ecd12013-02-01 18:15:58 -0200820 return r
carlosmscabral6d3dd602013-03-23 11:12:34 -0300821
carlosmscabralf40ecd12013-02-01 18:15:58 -0200822 def configCCN(self):
carlosmscabral6d3dd602013-03-23 11:12:34 -0300823
carlosmscabralf40ecd12013-02-01 18:15:58 -0200824 self.buildPeerIP()
825 self.setFIB()
carlosmscabral6d3dd602013-03-23 11:12:34 -0300826
carlosmscabralf40ecd12013-02-01 18:15:58 -0200827 def buildPeerIP(self):
828 for iface in self.intfList():
829 link = iface.link
830 if link:
831 node1, node2 = link.intf1.node, link.intf2.node
832 if node1 == self:
833 self.peerList[node2.name] = link.intf2.node.IP(link.intf2)
834 else:
835 self.peerList[node1.name] = link.intf1.node.IP(link.intf1)
carlosmscabral6d3dd602013-03-23 11:12:34 -0300836
837
carlosmscabralf40ecd12013-02-01 18:15:58 -0200838 def setFIB(self):
839
840 for name in self.params['fib']:
841 if not name:
842 pass
843 else:
844 self.insert_fib(name[0],self.peerList[name[1]])
carlosmscabral6d3dd602013-03-23 11:12:34 -0300845
846
carlosmscabralf40ecd12013-02-01 18:15:58 -0200847 def insert_fib(self, uri, host):
848 self.cmd('ccndc add {0} tcp {1}'.format(uri,host))
carlosmscabral6d3dd602013-03-23 11:12:34 -0300849# self.cmd('ccndc add {0} udp {1}'.format(uri,host))
850
carlosmscabralf40ecd12013-02-01 18:15:58 -0200851 def terminate( self ):
852 "Stop node."
853 self.cmd('ccndstop')
carlosmscabral6d3dd602013-03-23 11:12:34 -0300854 self.cmd('killall -r zebra ospf')
carlosmscabralf40ecd12013-02-01 18:15:58 -0200855 Host.terminate(self)
carlosmscabral6d3dd602013-03-23 11:12:34 -0300856
carlosmscabralf40ecd12013-02-01 18:15:58 -0200857 inited = False
858
carlosmscabral6d3dd602013-03-23 11:12:34 -0300859
carlosmscabralf40ecd12013-02-01 18:15:58 -0200860 @classmethod
861 def init( cls ):
862 "Initialization for CCNHost class"
863 cls.inited = True
864
865# Some important things to note:
866#
867# The "IP" address which setIP() assigns to the switch is not
868# an "IP address for the switch" in the sense of IP routing.
869# Rather, it is the IP address for the control interface,
870# on the control network, and it is only relevant to the
871# controller. If you are running in the root namespace
872# (which is the only way to run OVS at the moment), the
873# control interface is the loopback interface, and you
874# normally never want to change its IP address!
875#
876# In general, you NEVER want to attempt to use Linux's
877# network stack (i.e. ifconfig) to "assign" an IP address or
878# MAC address to a switch data port. Instead, you "assign"
879# the IP and MAC addresses in the controller by specifying
880# packets that you want to receive or send. The "MAC" address
881# reported by ifconfig for a switch data port is essentially
882# meaningless. It is important to understand this if you
883# want to create a functional router using OpenFlow.
884
885class Switch( Node ):
886 """A Switch is a Node that is running (or has execed?)
887 an OpenFlow switch."""
888
889 portBase = 1 # Switches start with port 1 in OpenFlow
890 dpidLen = 16 # digits in dpid passed to switch
891
892 def __init__( self, name, dpid=None, opts='', listenPort=None, **params):
893 """dpid: dpid for switch (or None to derive from name, e.g. s1 -> 1)
894 opts: additional switch options
895 listenPort: port to listen on for dpctl connections"""
896 Node.__init__( self, name, **params )
897 self.dpid = dpid if dpid else self.defaultDpid()
898 self.opts = opts
899 self.listenPort = listenPort
900 if not self.inNamespace:
901 self.controlIntf = Intf( 'lo', self, port=0 )
902
903 def defaultDpid( self ):
904 "Derive dpid from switch name, s1 -> 1"
905 try:
906 dpid = int( re.findall( '\d+', self.name )[ 0 ] )
907 dpid = hex( dpid )[ 2: ]
908 dpid = '0' * ( self.dpidLen - len( dpid ) ) + dpid
909 return dpid
910 except IndexError:
911 raise Exception( 'Unable to derive default datapath ID - '
912 'please either specify a dpid or use a '
913 'canonical switch name such as s23.' )
914
915 def defaultIntf( self ):
916 "Return control interface"
917 if self.controlIntf:
918 return self.controlIntf
919 else:
920 return Node.defaultIntf( self )
921
922 def sendCmd( self, *cmd, **kwargs ):
923 """Send command to Node.
924 cmd: string"""
925 kwargs.setdefault( 'printPid', False )
926 if not self.execed:
927 return Node.sendCmd( self, *cmd, **kwargs )
928 else:
929 error( '*** Error: %s has execed and cannot accept commands' %
930 self.name )
931
932 def __repr__( self ):
933 "More informative string representation"
934 intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
935 for i in self.intfList() ] ) )
936 return '<%s %s: %s pid=%s> ' % (
937 self.__class__.__name__, self.name, intfs, self.pid )
938
939class UserSwitch( Switch ):
940 "User-space switch."
941
942 dpidLen = 12
943
944 def __init__( self, name, **kwargs ):
945 """Init.
946 name: name for the switch"""
947 Switch.__init__( self, name, **kwargs )
948 pathCheck( 'ofdatapath', 'ofprotocol',
949 moduleName='the OpenFlow reference user switch' +
950 '(openflow.org)' )
951 if self.listenPort:
952 self.opts += ' --listen=ptcp:%i ' % self.listenPort
953
954 @classmethod
955 def setup( cls ):
956 "Ensure any dependencies are loaded; if not, try to load them."
957 if not os.path.exists( '/dev/net/tun' ):
958 moduleDeps( add=TUN )
959
960 def dpctl( self, *args ):
961 "Run dpctl command"
962 if not self.listenPort:
963 return "can't run dpctl without passive listening port"
964 return self.cmd( 'dpctl ' + ' '.join( args ) +
965 ' tcp:127.0.0.1:%i' % self.listenPort )
966
967 def start( self, controllers ):
968 """Start OpenFlow reference user datapath.
969 Log to /tmp/sN-{ofd,ofp}.log.
970 controllers: list of controller objects"""
971 # Add controllers
972 clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
973 for c in controllers ] )
974 ofdlog = '/tmp/' + self.name + '-ofd.log'
975 ofplog = '/tmp/' + self.name + '-ofp.log'
976 self.cmd( 'ifconfig lo up' )
977 intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
978 self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
979 ' punix:/tmp/' + self.name + ' -d ' + self.dpid +
980 ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
981 self.cmd( 'ofprotocol unix:/tmp/' + self.name +
982 ' ' + clist +
983 ' --fail=closed ' + self.opts +
984 ' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
985
986 def stop( self ):
987 "Stop OpenFlow reference user datapath."
988 self.cmd( 'kill %ofdatapath' )
989 self.cmd( 'kill %ofprotocol' )
990 self.deleteIntfs()
991
992
993class OVSLegacyKernelSwitch( Switch ):
994 """Open VSwitch legacy kernel-space switch using ovs-openflowd.
995 Currently only works in the root namespace."""
996
997 def __init__( self, name, dp=None, **kwargs ):
998 """Init.
999 name: name for switch
1000 dp: netlink id (0, 1, 2, ...)
1001 defaultMAC: default MAC as unsigned int; random value if None"""
1002 Switch.__init__( self, name, **kwargs )
1003 self.dp = dp if dp else self.name
1004 self.intf = self.dp
1005 if self.inNamespace:
1006 error( "OVSKernelSwitch currently only works"
1007 " in the root namespace.\n" )
1008 exit( 1 )
1009
1010 @classmethod
1011 def setup( cls ):
1012 "Ensure any dependencies are loaded; if not, try to load them."
1013 pathCheck( 'ovs-dpctl', 'ovs-openflowd',
1014 moduleName='Open vSwitch (openvswitch.org)')
1015 moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
1016
1017 def start( self, controllers ):
1018 "Start up kernel datapath."
1019 ofplog = '/tmp/' + self.name + '-ofp.log'
1020 quietRun( 'ifconfig lo up' )
1021 # Delete local datapath if it exists;
1022 # then create a new one monitoring the given interfaces
1023 self.cmd( 'ovs-dpctl del-dp ' + self.dp )
1024 self.cmd( 'ovs-dpctl add-dp ' + self.dp )
1025 intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
1026 self.cmd( 'ovs-dpctl', 'add-if', self.dp, ' '.join( intfs ) )
1027 # Run protocol daemon
1028 clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
1029 for c in controllers ] )
1030 self.cmd( 'ovs-openflowd ' + self.dp +
1031 ' ' + clist +
1032 ' --fail=secure ' + self.opts +
1033 ' --datapath-id=' + self.dpid +
1034 ' 1>' + ofplog + ' 2>' + ofplog + '&' )
1035 self.execed = False
1036
1037 def stop( self ):
1038 "Terminate kernel datapath."
1039 quietRun( 'ovs-dpctl del-dp ' + self.dp )
1040 self.cmd( 'kill %ovs-openflowd' )
1041 self.deleteIntfs()
1042
1043
1044class OVSSwitch( Switch ):
1045 "Open vSwitch switch. Depends on ovs-vsctl."
1046
1047 def __init__( self, name, failMode='secure', **params ):
1048 """Init.
1049 name: name for switch
1050 failMode: controller loss behavior (secure|open)"""
1051 Switch.__init__( self, name, **params )
1052 self.failMode = failMode
1053
1054 @classmethod
1055 def setup( cls ):
1056 "Make sure Open vSwitch is installed and working"
1057 pathCheck( 'ovs-vsctl',
1058 moduleName='Open vSwitch (openvswitch.org)')
1059 # This should no longer be needed, and it breaks
1060 # with OVS 1.7 which has renamed the kernel module:
1061 # moduleDeps( subtract=OF_KMOD, add=OVS_KMOD )
1062 out, err, exitcode = errRun( 'ovs-vsctl -t 1 show' )
1063 if exitcode:
1064 error( out + err +
1065 'ovs-vsctl exited with code %d\n' % exitcode +
1066 '*** Error connecting to ovs-db with ovs-vsctl\n'
1067 'Make sure that Open vSwitch is installed, '
1068 'that ovsdb-server is running, and that\n'
1069 '"ovs-vsctl show" works correctly.\n'
1070 'You may wish to try '
1071 '"service openvswitch-switch start".\n' )
1072 exit( 1 )
1073
1074 def dpctl( self, *args ):
1075 "Run ovs-dpctl command"
1076 return self.cmd( 'ovs-dpctl', args[ 0 ], self, *args[ 1: ] )
1077
1078 @staticmethod
1079 def TCReapply( intf ):
1080 """Unfortunately OVS and Mininet are fighting
1081 over tc queuing disciplines. As a quick hack/
1082 workaround, we clear OVS's and reapply our own."""
1083 if type( intf ) is TCIntf:
1084 intf.config( **intf.params )
1085
1086 def attach( self, intf ):
1087 "Connect a data port"
1088 self.cmd( 'ovs-vsctl add-port', self, intf )
1089 self.cmd( 'ifconfig', intf, 'up' )
1090 self.TCReapply( intf )
1091
1092 def detach( self, intf ):
1093 "Disconnect a data port"
1094 self.cmd( 'ovs-vsctl del-port', self, intf )
1095
1096 def start( self, controllers ):
1097 "Start up a new OVS OpenFlow switch using ovs-vsctl"
1098 if self.inNamespace:
1099 raise Exception(
1100 'OVS kernel switch does not work in a namespace' )
1101 # We should probably call config instead, but this
1102 # requires some rethinking...
1103 self.cmd( 'ifconfig lo up' )
1104 # Annoyingly, --if-exists option seems not to work
1105 self.cmd( 'ovs-vsctl del-br', self )
1106 self.cmd( 'ovs-vsctl add-br', self )
1107 self.cmd( 'ovs-vsctl -- set Bridge', self,
1108 'other_config:datapath-id=' + self.dpid )
1109 self.cmd( 'ovs-vsctl set-fail-mode', self, self.failMode )
1110 for intf in self.intfList():
1111 if not intf.IP():
1112 self.attach( intf )
1113 # Add controllers
1114 clist = ' '.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
1115 for c in controllers ] )
1116 if self.listenPort:
1117 clist += ' ptcp:%s' % self.listenPort
1118 self.cmd( 'ovs-vsctl set-controller', self, clist )
1119
1120 def stop( self ):
1121 "Terminate OVS switch."
1122 self.cmd( 'ovs-vsctl del-br', self )
1123 self.deleteIntfs()
1124
1125OVSKernelSwitch = OVSSwitch
1126
1127
1128class Controller( Node ):
1129 """A Controller is a Node that is running (or has execed?) an
1130 OpenFlow controller."""
1131
1132 def __init__( self, name, inNamespace=False, command='controller',
1133 cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
1134 port=6633, **params ):
1135 self.command = command
1136 self.cargs = cargs
1137 self.cdir = cdir
1138 self.ip = ip
1139 self.port = port
1140 Node.__init__( self, name, inNamespace=inNamespace,
1141 ip=ip, **params )
1142 self.cmd( 'ifconfig lo up' ) # Shouldn't be necessary
1143 self.checkListening()
1144
1145 def checkListening( self ):
1146 "Make sure no controllers are running on our port"
1147 # Verify that Telnet is installed first:
1148 out, _err, returnCode = errRun( "which telnet" )
1149 if 'telnet' not in out or returnCode != 0:
1150 raise Exception( "Error running telnet to check for listening "
1151 "controllers; please check that it is "
1152 "installed." )
1153 listening = self.cmd( "echo A | telnet -e A %s %d" %
1154 ( self.ip, self.port ) )
1155 if 'Unable' not in listening:
1156 servers = self.cmd( 'netstat -atp' ).split( '\n' )
1157 pstr = ':%d ' % self.port
1158 clist = servers[ 0:1 ] + [ s for s in servers if pstr in s ]
1159 raise Exception( "Please shut down the controller which is"
1160 " running on port %d:\n" % self.port +
1161 '\n'.join( clist ) )
1162
1163 def start( self ):
1164 """Start <controller> <args> on controller.
1165 Log to /tmp/cN.log"""
1166 pathCheck( self.command )
1167 cout = '/tmp/' + self.name + '.log'
1168 if self.cdir is not None:
1169 self.cmd( 'cd ' + self.cdir )
1170 self.cmd( self.command + ' ' + self.cargs % self.port +
1171 ' 1>' + cout + ' 2>' + cout + '&' )
1172 self.execed = False
1173
1174 def stop( self ):
1175 "Stop controller."
1176 self.cmd( 'kill %' + self.command )
1177 self.terminate()
1178
1179 def IP( self, intf=None ):
1180 "Return IP address of the Controller"
1181 if self.intfs:
1182 ip = Node.IP( self, intf )
1183 else:
1184 ip = self.ip
1185 return ip
1186
1187 def __repr__( self ):
1188 "More informative string representation"
1189 return '<%s %s: %s:%s pid=%s> ' % (
1190 self.__class__.__name__, self.name,
1191 self.IP(), self.port, self.pid )
1192
1193
1194class OVSController( Controller ):
1195 "Open vSwitch controller"
1196 def __init__( self, name, command='ovs-controller', **kwargs ):
1197 Controller.__init__( self, name, command=command, **kwargs )
1198
1199
1200class NOX( Controller ):
1201 "Controller to run a NOX application."
1202
1203 def __init__( self, name, *noxArgs, **kwargs ):
1204 """Init.
1205 name: name to give controller
1206 noxArgs: arguments (strings) to pass to NOX"""
1207 if not noxArgs:
1208 warn( 'warning: no NOX modules specified; '
1209 'running packetdump only\n' )
1210 noxArgs = [ 'packetdump' ]
1211 elif type( noxArgs ) not in ( list, tuple ):
1212 noxArgs = [ noxArgs ]
1213
1214 if 'NOX_CORE_DIR' not in os.environ:
1215 exit( 'exiting; please set missing NOX_CORE_DIR env var' )
1216 noxCoreDir = os.environ[ 'NOX_CORE_DIR' ]
1217
1218 Controller.__init__( self, name,
1219 command=noxCoreDir + '/nox_core',
1220 cargs='--libdir=/usr/local/lib -v -i ptcp:%s ' +
1221 ' '.join( noxArgs ),
1222 cdir=noxCoreDir,
1223 **kwargs )
1224
1225
1226class RemoteController( Controller ):
1227 "Controller running outside of Mininet's control."
1228
1229 def __init__( self, name, ip='127.0.0.1',
1230 port=6633, **kwargs):
1231 """Init.
1232 name: name to give controller
1233 ip: the IP address where the remote controller is
1234 listening
1235 port: the port where the remote controller is listening"""
1236 Controller.__init__( self, name, ip=ip, port=port, **kwargs )
1237
1238 def start( self ):
1239 "Overridden to do nothing."
1240 return
1241
1242 def stop( self ):
1243 "Overridden to do nothing."
1244 return
1245
1246 def checkListening( self ):
1247 "Warn if remote controller is not accessible"
1248 listening = self.cmd( "echo A | telnet -e A %s %d" %
1249 ( self.ip, self.port ) )
1250 if 'Unable' in listening:
1251 warn( "Unable to contact the remote controller"
1252 " at %s:%d\n" % ( self.ip, self.port ) )