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