2 A simple command-line interface for Mininet.
4 The Mininet CLI provides a simple control console which
5 makes it easy to talk to nodes. For example, the command
9 runs 'ifconfig' on host h27.
11 Having a single console rather than, for example, an xterm for each
12 node is particularly convenient for networks of any reasonable
15 The CLI automatically substitutes IP addresses for node names,
20 should work correctly and allow host h2 to ping host h3
22 Several useful commands are provided, including the ability to
23 list all nodes ('nodes'), to print out the network topology
24 ('net') and to check connectivity ('pingall', 'pingpair')
25 and bandwidth ('iperf'.)
28 from subprocess import call
31 from select import poll, POLLIN
39 from mininet.log import info, output, error
40 from mininet.term import makeTerms, runX11
41 from mininet.util import ( quietRun, dumpNodeConnections,
45 "Simple command-line interface to talk to nodes."
49 def __init__( self, mininet, stdin=sys.stdin, script=None ):
50 """Start and run interactive or batch mode CLI
51 mininet: Mininet network object
52 stdin: standard input for CLI
53 script: script to run in batch mode"""
55 # Local variable bindings for py command
56 self.locals = { 'net': mininet }
57 # Attempt to handle input
59 self.inPoller = poll()
60 self.inPoller.register( stdin )
61 self.inputFile = script
63 info( '*** Starting CLI:\n' )
66 self.do_source( self.inputFile )
72 readlineInited = False
75 def initReadline( cls ):
76 "Set up history if readline is available"
77 # Only set up readline once to prevent multiplying the history file
78 if cls.readlineInited:
80 cls.readlineInited = True
82 from readline import ( read_history_file, write_history_file,
87 history_path = os.path.expanduser( '~/.mininet_history' )
88 if os.path.isfile( history_path ):
89 read_history_file( history_path )
90 set_history_length( 1000 )
91 atexit.register( lambda: write_history_file( history_path ) )
94 "Run our cmdloop(), catching KeyboardInterrupt"
95 info( '*** CLI Started:\n' )
98 # Make sure no nodes are still waiting
99 for node in self.mn.values():
101 info( 'stopping', node, '\n' )
105 quietRun( 'stty echo sane intr ^C' )
108 except KeyboardInterrupt:
109 # Output a message - unless it's also interrupted
110 # pylint: disable=broad-except
112 output( '\nInterrupt\n' )
115 # pylint: enable=broad-except
117 def emptyline( self ):
118 "Don't repeat last command when you hit return."
121 def getLocals( self ):
122 "Local variable bindings for py command"
123 self.locals.update( self.mn )
127 'You may also send a command to a node using:\n'
128 ' <node> command {args}\n'
130 ' mininet> h1 ifconfig\n'
132 'The interpreter automatically substitutes IP addresses\n'
133 'for node names when a node is the first arg, so commands\n'
135 ' mininet> h2 ping h3\n'
138 'Some character-oriented interactive commands require\n'
140 ' mininet> noecho h2 vi foo.py\n'
141 'However, starting up an xterm/gterm is generally better:\n'
142 ' mininet> xterm h2\n\n'
145 def do_help( self, line ):
146 "Describe available CLI commands."
147 Cmd.do_help( self, line )
149 output( self.helpStr )
151 def do_nodes( self, _line ):
153 nodes = ' '.join( sorted( self.mn ) )
154 output( 'available nodes are: \n%s\n' % nodes )
156 def do_ports( self, _line ):
157 "display ports and interfaces for each switch"
158 dumpPorts( self.mn.switches )
160 def do_net( self, _line ):
161 "List network connections."
162 dumpNodeConnections( self.mn.values() )
164 def do_sh( self, line ):
165 """Run an external shell command
166 Usage: sh [cmd args]"""
167 assert self # satisfy pylint and allow override
168 call( line, shell=True )
170 # do_py() and do_px() need to catch any exception during eval()/exec()
171 # pylint: disable=broad-except
173 def do_py( self, line ):
174 """Evaluate a Python expression.
175 Node names may be used, e.g.: py h1.cmd('ls')"""
177 result = eval( line, globals(), self.getLocals() )
180 elif isinstance( result, str ):
181 output( result + '\n' )
183 output( repr( result ) + '\n' )
184 except Exception as e:
185 output( str( e ) + '\n' )
187 # We are in fact using the exec() pseudo-function
188 # pylint: disable=exec-used
190 def do_px( self, line ):
191 """Execute a Python statement.
192 Node names may be used, e.g.: px print h1.cmd('ls')"""
194 exec( line, globals(), self.getLocals() )
195 except Exception as e:
196 output( str( e ) + '\n' )
198 # pylint: enable=broad-except,exec-used
200 def do_pingall( self, line ):
201 "Ping between all hosts."
202 self.mn.pingAll( line )
204 def do_pingpair( self, _line ):
205 "Ping between first two hosts, useful for testing."
208 def do_pingallfull( self, _line ):
209 "Ping between all hosts, returns all ping results."
210 self.mn.pingAllFull()
212 def do_pingpairfull( self, _line ):
213 "Ping between first two hosts, returns all ping results."
214 self.mn.pingPairFull()
216 def do_iperf( self, line ):
217 """Simple iperf TCP test between two (optionally specified) hosts.
218 Usage: iperf node1 node2"""
226 if arg not in self.mn:
228 error( "node '%s' not in network\n" % arg )
230 hosts.append( self.mn[ arg ] )
232 self.mn.iperf( hosts )
234 error( 'invalid number of args: iperf src dst\n' )
236 def do_iperfudp( self, line ):
237 """Simple iperf UDP test between two (optionally specified) hosts.
238 Usage: iperfudp bw node1 node2"""
241 self.mn.iperf( l4Type='UDP' )
246 for arg in args[ 1:3 ]:
247 if arg not in self.mn:
249 error( "node '%s' not in network\n" % arg )
251 hosts.append( self.mn[ arg ] )
253 self.mn.iperf( hosts, l4Type='UDP', udpBw=udpBw )
255 error( 'invalid number of args: iperfudp bw src dst\n' +
256 'bw examples: 10M\n' )
258 def do_intfs( self, _line ):
260 for node in self.mn.values():
262 ( node.name, ','.join( node.intfNames() ) ) )
264 def do_dump( self, _line ):
266 for node in self.mn.values():
267 output( '%s\n' % repr( node ) )
269 def do_link( self, line ):
270 """Bring link(s) between two nodes up or down.
271 Usage: link node1 node2 [up/down]"""
274 error( 'invalid number of args: link end1 end2 [up down]\n' )
275 elif args[ 2 ] not in [ 'up', 'down' ]:
276 error( 'invalid type: link end1 end2 [up down]\n' )
278 self.mn.configLinkStatus( *args )
280 def do_xterm( self, line, term='xterm' ):
281 """Spawn xterm(s) for the given node(s).
282 Usage: xterm node1 node2 ..."""
285 error( 'usage: %s node1 node2 ...\n' % term )
288 if arg not in self.mn:
289 error( "node '%s' not in network\n" % arg )
291 node = self.mn[ arg ]
292 self.mn.terms += makeTerms( [ node ], term = term )
294 def do_x( self, line ):
295 """Create an X11 tunnel to the given node,
296 optionally starting a client.
297 Usage: x node [cmd args]"""
300 error( 'usage: x node [cmd args]...\n' )
302 node = self.mn[ args[ 0 ] ]
304 self.mn.terms += runX11( node, cmd )
306 def do_gterm( self, line ):
307 """Spawn gnome-terminal(s) for the given node(s).
308 Usage: gterm node1 node2 ..."""
309 self.do_xterm( line, term='gterm' )
311 def do_exit( self, _line ):
313 assert self # satisfy pylint and allow override
314 return 'exited by user command'
316 def do_quit( self, line ):
318 return self.do_exit( line )
320 def do_EOF( self, line ):
323 return self.do_exit( line )
326 "Is our standard input a tty?"
327 return isatty( self.stdin.fileno() )
329 def do_noecho( self, line ):
330 """Run an interactive command with echoing turned off.
331 Usage: noecho [cmd args]"""
333 quietRun( 'stty -echo' )
336 quietRun( 'stty echo' )
338 def do_source( self, line ):
339 """Read commands from an input file.
340 Usage: source <file>"""
343 error( 'usage: source <file>\n' )
346 self.inputFile = open( args[ 0 ] )
348 line = self.inputFile.readline()
354 error( 'error reading file %s\n' % args[ 0 ] )
355 self.inputFile.close()
356 self.inputFile = None
358 def do_dpctl( self, line ):
359 """Run dpctl (or ovs-ofctl) command on all switches.
360 Usage: dpctl command [arg1] [arg2] ..."""
363 error( 'usage: dpctl command [arg1] [arg2] ...\n' )
365 for sw in self.mn.switches:
366 output( '*** ' + sw.name + ' ' + ('-' * 72) + '\n' )
367 output( sw.dpctl( *args ) )
369 def do_time( self, line ):
370 "Measure time taken for any command in Mininet."
373 elapsed = time.time() - start
374 self.stdout.write("*** Elapsed time: %0.6f secs\n" % elapsed)
376 def do_links( self, _line ):
378 for link in self.mn.links:
379 output( link, link.status(), '\n' )
381 def do_switch( self, line ):
382 "Starts or stops a switch"
385 error( 'invalid number of args: switch <switch name>'
390 if sw not in self.mn or self.mn.get( sw ) not in self.mn.switches:
391 error( 'invalid switch: %s\n' % args[ 1 ] )
395 if command == 'start':
396 self.mn.get( sw ).start( self.mn.controllers )
397 elif command == 'stop':
398 self.mn.get( sw ).stop( deleteIntfs=False )
400 error( 'invalid command: '
401 'switch <switch name> {start, stop}\n' )
403 def default( self, line ):
404 """Called on an input line when the command prefix is not recognized.
405 Overridden to run shell commands when a node is the first
406 CLI argument. Past the first CLI argument, node names are
407 automatically replaced with corresponding IP addrs."""
409 first, args, line = self.parseline( line )
413 error( '*** Please enter a command for node: %s <cmd>\n'
416 node = self.mn[ first ]
417 rest = args.split( ' ' )
418 # Substitute IP addresses for node names in command
419 # If updateIP() returns None, then use node name
420 rest = [ self.mn[ arg ].defaultIntf().updateIP() or arg
421 if arg in self.mn else arg
423 rest = ' '.join( rest )
426 self.waitForNode( node )
428 error( '*** Unknown command: %s\n' % line )
430 def waitForNode( self, node ):
431 "Wait for a node to finish, and print its output."
434 nodePoller.register( node.stdout )
436 bothPoller.register( self.stdin, POLLIN )
437 bothPoller.register( node.stdout, POLLIN )
439 # Buffer by character, so that interactive
440 # commands sort of work
441 quietRun( 'stty -icanon min 1' )
445 # XXX BL: this doesn't quite do what we want.
446 if False and self.inputFile:
447 key = self.inputFile.read( 1 )
451 self.inputFile = None
452 if isReadable( self.inPoller ):
453 key = self.stdin.read( 1 )
455 if isReadable( nodePoller ):
456 data = node.monitor()
460 except KeyboardInterrupt:
461 # There is an at least one race condition here, since
462 # it's possible to interrupt ourselves after we've
463 # read data but before it has been printed.
465 except select.error as e:
466 # pylint: disable=unpacking-non-sequence
467 errno_, errmsg = e.args
468 # pylint: enable=unpacking-non-sequence
469 if errno_ != errno.EINTR:
470 error( "select.error: %d, %s" % (errno_, errmsg) )
473 def precmd( self, line ):
474 "allow for comments in the cli"
476 line = line.split( '#' )[ 0 ]
482 def isReadable( poller ):
483 "Check whether a Poll object has a readable fd."
484 for fdmask in poller.poll( 0 ):