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"
97 # Make sure no nodes are still waiting
98 for node in self.mn.values():
100 info( 'stopping', node, '\n' )
104 quietRun( 'stty echo sane intr ^C' )
107 except KeyboardInterrupt:
108 # Output a message - unless it's also interrupted
109 # pylint: disable=broad-except
111 output( '\nInterrupt\n' )
114 # pylint: enable=broad-except
116 def emptyline( self ):
117 "Don't repeat last command when you hit return."
120 def getLocals( self ):
121 "Local variable bindings for py command"
122 self.locals.update( self.mn )
126 'You may also send a command to a node using:\n'
127 ' <node> command {args}\n'
129 ' mininet> h1 ifconfig\n'
131 'The interpreter automatically substitutes IP addresses\n'
132 'for node names when a node is the first arg, so commands\n'
134 ' mininet> h2 ping h3\n'
137 'Some character-oriented interactive commands require\n'
139 ' mininet> noecho h2 vi foo.py\n'
140 'However, starting up an xterm/gterm is generally better:\n'
141 ' mininet> xterm h2\n\n'
144 def do_help( self, line ):
145 "Describe available CLI commands."
146 Cmd.do_help( self, line )
148 output( self.helpStr )
150 def do_nodes( self, _line ):
152 nodes = ' '.join( sorted( self.mn ) )
153 output( 'available nodes are: \n%s\n' % nodes )
155 def do_ports( self, _line ):
156 "display ports and interfaces for each switch"
157 dumpPorts( self.mn.switches )
159 def do_net( self, _line ):
160 "List network connections."
161 dumpNodeConnections( self.mn.values() )
163 def do_sh( self, line ):
164 """Run an external shell command
165 Usage: sh [cmd args]"""
166 assert self # satisfy pylint and allow override
167 call( line, shell=True )
169 # do_py() and do_px() need to catch any exception during eval()/exec()
170 # pylint: disable=broad-except
172 def do_py( self, line ):
173 """Evaluate a Python expression.
174 Node names may be used, e.g.: py h1.cmd('ls')"""
176 result = eval( line, globals(), self.getLocals() )
179 elif isinstance( result, str ):
180 output( result + '\n' )
182 output( repr( result ) + '\n' )
183 except Exception as e:
184 output( str( e ) + '\n' )
186 # We are in fact using the exec() pseudo-function
187 # pylint: disable=exec-used
189 def do_px( self, line ):
190 """Execute a Python statement.
191 Node names may be used, e.g.: px print h1.cmd('ls')"""
193 exec( line, globals(), self.getLocals() )
194 except Exception as e:
195 output( str( e ) + '\n' )
197 # pylint: enable=broad-except,exec-used
199 def do_pingall( self, line ):
200 "Ping between all hosts."
201 self.mn.pingAll( line )
203 def do_pingpair( self, _line ):
204 "Ping between first two hosts, useful for testing."
207 def do_pingallfull( self, _line ):
208 "Ping between all hosts, returns all ping results."
209 self.mn.pingAllFull()
211 def do_pingpairfull( self, _line ):
212 "Ping between first two hosts, returns all ping results."
213 self.mn.pingPairFull()
215 def do_iperf( self, line ):
216 """Simple iperf TCP test between two (optionally specified) hosts.
217 Usage: iperf node1 node2"""
225 if arg not in self.mn:
227 error( "node '%s' not in network\n" % arg )
229 hosts.append( self.mn[ arg ] )
231 self.mn.iperf( hosts )
233 error( 'invalid number of args: iperf src dst\n' )
235 def do_iperfudp( self, line ):
236 """Simple iperf UDP test between two (optionally specified) hosts.
237 Usage: iperfudp bw node1 node2"""
240 self.mn.iperf( l4Type='UDP' )
245 for arg in args[ 1:3 ]:
246 if arg not in self.mn:
248 error( "node '%s' not in network\n" % arg )
250 hosts.append( self.mn[ arg ] )
252 self.mn.iperf( hosts, l4Type='UDP', udpBw=udpBw )
254 error( 'invalid number of args: iperfudp bw src dst\n' +
255 'bw examples: 10M\n' )
257 def do_intfs( self, _line ):
259 for node in self.mn.values():
261 ( node.name, ','.join( node.intfNames() ) ) )
263 def do_dump( self, _line ):
265 for node in self.mn.values():
266 output( '%s\n' % repr( node ) )
268 def do_link( self, line ):
269 """Bring link(s) between two nodes up or down.
270 Usage: link node1 node2 [up/down]"""
273 error( 'invalid number of args: link end1 end2 [up down]\n' )
274 elif args[ 2 ] not in [ 'up', 'down' ]:
275 error( 'invalid type: link end1 end2 [up down]\n' )
277 self.mn.configLinkStatus( *args )
279 def do_xterm( self, line, term='xterm' ):
280 """Spawn xterm(s) for the given node(s).
281 Usage: xterm node1 node2 ..."""
284 error( 'usage: %s node1 node2 ...\n' % term )
287 if arg not in self.mn:
288 error( "node '%s' not in network\n" % arg )
290 node = self.mn[ arg ]
291 self.mn.terms += makeTerms( [ node ], term = term )
293 def do_x( self, line ):
294 """Create an X11 tunnel to the given node,
295 optionally starting a client.
296 Usage: x node [cmd args]"""
299 error( 'usage: x node [cmd args]...\n' )
301 node = self.mn[ args[ 0 ] ]
303 self.mn.terms += runX11( node, cmd )
305 def do_gterm( self, line ):
306 """Spawn gnome-terminal(s) for the given node(s).
307 Usage: gterm node1 node2 ..."""
308 self.do_xterm( line, term='gterm' )
310 def do_exit( self, _line ):
312 assert self # satisfy pylint and allow override
313 return 'exited by user command'
315 def do_quit( self, line ):
317 return self.do_exit( line )
319 def do_EOF( self, line ):
322 return self.do_exit( line )
325 "Is our standard input a tty?"
326 return isatty( self.stdin.fileno() )
328 def do_noecho( self, line ):
329 """Run an interactive command with echoing turned off.
330 Usage: noecho [cmd args]"""
332 quietRun( 'stty -echo' )
335 quietRun( 'stty echo' )
337 def do_source( self, line ):
338 """Read commands from an input file.
339 Usage: source <file>"""
342 error( 'usage: source <file>\n' )
345 self.inputFile = open( args[ 0 ] )
347 line = self.inputFile.readline()
353 error( 'error reading file %s\n' % args[ 0 ] )
354 self.inputFile.close()
355 self.inputFile = None
357 def do_dpctl( self, line ):
358 """Run dpctl (or ovs-ofctl) command on all switches.
359 Usage: dpctl command [arg1] [arg2] ..."""
362 error( 'usage: dpctl command [arg1] [arg2] ...\n' )
364 for sw in self.mn.switches:
365 output( '*** ' + sw.name + ' ' + ('-' * 72) + '\n' )
366 output( sw.dpctl( *args ) )
368 def do_time( self, line ):
369 "Measure time taken for any command in Mininet."
372 elapsed = time.time() - start
373 self.stdout.write("*** Elapsed time: %0.6f secs\n" % elapsed)
375 def do_links( self, _line ):
377 for link in self.mn.links:
378 output( link, link.status(), '\n' )
380 def do_switch( self, line ):
381 "Starts or stops a switch"
384 error( 'invalid number of args: switch <switch name>'
389 if sw not in self.mn or self.mn.get( sw ) not in self.mn.switches:
390 error( 'invalid switch: %s\n' % args[ 1 ] )
394 if command == 'start':
395 self.mn.get( sw ).start( self.mn.controllers )
396 elif command == 'stop':
397 self.mn.get( sw ).stop( deleteIntfs=False )
399 error( 'invalid command: '
400 'switch <switch name> {start, stop}\n' )
402 def default( self, line ):
403 """Called on an input line when the command prefix is not recognized.
404 Overridden to run shell commands when a node is the first
405 CLI argument. Past the first CLI argument, node names are
406 automatically replaced with corresponding IP addrs."""
408 first, args, line = self.parseline( line )
412 error( '*** Please enter a command for node: %s <cmd>\n'
415 node = self.mn[ first ]
416 rest = args.split( ' ' )
417 # Substitute IP addresses for node names in command
418 # If updateIP() returns None, then use node name
419 rest = [ self.mn[ arg ].defaultIntf().updateIP() or arg
420 if arg in self.mn else arg
422 rest = ' '.join( rest )
425 self.waitForNode( node )
427 error( '*** Unknown command: %s\n' % line )
429 def waitForNode( self, node ):
430 "Wait for a node to finish, and print its output."
433 nodePoller.register( node.stdout )
435 bothPoller.register( self.stdin, POLLIN )
436 bothPoller.register( node.stdout, POLLIN )
438 # Buffer by character, so that interactive
439 # commands sort of work
440 quietRun( 'stty -icanon min 1' )
444 # XXX BL: this doesn't quite do what we want.
445 if False and self.inputFile:
446 key = self.inputFile.read( 1 )
450 self.inputFile = None
451 if isReadable( self.inPoller ):
452 key = self.stdin.read( 1 )
454 if isReadable( nodePoller ):
455 data = node.monitor()
459 except KeyboardInterrupt:
460 # There is an at least one race condition here, since
461 # it's possible to interrupt ourselves after we've
462 # read data but before it has been printed.
464 except select.error as e:
465 # pylint: disable=unpacking-non-sequence
466 errno_, errmsg = e.args
467 # pylint: enable=unpacking-non-sequence
468 if errno_ != errno.EINTR:
469 error( "select.error: %d, %s" % (errno_, errmsg) )
472 def precmd( self, line ):
473 "allow for comments in the cli"
475 line = line.split( '#' )[ 0 ]
481 def isReadable( poller ):
482 "Check whether a Poll object has a readable fd."
483 for fdmask in poller.poll( 0 ):