blob: efb18089e5d79c9074d857e71bdd23b7fcfd8590 [file] [log] [blame]
carlosmscabralf40ecd12013-02-01 18:15:58 -02001"""
2A simple command-line interface for Mininet.
3
4The Mininet CLI provides a simple control console which
5makes it easy to talk to nodes. For example, the command
6
7mininet> h27 ifconfig
8
9runs 'ifconfig' on host h27.
10
11Having a single console rather than, for example, an xterm for each
12node is particularly convenient for networks of any reasonable
13size.
14
15The CLI automatically substitutes IP addresses for node names,
16so commands like
17
18mininet> h2 ping h3
19
20should work correctly and allow host h2 to ping host h3
21
22Several useful commands are provided, including the ability to
23list all nodes ('nodes'), to print out the network topology
24('net') and to check connectivity ('pingall', 'pingpair')
25and bandwidth ('iperf'.)
26"""
27
28from subprocess import call
29from cmd import Cmd
30from os import isatty
31from select import poll, POLLIN
32import sys
33import time
34
35from mininet.log import info, output, error
36from mininet.term import makeTerms
37from mininet.util import quietRun, isShellBuiltin, dumpNodeConnections
38
39class CLI( Cmd ):
40 "Simple command-line interface to talk to nodes."
41
42 prompt = 'mininet> '
43
44 def __init__( self, mininet, stdin=sys.stdin, script=None ):
45 self.mn = mininet
46 self.nodelist = self.mn.controllers + self.mn.switches + self.mn.hosts
47 self.nodemap = {} # map names to Node objects
48 for node in self.nodelist:
49 self.nodemap[ node.name ] = node
50 # Local variable bindings for py command
51 self.locals = { 'net': mininet }
52 self.locals.update( self.nodemap )
53 # Attempt to handle input
54 self.stdin = stdin
55 self.inPoller = poll()
56 self.inPoller.register( stdin )
57 self.inputFile = script
58 Cmd.__init__( self )
59 info( '*** Starting CLI:\n' )
60 if self.inputFile:
61 self.do_source( self.inputFile )
62 return
63 while True:
64 try:
65 # Make sure no nodes are still waiting
66 for node in self.nodelist:
67 while node.waiting:
68 node.sendInt()
69 node.monitor()
70 if self.isatty():
71 quietRun( 'stty sane' )
72 self.cmdloop()
73 break
74 except KeyboardInterrupt:
75 output( '\nInterrupt\n' )
76
77 def emptyline( self ):
78 "Don't repeat last command when you hit return."
79 pass
80
81 # Disable pylint "Unused argument: 'arg's'" messages, as well as
82 # "method could be a function" warning, since each CLI function
83 # must have the same interface
84 # pylint: disable-msg=R0201
85
86 helpStr = (
87 'You may also send a command to a node using:\n'
88 ' <node> command {args}\n'
89 'For example:\n'
90 ' mininet> h1 ifconfig\n'
91 '\n'
92 'The interpreter automatically substitutes IP addresses\n'
93 'for node names when a node is the first arg, so commands\n'
94 'like\n'
95 ' mininet> h2 ping h3\n'
96 'should work.\n'
97 '\n'
98 'Some character-oriented interactive commands require\n'
99 'noecho:\n'
100 ' mininet> noecho h2 vi foo.py\n'
101 'However, starting up an xterm/gterm is generally better:\n'
102 ' mininet> xterm h2\n\n'
103 )
104
105 def do_help( self, line ):
106 "Describe available CLI commands."
107 Cmd.do_help( self, line )
108 if line is '':
109 output( self.helpStr )
110
111 def do_nodes( self, _line ):
112 "List all nodes."
113 nodes = ' '.join( [ node.name for node in sorted( self.nodelist ) ] )
114 output( 'available nodes are: \n%s\n' % nodes )
115
116 def do_net( self, _line ):
117 "List network connections."
118 dumpNodeConnections( self.nodelist )
119
120 def do_sh( self, line ):
121 "Run an external shell command"
122 call( line, shell=True )
123
124 # do_py() needs to catch any exception during eval()
125 # pylint: disable-msg=W0703
126
127 def do_py( self, line ):
128 """Evaluate a Python expression.
129 Node names may be used, e.g.: h1.cmd('ls')"""
130 try:
131 result = eval( line, globals(), self.locals )
132 if not result:
133 return
134 elif isinstance( result, str ):
135 output( result + '\n' )
136 else:
137 output( repr( result ) + '\n' )
138 except Exception, e:
139 output( str( e ) + '\n' )
140
141 # pylint: enable-msg=W0703
142
143 def do_pingall( self, _line ):
144 "Ping between all hosts."
145 self.mn.pingAll()
146
147 def do_pingpair( self, _line ):
148 "Ping between first two hosts, useful for testing."
149 self.mn.pingPair()
150
151 def do_pingallfull( self, _line ):
152 "Ping between first two hosts, returns all ping results."
153 self.mn.pingAllFull()
154
155 def do_pingpairfull( self, _line ):
156 "Ping between first two hosts, returns all ping results."
157 self.mn.pingPairFull()
158
159 def do_iperf( self, line ):
160 "Simple iperf TCP test between two (optionally specified) hosts."
161 args = line.split()
162 if not args:
163 self.mn.iperf()
164 elif len(args) == 2:
165 hosts = []
166 err = False
167 for arg in args:
168 if arg not in self.nodemap:
169 err = True
170 error( "node '%s' not in network\n" % arg )
171 else:
172 hosts.append( self.nodemap[ arg ] )
173 if not err:
174 self.mn.iperf( hosts )
175 else:
176 error( 'invalid number of args: iperf src dst\n' )
177
178 def do_iperfudp( self, line ):
179 "Simple iperf TCP test between two (optionally specified) hosts."
180 args = line.split()
181 if not args:
182 self.mn.iperf( l4Type='UDP' )
183 elif len(args) == 3:
184 udpBw = args[ 0 ]
185 hosts = []
186 err = False
187 for arg in args[ 1:3 ]:
188 if arg not in self.nodemap:
189 err = True
190 error( "node '%s' not in network\n" % arg )
191 else:
192 hosts.append( self.nodemap[ arg ] )
193 if not err:
194 self.mn.iperf( hosts, l4Type='UDP', udpBw=udpBw )
195 else:
196 error( 'invalid number of args: iperfudp bw src dst\n' +
197 'bw examples: 10M\n' )
198
199 def do_intfs( self, _line ):
200 "List interfaces."
201 for node in self.nodelist:
202 output( '%s: %s\n' %
203 ( node.name, ','.join( node.intfNames() ) ) )
204
205 def do_dump( self, _line ):
206 "Dump node info."
207 for node in self.nodelist:
208 output( '%s\n' % repr( node ) )
209
210 def do_link( self, line ):
211 "Bring link(s) between two nodes up or down."
212 args = line.split()
213 if len(args) != 3:
214 error( 'invalid number of args: link end1 end2 [up down]\n' )
215 elif args[ 2 ] not in [ 'up', 'down' ]:
216 error( 'invalid type: link end1 end2 [up down]\n' )
217 else:
218 self.mn.configLinkStatus( *args )
219
220 def do_xterm( self, line, term='xterm' ):
221 "Spawn xterm(s) for the given node(s)."
222 args = line.split()
223 if not args:
224 error( 'usage: %s node1 node2 ...\n' % term )
225 else:
226 for arg in args:
227 if arg not in self.nodemap:
228 error( "node '%s' not in network\n" % arg )
229 else:
230 node = self.nodemap[ arg ]
231 self.mn.terms += makeTerms( [ node ], term = term )
232
233 def do_gterm( self, line ):
234 "Spawn gnome-terminal(s) for the given node(s)."
235 self.do_xterm( line, term='gterm' )
236
237 def do_exit( self, _line ):
238 "Exit"
239 return 'exited by user command'
240
241 def do_quit( self, line ):
242 "Exit"
243 return self.do_exit( line )
244
245 def do_EOF( self, line ):
246 "Exit"
247 output( '\n' )
248 return self.do_exit( line )
249
250 def isatty( self ):
251 "Is our standard input a tty?"
252 return isatty( self.stdin.fileno() )
253
254 def do_noecho( self, line ):
255 "Run an interactive command with echoing turned off."
256 if self.isatty():
257 quietRun( 'stty -echo' )
258 self.default( line )
259 if self.isatty():
260 quietRun( 'stty echo' )
261
262 def do_source( self, line ):
263 "Read commands from an input file."
264 args = line.split()
265 if len(args) != 1:
266 error( 'usage: source <file>\n' )
267 return
268 try:
269 self.inputFile = open( args[ 0 ] )
270 while True:
271 line = self.inputFile.readline()
272 if len( line ) > 0:
273 self.onecmd( line )
274 else:
275 break
276 except IOError:
277 error( 'error reading file %s\n' % args[ 0 ] )
278 self.inputFile = None
279
280 def do_dpctl( self, line ):
281 "Run dpctl command on all switches."
282 args = line.split()
283 if len(args) < 1:
284 error( 'usage: dpctl command [arg1] [arg2] ...\n' )
285 return
286 for sw in self.mn.switches:
287 output( '*** ' + sw.name + ' ' + ('-' * 72) + '\n' )
288 output( sw.dpctl( *args ) )
289
290 def do_time( self, line ):
291 "Measure time taken for any command in Mininet."
292 start = time.time()
293 self.onecmd(line)
294 elapsed = time.time() - start
295 self.stdout.write("*** Elapsed time: %0.6f secs\n" % elapsed)
296
297 def default( self, line ):
298 """Called on an input line when the command prefix is not recognized.
299 Overridden to run shell commands when a node is the first CLI argument.
300 Past the first CLI argument, node names are automatically replaced with
301 corresponding IP addrs."""
302
303 first, args, line = self.parseline( line )
304 if not args:
305 return
306 if args and len(args) > 0 and args[ -1 ] == '\n':
307 args = args[ :-1 ]
308 rest = args.split( ' ' )
309
310 if first in self.nodemap:
311 node = self.nodemap[ first ]
312 # Substitute IP addresses for node names in command
313 rest = [ self.nodemap[ arg ].IP()
314 if arg in self.nodemap else arg
315 for arg in rest ]
316 rest = ' '.join( rest )
317 # Run cmd on node:
318 builtin = isShellBuiltin( first )
319 node.sendCmd( rest, printPid=( not builtin ) )
320 self.waitForNode( node )
321 else:
322 error( '*** Unknown command: %s\n' % first )
323
324 # pylint: enable-msg=R0201
325
326 def waitForNode( self, node ):
327 "Wait for a node to finish, and print its output."
328 # Pollers
329 nodePoller = poll()
330 nodePoller.register( node.stdout )
331 bothPoller = poll()
332 bothPoller.register( self.stdin, POLLIN )
333 bothPoller.register( node.stdout, POLLIN )
334 if self.isatty():
335 # Buffer by character, so that interactive
336 # commands sort of work
337 quietRun( 'stty -icanon min 1' )
338 while True:
339 try:
340 bothPoller.poll()
341 # XXX BL: this doesn't quite do what we want.
342 if False and self.inputFile:
343 key = self.inputFile.read( 1 )
344 if key is not '':
345 node.write(key)
346 else:
347 self.inputFile = None
348 if isReadable( self.inPoller ):
349 key = self.stdin.read( 1 )
350 node.write( key )
351 if isReadable( nodePoller ):
352 data = node.monitor()
353 output( data )
354 if not node.waiting:
355 break
356 except KeyboardInterrupt:
357 node.sendInt()
358
359# Helper functions
360
361def isReadable( poller ):
362 "Check whether a Poll object has a readable fd."
363 for fdmask in poller.poll( 0 ):
364 mask = fdmask[ 1 ]
365 if mask & POLLIN:
366 return True