a8a100ece3242de965b177ade432200919ffad3d
[vsorcdistro/.git] / mininet / mininet / cli.py
1 """
2 A simple command-line interface for Mininet.
3
4 The Mininet CLI provides a simple control console which
5 makes it easy to talk to nodes. For example, the command
6
7 mininet> h27 ifconfig
8
9 runs 'ifconfig' on host h27.
10
11 Having a single console rather than, for example, an xterm for each
12 node is particularly convenient for networks of any reasonable
13 size.
14
15 The CLI automatically substitutes IP addresses for node names,
16 so commands like
17
18 mininet> h2 ping h3
19
20 should work correctly and allow host h2 to ping host h3
21
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'.)
26 """
27
28 from subprocess import call
29 from cmd import Cmd
30 from os import isatty
31 from select import poll, POLLIN
32 import select
33 import errno
34 import sys
35 import time
36 import os
37 import atexit
38
39 from mininet.log import info, output, error
40 from mininet.term import makeTerms, runX11
41 from mininet.util import ( quietRun, dumpNodeConnections,
42                            dumpPorts )
43
44 class CLI( Cmd ):
45     "Simple command-line interface to talk to nodes."
46
47     prompt = 'VSoRC>'
48
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"""
54         self.mn = mininet
55         # Local variable bindings for py command
56         self.locals = { 'net': mininet }
57         # Attempt to handle input
58         self.stdin = stdin
59         self.inPoller = poll()
60         self.inPoller.register( stdin )
61         self.inputFile = script
62         Cmd.__init__( self )
63         info( '*** Starting CLI:\n' )
64
65         if self.inputFile:
66             self.do_source( self.inputFile )
67             return
68
69         self.initReadline()
70         self.run()
71
72     readlineInited = False
73
74     @classmethod
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:
79             return
80         cls.readlineInited = True
81         try:
82             from readline import ( read_history_file, write_history_file,
83                                    set_history_length )
84         except ImportError:
85             pass
86         else:
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 ) )
92
93     def run( self ):
94         "Run our cmdloop(), catching KeyboardInterrupt"
95         while True:
96             try:
97                 # Make sure no nodes are still waiting
98                 for node in self.mn.values():
99                     while node.waiting:
100                         info( 'stopping', node, '\n' )
101                         node.sendInt()
102                         node.waitOutput()
103                 if self.isatty():
104                     quietRun( 'stty echo sane intr ^C' )
105                 self.cmdloop()
106                 break
107             except KeyboardInterrupt:
108                 # Output a message - unless it's also interrupted
109                 # pylint: disable=broad-except
110                 try:
111                     output( '\nInterrupt\n' )
112                 except Exception:
113                     pass
114                 # pylint: enable=broad-except
115
116     def emptyline( self ):
117         "Don't repeat last command when you hit return."
118         pass
119
120     def getLocals( self ):
121         "Local variable bindings for py command"
122         self.locals.update( self.mn )
123         return self.locals
124
125     helpStr = (
126         'You may also send a command to a node using:\n'
127         '  <node> command {args}\n'
128         'For example:\n'
129         '  mininet> h1 ifconfig\n'
130         '\n'
131         'The interpreter automatically substitutes IP addresses\n'
132         'for node names when a node is the first arg, so commands\n'
133         'like\n'
134         '  mininet> h2 ping h3\n'
135         'should work.\n'
136         '\n'
137         'Some character-oriented interactive commands require\n'
138         'noecho:\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'
142     )
143
144     def do_help( self, line ):
145         "Describe available CLI commands."
146         Cmd.do_help( self, line )
147         if line is '':
148             output( self.helpStr )
149
150     def do_nodes( self, _line ):
151         "List all nodes."
152         nodes = ' '.join( sorted( self.mn ) )
153         output( 'available nodes are: \n%s\n' % nodes )
154
155     def do_ports( self, _line ):
156         "display ports and interfaces for each switch"
157         dumpPorts( self.mn.switches )
158
159     def do_net( self, _line ):
160         "List network connections."
161         dumpNodeConnections( self.mn.values() )
162
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 )
168
169     # do_py() and do_px() need to catch any exception during eval()/exec()
170     # pylint: disable=broad-except
171
172     def do_py( self, line ):
173         """Evaluate a Python expression.
174            Node names may be used, e.g.: py h1.cmd('ls')"""
175         try:
176             result = eval( line, globals(), self.getLocals() )
177             if not result:
178                 return
179             elif isinstance( result, str ):
180                 output( result + '\n' )
181             else:
182                 output( repr( result ) + '\n' )
183         except Exception as e:
184             output( str( e ) + '\n' )
185
186     # We are in fact using the exec() pseudo-function
187     # pylint: disable=exec-used
188
189     def do_px( self, line ):
190         """Execute a Python statement.
191             Node names may be used, e.g.: px print h1.cmd('ls')"""
192         try:
193             exec( line, globals(), self.getLocals() )
194         except Exception as e:
195             output( str( e ) + '\n' )
196
197     # pylint: enable=broad-except,exec-used
198
199     def do_pingall( self, line ):
200         "Ping between all hosts."
201         self.mn.pingAll( line )
202
203     def do_pingpair( self, _line ):
204         "Ping between first two hosts, useful for testing."
205         self.mn.pingPair()
206
207     def do_pingallfull( self, _line ):
208         "Ping between all hosts, returns all ping results."
209         self.mn.pingAllFull()
210
211     def do_pingpairfull( self, _line ):
212         "Ping between first two hosts, returns all ping results."
213         self.mn.pingPairFull()
214
215     def do_iperf( self, line ):
216         """Simple iperf TCP test between two (optionally specified) hosts.
217            Usage: iperf node1 node2"""
218         args = line.split()
219         if not args:
220             self.mn.iperf()
221         elif len(args) == 2:
222             hosts = []
223             err = False
224             for arg in args:
225                 if arg not in self.mn:
226                     err = True
227                     error( "node '%s' not in network\n" % arg )
228                 else:
229                     hosts.append( self.mn[ arg ] )
230             if not err:
231                 self.mn.iperf( hosts )
232         else:
233             error( 'invalid number of args: iperf src dst\n' )
234
235     def do_iperfudp( self, line ):
236         """Simple iperf UDP test between two (optionally specified) hosts.
237            Usage: iperfudp bw node1 node2"""
238         args = line.split()
239         if not args:
240             self.mn.iperf( l4Type='UDP' )
241         elif len(args) == 3:
242             udpBw = args[ 0 ]
243             hosts = []
244             err = False
245             for arg in args[ 1:3 ]:
246                 if arg not in self.mn:
247                     err = True
248                     error( "node '%s' not in network\n" % arg )
249                 else:
250                     hosts.append( self.mn[ arg ] )
251             if not err:
252                 self.mn.iperf( hosts, l4Type='UDP', udpBw=udpBw )
253         else:
254             error( 'invalid number of args: iperfudp bw src dst\n' +
255                    'bw examples: 10M\n' )
256
257     def do_intfs( self, _line ):
258         "List interfaces."
259         for node in self.mn.values():
260             output( '%s: %s\n' %
261                     ( node.name, ','.join( node.intfNames() ) ) )
262
263     def do_dump( self, _line ):
264         "Dump node info."
265         for node in self.mn.values():
266             output( '%s\n' % repr( node ) )
267
268     def do_link( self, line ):
269         """Bring link(s) between two nodes up or down.
270            Usage: link node1 node2 [up/down]"""
271         args = line.split()
272         if len(args) != 3:
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' )
276         else:
277             self.mn.configLinkStatus( *args )
278
279     def do_xterm( self, line, term='xterm' ):
280         """Spawn xterm(s) for the given node(s).
281            Usage: xterm node1 node2 ..."""
282         args = line.split()
283         if not args:
284             error( 'usage: %s node1 node2 ...\n' % term )
285         else:
286             for arg in args:
287                 if arg not in self.mn:
288                     error( "node '%s' not in network\n" % arg )
289                 else:
290                     node = self.mn[ arg ]
291                     self.mn.terms += makeTerms( [ node ], term = term )
292
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]"""
297         args = line.split()
298         if not args:
299             error( 'usage: x node [cmd args]...\n' )
300         else:
301             node = self.mn[ args[ 0 ] ]
302             cmd = args[ 1: ]
303             self.mn.terms += runX11( node, cmd )
304
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' )
309
310     def do_exit( self, _line ):
311         "Exit"
312         assert self  # satisfy pylint and allow override
313         return 'exited by user command'
314
315     def do_quit( self, line ):
316         "Exit"
317         return self.do_exit( line )
318
319     def do_EOF( self, line ):
320         "Exit"
321         output( '\n' )
322         return self.do_exit( line )
323
324     def isatty( self ):
325         "Is our standard input a tty?"
326         return isatty( self.stdin.fileno() )
327
328     def do_noecho( self, line ):
329         """Run an interactive command with echoing turned off.
330            Usage: noecho [cmd args]"""
331         if self.isatty():
332             quietRun( 'stty -echo' )
333         self.default( line )
334         if self.isatty():
335             quietRun( 'stty echo' )
336
337     def do_source( self, line ):
338         """Read commands from an input file.
339            Usage: source <file>"""
340         args = line.split()
341         if len(args) != 1:
342             error( 'usage: source <file>\n' )
343             return
344         try:
345             self.inputFile = open( args[ 0 ] )
346             while True:
347                 line = self.inputFile.readline()
348                 if len( line ) > 0:
349                     self.onecmd( line )
350                 else:
351                     break
352         except IOError:
353             error( 'error reading file %s\n' % args[ 0 ] )
354         self.inputFile.close()
355         self.inputFile = None
356
357     def do_dpctl( self, line ):
358         """Run dpctl (or ovs-ofctl) command on all switches.
359            Usage: dpctl command [arg1] [arg2] ..."""
360         args = line.split()
361         if len(args) < 1:
362             error( 'usage: dpctl command [arg1] [arg2] ...\n' )
363             return
364         for sw in self.mn.switches:
365             output( '*** ' + sw.name + ' ' + ('-' * 72) + '\n' )
366             output( sw.dpctl( *args ) )
367
368     def do_time( self, line ):
369         "Measure time taken for any command in Mininet."
370         start = time.time()
371         self.onecmd(line)
372         elapsed = time.time() - start
373         self.stdout.write("*** Elapsed time: %0.6f secs\n" % elapsed)
374
375     def do_links( self, _line ):
376         "Report on links"
377         for link in self.mn.links:
378             output( link, link.status(), '\n' )
379
380     def do_switch( self, line ):
381         "Starts or stops a switch"
382         args = line.split()
383         if len(args) != 2:
384             error( 'invalid number of args: switch <switch name>'
385                    '{start, stop}\n' )
386             return
387         sw = args[ 0 ]
388         command = args[ 1 ]
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 ] )
391         else:
392             sw = args[ 0 ]
393             command = 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 )
398             else:
399                 error( 'invalid command: '
400                        'switch <switch name> {start, stop}\n' )
401
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."""
407
408         first, args, line = self.parseline( line )
409
410         if first in self.mn:
411             if not args:
412                 error( '*** Please enter a command for node: %s <cmd>\n'
413                        % first )
414                 return
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
421                      for arg in rest ]
422             rest = ' '.join( rest )
423             # Run cmd on node:
424             node.sendCmd( rest )
425             self.waitForNode( node )
426         else:
427             error( '*** Unknown command: %s\n' % line )
428
429     def waitForNode( self, node ):
430         "Wait for a node to finish, and print its output."
431         # Pollers
432         nodePoller = poll()
433         nodePoller.register( node.stdout )
434         bothPoller = poll()
435         bothPoller.register( self.stdin, POLLIN )
436         bothPoller.register( node.stdout, POLLIN )
437         if self.isatty():
438             # Buffer by character, so that interactive
439             # commands sort of work
440             quietRun( 'stty -icanon min 1' )
441         while True:
442             try:
443                 bothPoller.poll()
444                 # XXX BL: this doesn't quite do what we want.
445                 if False and self.inputFile:
446                     key = self.inputFile.read( 1 )
447                     if key is not '':
448                         node.write( key )
449                     else:
450                         self.inputFile = None
451                 if isReadable( self.inPoller ):
452                     key = self.stdin.read( 1 )
453                     node.write( key )
454                 if isReadable( nodePoller ):
455                     data = node.monitor()
456                     output( data )
457                 if not node.waiting:
458                     break
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.
463                 node.sendInt()
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) )
470                     node.sendInt()
471
472     def precmd( self, line ):
473         "allow for comments in the cli"
474         if '#' in line:
475             line = line.split( '#' )[ 0 ]
476         return line
477
478
479 # Helper functions
480
481 def isReadable( poller ):
482     "Check whether a Poll object has a readable fd."
483     for fdmask in poller.poll( 0 ):
484         mask = fdmask[ 1 ]
485         if mask & POLLIN:
486             return True