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