blob: db037873e295226e4d166d03f756962cc68e58c5 [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
ashu01b62f72015-03-12 15:16:11 -050042 prompt = 'minindn> '
carlosmscabralf40ecd12013-02-01 18:15:58 -020043
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() ) ) )
carlosmscabrale121a7b2013-02-18 18:14:53 -0300204
205 def do_ccndump(self, _line):
206 "Dump FIB entries"
207 for node in self.nodelist:
208 if 'fib' in node.params:
209 output(node.name + ': ')
210 for name in node.params['fib']:
211 output(str(name) + ' ')
212 output('\n')
213
carlosmscabralf40ecd12013-02-01 18:15:58 -0200214
215 def do_dump( self, _line ):
216 "Dump node info."
217 for node in self.nodelist:
218 output( '%s\n' % repr( node ) )
219
220 def do_link( self, line ):
221 "Bring link(s) between two nodes up or down."
222 args = line.split()
223 if len(args) != 3:
224 error( 'invalid number of args: link end1 end2 [up down]\n' )
225 elif args[ 2 ] not in [ 'up', 'down' ]:
226 error( 'invalid type: link end1 end2 [up down]\n' )
227 else:
228 self.mn.configLinkStatus( *args )
229
230 def do_xterm( self, line, term='xterm' ):
231 "Spawn xterm(s) for the given node(s)."
232 args = line.split()
233 if not args:
234 error( 'usage: %s node1 node2 ...\n' % term )
235 else:
236 for arg in args:
237 if arg not in self.nodemap:
238 error( "node '%s' not in network\n" % arg )
239 else:
240 node = self.nodemap[ arg ]
241 self.mn.terms += makeTerms( [ node ], term = term )
242
243 def do_gterm( self, line ):
244 "Spawn gnome-terminal(s) for the given node(s)."
245 self.do_xterm( line, term='gterm' )
246
247 def do_exit( self, _line ):
248 "Exit"
249 return 'exited by user command'
250
251 def do_quit( self, line ):
252 "Exit"
253 return self.do_exit( line )
254
255 def do_EOF( self, line ):
256 "Exit"
257 output( '\n' )
258 return self.do_exit( line )
259
260 def isatty( self ):
261 "Is our standard input a tty?"
262 return isatty( self.stdin.fileno() )
263
264 def do_noecho( self, line ):
265 "Run an interactive command with echoing turned off."
266 if self.isatty():
267 quietRun( 'stty -echo' )
268 self.default( line )
269 if self.isatty():
270 quietRun( 'stty echo' )
271
272 def do_source( self, line ):
273 "Read commands from an input file."
274 args = line.split()
275 if len(args) != 1:
276 error( 'usage: source <file>\n' )
277 return
278 try:
279 self.inputFile = open( args[ 0 ] )
280 while True:
281 line = self.inputFile.readline()
282 if len( line ) > 0:
283 self.onecmd( line )
284 else:
285 break
286 except IOError:
287 error( 'error reading file %s\n' % args[ 0 ] )
288 self.inputFile = None
289
290 def do_dpctl( self, line ):
291 "Run dpctl command on all switches."
292 args = line.split()
293 if len(args) < 1:
294 error( 'usage: dpctl command [arg1] [arg2] ...\n' )
295 return
296 for sw in self.mn.switches:
297 output( '*** ' + sw.name + ' ' + ('-' * 72) + '\n' )
298 output( sw.dpctl( *args ) )
299
300 def do_time( self, line ):
301 "Measure time taken for any command in Mininet."
302 start = time.time()
303 self.onecmd(line)
304 elapsed = time.time() - start
305 self.stdout.write("*** Elapsed time: %0.6f secs\n" % elapsed)
306
307 def default( self, line ):
308 """Called on an input line when the command prefix is not recognized.
309 Overridden to run shell commands when a node is the first CLI argument.
310 Past the first CLI argument, node names are automatically replaced with
311 corresponding IP addrs."""
312
313 first, args, line = self.parseline( line )
314 if not args:
315 return
316 if args and len(args) > 0 and args[ -1 ] == '\n':
317 args = args[ :-1 ]
318 rest = args.split( ' ' )
319
320 if first in self.nodemap:
321 node = self.nodemap[ first ]
322 # Substitute IP addresses for node names in command
323 rest = [ self.nodemap[ arg ].IP()
324 if arg in self.nodemap else arg
325 for arg in rest ]
326 rest = ' '.join( rest )
327 # Run cmd on node:
328 builtin = isShellBuiltin( first )
329 node.sendCmd( rest, printPid=( not builtin ) )
330 self.waitForNode( node )
331 else:
332 error( '*** Unknown command: %s\n' % first )
333
334 # pylint: enable-msg=R0201
335
336 def waitForNode( self, node ):
337 "Wait for a node to finish, and print its output."
338 # Pollers
339 nodePoller = poll()
340 nodePoller.register( node.stdout )
341 bothPoller = poll()
342 bothPoller.register( self.stdin, POLLIN )
343 bothPoller.register( node.stdout, POLLIN )
344 if self.isatty():
345 # Buffer by character, so that interactive
346 # commands sort of work
347 quietRun( 'stty -icanon min 1' )
348 while True:
349 try:
350 bothPoller.poll()
351 # XXX BL: this doesn't quite do what we want.
352 if False and self.inputFile:
353 key = self.inputFile.read( 1 )
354 if key is not '':
355 node.write(key)
356 else:
357 self.inputFile = None
358 if isReadable( self.inPoller ):
359 key = self.stdin.read( 1 )
360 node.write( key )
361 if isReadable( nodePoller ):
362 data = node.monitor()
363 output( data )
364 if not node.waiting:
365 break
366 except KeyboardInterrupt:
367 node.sendInt()
368
369# Helper functions
370
371def isReadable( poller ):
372 "Check whether a Poll object has a readable fd."
373 for fdmask in poller.poll( 0 ):
374 mask = fdmask[ 1 ]
375 if mask & POLLIN:
376 return True