mininet / mininet / cli.py @ ab8c4e91
History | View | Annotate | Download (15.4 KB)
1 | 496b5f9e | Bob Lantz | """
|
---|---|---|---|
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 | bcacfc05 | Bob Lantz | mininet> h2 ping h3
|
19 | 496b5f9e | Bob Lantz |
|
20 | bcacfc05 | Bob Lantz | should work correctly and allow host h2 to ping host h3
|
21 | 496b5f9e | Bob Lantz |
|
22 | Several useful commands are provided, including the ability to
|
||
23 | list all nodes ('nodes'), to print out the network topology
|
||
24 | 64c451e0 | Bob Lantz | ('net') and to check connectivity ('pingall', 'pingpair')
|
25 | 496b5f9e | Bob Lantz | and bandwidth ('iperf'.)
|
26 | """
|
||
27 | |||
28 | from subprocess import call |
||
29 | 114dcd56 | Brandon Heller | from cmd import Cmd |
30 | f800e512 | Bob Lantz | from os import isatty |
31 | 259d7133 | Bob Lantz | from select import poll, POLLIN |
32 | 82b72072 | Bob Lantz | import sys |
33 | f509ae28 | Brandon Heller | import time |
34 | a905be22 | Rich Lane | import os |
35 | import atexit |
||
36 | 496b5f9e | Bob Lantz | |
37 | 8d3c2859 | Brandon Heller | from mininet.log import info, output, error |
38 | e3d07bc1 | Bob Lantz | from mininet.term import makeTerms, runX11 |
39 | 50774e40 | Bob Lantz | from mininet.util import ( quietRun, dumpNodeConnections, |
40 | 7a3159c9 | Bob Lantz | dumpPorts ) |
41 | 82b72072 | Bob Lantz | |
42 | 114dcd56 | Brandon Heller | class CLI( Cmd ): |
43 | 496b5f9e | Bob Lantz | "Simple command-line interface to talk to nodes."
|
44 | |||
45 | 114dcd56 | Brandon Heller | prompt = 'mininet> '
|
46 | 496b5f9e | Bob Lantz | |
47 | e3a1fbb0 | Bob Lantz | def __init__( self, mininet, stdin=sys.stdin, script=None ): |
48 | 613fac4b | Bob Lantz | """Start and run interactive or batch mode CLI
|
49 | mininet: Mininet network object
|
||
50 | stdin: standard input for CLI
|
||
51 | script: script to run in batch mode"""
|
||
52 | 496b5f9e | Bob Lantz | self.mn = mininet
|
53 | 98cb3359 | Brian O'Connor | # Local variable bindings for py command
|
54 | 9281719d | Brian O'Connor | self.locals = { 'net': mininet } |
55 | f800e512 | Bob Lantz | # Attempt to handle input
|
56 | self.stdin = stdin
|
||
57 | self.inPoller = poll()
|
||
58 | self.inPoller.register( stdin )
|
||
59 | e3a1fbb0 | Bob Lantz | self.inputFile = script
|
60 | 114dcd56 | Brandon Heller | Cmd.__init__( self )
|
61 | 4e69ae83 | Brandon Heller | info( '*** Starting CLI:\n' )
|
62 | a905be22 | Rich Lane | |
63 | e3a1fbb0 | Bob Lantz | if self.inputFile: |
64 | self.do_source( self.inputFile ) |
||
65 | return
|
||
66 | e0cd11ab | Rich Lane | |
67 | 613fac4b | Bob Lantz | self.initReadline()
|
68 | self.run()
|
||
69 | e0cd11ab | Rich Lane | |
70 | 613fac4b | Bob Lantz | readlineInited = False
|
71 | c5779dee | Bob Lantz | |
72 | e0cd11ab | Rich Lane | @classmethod
|
73 | 613fac4b | Bob Lantz | def initReadline( cls ): |
74 | e0cd11ab | Rich Lane | "Set up history if readline is available"
|
75 | # Only set up readline once to prevent multiplying the history file
|
||
76 | 613fac4b | Bob Lantz | if cls.readlineInited:
|
77 | e0cd11ab | Rich Lane | return
|
78 | 613fac4b | Bob Lantz | cls.readlineInited = True
|
79 | e0cd11ab | Rich Lane | try:
|
80 | f49e2539 | Bob Lantz | from readline import read_history_file, write_history_file |
81 | e0cd11ab | Rich Lane | except ImportError: |
82 | pass
|
||
83 | else:
|
||
84 | f49e2539 | Bob Lantz | history_path = os.path.expanduser( '~/.mininet_history' )
|
85 | if os.path.isfile( history_path ):
|
||
86 | read_history_file( history_path ) |
||
87 | atexit.register( lambda: write_history_file( history_path ) )
|
||
88 | e0cd11ab | Rich Lane | |
89 | 613fac4b | Bob Lantz | def run( self ): |
90 | "Run our cmdloop(), catching KeyboardInterrupt"
|
||
91 | while True: |
||
92 | try:
|
||
93 | # Make sure no nodes are still waiting
|
||
94 | for node in self.mn.values(): |
||
95 | while node.waiting:
|
||
96 | info( 'stopping', node, '\n' ) |
||
97 | node.sendInt() |
||
98 | node.waitOutput() |
||
99 | if self.isatty(): |
||
100 | quietRun( 'stty echo sane intr ^C' )
|
||
101 | self.cmdloop()
|
||
102 | break
|
||
103 | except KeyboardInterrupt: |
||
104 | # Output a message - unless it's also interrupted
|
||
105 | c5779dee | Bob Lantz | # pylint: disable=broad-except
|
106 | 613fac4b | Bob Lantz | try:
|
107 | output( '\nInterrupt\n' )
|
||
108 | c5779dee | Bob Lantz | except Exception: |
109 | 613fac4b | Bob Lantz | pass
|
110 | c5779dee | Bob Lantz | # pylint: enable=broad-except
|
111 | 613fac4b | Bob Lantz | |
112 | bcacfc05 | Bob Lantz | def emptyline( self ): |
113 | "Don't repeat last command when you hit return."
|
||
114 | pass
|
||
115 | 496b5f9e | Bob Lantz | |
116 | 9281719d | Brian O'Connor | def getLocals( self ): |
117 | 8e04a9f8 | Brian O'Connor | "Local variable bindings for py command"
|
118 | 9281719d | Brian O'Connor | self.locals.update( self.mn ) |
119 | return self.locals |
||
120 | 8e04a9f8 | Brian O'Connor | |
121 | 82b72072 | Bob Lantz | helpStr = ( |
122 | caf024bc | Bob Lantz | 'You may also send a command to a node using:\n'
|
123 | ' <node> command {args}\n'
|
||
124 | 'For example:\n'
|
||
125 | ' mininet> h1 ifconfig\n'
|
||
126 | '\n'
|
||
127 | 'The interpreter automatically substitutes IP addresses\n'
|
||
128 | 'for node names when a node is the first arg, so commands\n'
|
||
129 | 'like\n'
|
||
130 | ' mininet> h2 ping h3\n'
|
||
131 | 'should work.\n'
|
||
132 | '\n'
|
||
133 | 'Some character-oriented interactive commands require\n'
|
||
134 | 'noecho:\n'
|
||
135 | ' mininet> noecho h2 vi foo.py\n'
|
||
136 | 'However, starting up an xterm/gterm is generally better:\n'
|
||
137 | ' mininet> xterm h2\n\n'
|
||
138 | ) |
||
139 | |||
140 | 2a554ae3 | Bob Lantz | def do_help( self, line ): |
141 | 114dcd56 | Brandon Heller | "Describe available CLI commands."
|
142 | 2a554ae3 | Bob Lantz | Cmd.do_help( self, line )
|
143 | if line is '': |
||
144 | caf024bc | Bob Lantz | output( self.helpStr )
|
145 | 496b5f9e | Bob Lantz | |
146 | 14ff3ad3 | Bob Lantz | def do_nodes( self, _line ): |
147 | 496b5f9e | Bob Lantz | "List all nodes."
|
148 | 9281719d | Brian O'Connor | nodes = ' '.join( sorted( self.mn ) ) |
149 | cdeaca86 | Brandon Heller | output( 'available nodes are: \n%s\n' % nodes )
|
150 | 496b5f9e | Bob Lantz | |
151 | b1ec912d | Bob Lantz | def do_ports( self, _line ): |
152 | 08643fe6 | cody burkard | "display ports and interfaces for each switch"
|
153 | dumpPorts( self.mn.switches )
|
||
154 | |||
155 | 14ff3ad3 | Bob Lantz | def do_net( self, _line ): |
156 | 496b5f9e | Bob Lantz | "List network connections."
|
157 | 9281719d | Brian O'Connor | dumpNodeConnections( self.mn.values() )
|
158 | 496b5f9e | Bob Lantz | |
159 | 2a554ae3 | Bob Lantz | def do_sh( self, line ): |
160 | cac98f5f | cody burkard | """Run an external shell command
|
161 | Usage: sh [cmd args]"""
|
||
162 | 061598f0 | Bob Lantz | assert self # satisfy pylint and allow override |
163 | 2a554ae3 | Bob Lantz | call( line, shell=True )
|
164 | 496b5f9e | Bob Lantz | |
165 | 23c70f60 | Bob Lantz | # do_py() and do_px() need to catch any exception during eval()/exec()
|
166 | 061598f0 | Bob Lantz | # pylint: disable=broad-except
|
167 | 1fdcd676 | Bob Lantz | |
168 | 2a554ae3 | Bob Lantz | def do_py( self, line ): |
169 | 1fdcd676 | Bob Lantz | """Evaluate a Python expression.
|
170 | 23c70f60 | Bob Lantz | Node names may be used, e.g.: py h1.cmd('ls')"""
|
171 | 1fdcd676 | Bob Lantz | try:
|
172 | 9281719d | Brian O'Connor | result = eval( line, globals(), self.getLocals() ) |
173 | 1fdcd676 | Bob Lantz | if not result: |
174 | return
|
||
175 | elif isinstance( result, str ): |
||
176 | f800e512 | Bob Lantz | output( result + '\n' )
|
177 | 1fdcd676 | Bob Lantz | else:
|
178 | f800e512 | Bob Lantz | output( repr( result ) + '\n' ) |
179 | 1fdcd676 | Bob Lantz | except Exception, e: |
180 | f800e512 | Bob Lantz | output( str( e ) + '\n' ) |
181 | 1fdcd676 | Bob Lantz | |
182 | 23c70f60 | Bob Lantz | # We are in fact using the exec() pseudo-function
|
183 | 061598f0 | Bob Lantz | # pylint: disable=exec-used
|
184 | 23c70f60 | Bob Lantz | |
185 | def do_px( self, line ): |
||
186 | """Execute a Python statement.
|
||
187 | Node names may be used, e.g.: px print h1.cmd('ls')"""
|
||
188 | try:
|
||
189 | 9281719d | Brian O'Connor | exec( line, globals(), self.getLocals() ) |
190 | 23c70f60 | Bob Lantz | except Exception, e: |
191 | output( str( e ) + '\n' ) |
||
192 | |||
193 | 061598f0 | Bob Lantz | # pylint: enable=broad-except,exec-used
|
194 | 1fdcd676 | Bob Lantz | |
195 | 4d1a9cdc | Jon Hall | def do_pingall( self, line ): |
196 | 496b5f9e | Bob Lantz | "Ping between all hosts."
|
197 | 4d1a9cdc | Jon Hall | self.mn.pingAll( line )
|
198 | 496b5f9e | Bob Lantz | |
199 | 14ff3ad3 | Bob Lantz | def do_pingpair( self, _line ): |
200 | 496b5f9e | Bob Lantz | "Ping between first two hosts, useful for testing."
|
201 | self.mn.pingPair()
|
||
202 | |||
203 | 1f1d590c | Brandon Heller | def do_pingallfull( self, _line ): |
204 | 24fe68d9 | Baohua Yang | "Ping between all hosts, returns all ping results."
|
205 | 1f1d590c | Brandon Heller | self.mn.pingAllFull()
|
206 | |||
207 | def do_pingpairfull( self, _line ): |
||
208 | "Ping between first two hosts, returns all ping results."
|
||
209 | self.mn.pingPairFull()
|
||
210 | |||
211 | 2a554ae3 | Bob Lantz | def do_iperf( self, line ): |
212 | cac98f5f | cody burkard | """Simple iperf TCP test between two (optionally specified) hosts.
|
213 | Usage: iperf node1 node2"""
|
||
214 | 60a39a72 | Brandon Heller | args = line.split() |
215 | if not args: |
||
216 | self.mn.iperf()
|
||
217 | elif len(args) == 2: |
||
218 | hosts = [] |
||
219 | err = False
|
||
220 | for arg in args: |
||
221 | 8e04a9f8 | Brian O'Connor | if arg not in self.mn: |
222 | 60a39a72 | Brandon Heller | err = True
|
223 | error( "node '%s' not in network\n" % arg )
|
||
224 | else:
|
||
225 | 8e04a9f8 | Brian O'Connor | hosts.append( self.mn[ arg ] )
|
226 | 60a39a72 | Brandon Heller | if not err: |
227 | self.mn.iperf( hosts )
|
||
228 | else:
|
||
229 | error( 'invalid number of args: iperf src dst\n' )
|
||
230 | 496b5f9e | Bob Lantz | |
231 | 2a554ae3 | Bob Lantz | def do_iperfudp( self, line ): |
232 | cac98f5f | cody burkard | """Simple iperf UDP test between two (optionally specified) hosts.
|
233 | Usage: iperfudp bw node1 node2"""
|
||
234 | 2a554ae3 | Bob Lantz | args = line.split() |
235 | 57aae3e1 | Brandon Heller | if not args: |
236 | self.mn.iperf( l4Type='UDP' ) |
||
237 | elif len(args) == 3: |
||
238 | udpBw = args[ 0 ]
|
||
239 | hosts = [] |
||
240 | err = False
|
||
241 | for arg in args[ 1:3 ]: |
||
242 | 8e04a9f8 | Brian O'Connor | if arg not in self.mn: |
243 | 57aae3e1 | Brandon Heller | err = True
|
244 | error( "node '%s' not in network\n" % arg )
|
||
245 | else:
|
||
246 | 8e04a9f8 | Brian O'Connor | hosts.append( self.mn[ arg ] )
|
247 | 57aae3e1 | Brandon Heller | if not err: |
248 | self.mn.iperf( hosts, l4Type='UDP', udpBw=udpBw ) |
||
249 | else:
|
||
250 | error( 'invalid number of args: iperfudp bw src dst\n' +
|
||
251 | 'bw examples: 10M\n' )
|
||
252 | 496b5f9e | Bob Lantz | |
253 | 14ff3ad3 | Bob Lantz | def do_intfs( self, _line ): |
254 | 496b5f9e | Bob Lantz | "List interfaces."
|
255 | 9281719d | Brian O'Connor | for node in self.mn.values(): |
256 | cdeaca86 | Brandon Heller | output( '%s: %s\n' %
|
257 | edf60032 | Brandon Heller | ( node.name, ','.join( node.intfNames() ) ) )
|
258 | 496b5f9e | Bob Lantz | |
259 | 14ff3ad3 | Bob Lantz | def do_dump( self, _line ): |
260 | 496b5f9e | Bob Lantz | "Dump node info."
|
261 | 9281719d | Brian O'Connor | for node in self.mn.values(): |
262 | 8856d284 | Bob Lantz | output( '%s\n' % repr( node ) ) |
263 | 496b5f9e | Bob Lantz | |
264 | 2a554ae3 | Bob Lantz | def do_link( self, line ): |
265 | cac98f5f | cody burkard | """Bring link(s) between two nodes up or down.
|
266 | Usage: link node1 node2 [up/down]"""
|
||
267 | 2a554ae3 | Bob Lantz | args = line.split() |
268 | 8d3c2859 | Brandon Heller | if len(args) != 3: |
269 | c70aab0a | Bob Lantz | error( 'invalid number of args: link end1 end2 [up down]\n' )
|
270 | elif args[ 2 ] not in [ 'up', 'down' ]: |
||
271 | error( 'invalid type: link end1 end2 [up down]\n' )
|
||
272 | 8d3c2859 | Brandon Heller | else:
|
273 | c70aab0a | Bob Lantz | self.mn.configLinkStatus( *args )
|
274 | 8d3c2859 | Brandon Heller | |
275 | 2a554ae3 | Bob Lantz | def do_xterm( self, line, term='xterm' ): |
276 | cac98f5f | cody burkard | """Spawn xterm(s) for the given node(s).
|
277 | Usage: xterm node1 node2 ..."""
|
||
278 | 2a554ae3 | Bob Lantz | args = line.split() |
279 | ce9cd5be | Brandon Heller | if not args: |
280 | f800e512 | Bob Lantz | error( 'usage: %s node1 node2 ...\n' % term )
|
281 | ce9cd5be | Brandon Heller | else:
|
282 | for arg in args: |
||
283 | 8e04a9f8 | Brian O'Connor | if arg not in self.mn: |
284 | 68f97b74 | Bob Lantz | error( "node '%s' not in network\n" % arg )
|
285 | ce9cd5be | Brandon Heller | else:
|
286 | 8e04a9f8 | Brian O'Connor | node = self.mn[ arg ]
|
287 | 15b482e3 | Brandon Heller | self.mn.terms += makeTerms( [ node ], term = term )
|
288 | ce9cd5be | Brandon Heller | |
289 | e3d07bc1 | Bob Lantz | def do_x( self, line ): |
290 | """Create an X11 tunnel to the given node,
|
||
291 | cac98f5f | cody burkard | optionally starting a client.
|
292 | Usage: x node [cmd args]"""
|
||
293 | e3d07bc1 | Bob Lantz | args = line.split() |
294 | if not args: |
||
295 | error( 'usage: x node [cmd args]...\n' )
|
||
296 | else:
|
||
297 | node = self.mn[ args[ 0 ] ] |
||
298 | cmd = args[ 1: ]
|
||
299 | self.mn.terms += runX11( node, cmd )
|
||
300 | |||
301 | 2a554ae3 | Bob Lantz | def do_gterm( self, line ): |
302 | cac98f5f | cody burkard | """Spawn gnome-terminal(s) for the given node(s).
|
303 | Usage: gterm node1 node2 ..."""
|
||
304 | 2a554ae3 | Bob Lantz | self.do_xterm( line, term='gterm' ) |
305 | 68f97b74 | Bob Lantz | |
306 | 14ff3ad3 | Bob Lantz | def do_exit( self, _line ): |
307 | 114dcd56 | Brandon Heller | "Exit"
|
308 | 061598f0 | Bob Lantz | assert self # satisfy pylint and allow override |
309 | 114dcd56 | Brandon Heller | return 'exited by user command' |
310 | 496b5f9e | Bob Lantz | |
311 | 2a554ae3 | Bob Lantz | def do_quit( self, line ): |
312 | 114dcd56 | Brandon Heller | "Exit"
|
313 | 2a554ae3 | Bob Lantz | return self.do_exit( line ) |
314 | 64c451e0 | Bob Lantz | |
315 | 2a554ae3 | Bob Lantz | def do_EOF( self, line ): |
316 | 64c451e0 | Bob Lantz | "Exit"
|
317 | f800e512 | Bob Lantz | output( '\n' )
|
318 | 2a554ae3 | Bob Lantz | return self.do_exit( line ) |
319 | 114dcd56 | Brandon Heller | |
320 | f800e512 | Bob Lantz | def isatty( self ): |
321 | "Is our standard input a tty?"
|
||
322 | return isatty( self.stdin.fileno() ) |
||
323 | 82b72072 | Bob Lantz | |
324 | f800e512 | Bob Lantz | def do_noecho( self, line ): |
325 | cac98f5f | cody burkard | """Run an interactive command with echoing turned off.
|
326 | Usage: noecho [cmd args]"""
|
||
327 | f800e512 | Bob Lantz | if self.isatty(): |
328 | quietRun( 'stty -echo' )
|
||
329 | self.default( line )
|
||
330 | 9d0dbe48 | Bob Lantz | if self.isatty(): |
331 | quietRun( 'stty echo' )
|
||
332 | |||
333 | def do_source( self, line ): |
||
334 | cac98f5f | cody burkard | """Read commands from an input file.
|
335 | Usage: source <file>"""
|
||
336 | 9d0dbe48 | Bob Lantz | args = line.split() |
337 | if len(args) != 1: |
||
338 | error( 'usage: source <file>\n' )
|
||
339 | return
|
||
340 | try:
|
||
341 | self.inputFile = open( args[ 0 ] ) |
||
342 | while True: |
||
343 | line = self.inputFile.readline()
|
||
344 | if len( line ) > 0: |
||
345 | self.onecmd( line )
|
||
346 | else:
|
||
347 | break
|
||
348 | except IOError: |
||
349 | dde9c91d | Bob Lantz | error( 'error reading file %s\n' % args[ 0 ] ) |
350 | 3780d9cd | Baohua Yang | self.inputFile.close()
|
351 | 9d0dbe48 | Bob Lantz | self.inputFile = None |
352 | f800e512 | Bob Lantz | |
353 | 9da63d4e | Brandon Heller | def do_dpctl( self, line ): |
354 | cac98f5f | cody burkard | """Run dpctl (or ovs-ofctl) command on all switches.
|
355 | Usage: dpctl command [arg1] [arg2] ..."""
|
||
356 | 9da63d4e | Brandon Heller | args = line.split() |
357 | 8856d284 | Bob Lantz | if len(args) < 1: |
358 | 9da63d4e | Brandon Heller | error( 'usage: dpctl command [arg1] [arg2] ...\n' )
|
359 | return
|
||
360 | for sw in self.mn.switches: |
||
361 | output( '*** ' + sw.name + ' ' + ('-' * 72) + '\n' ) |
||
362 | 8856d284 | Bob Lantz | output( sw.dpctl( *args ) ) |
363 | 9da63d4e | Brandon Heller | |
364 | f509ae28 | Brandon Heller | def do_time( self, line ): |
365 | "Measure time taken for any command in Mininet."
|
||
366 | start = time.time() |
||
367 | self.onecmd(line)
|
||
368 | elapsed = time.time() - start |
||
369 | self.stdout.write("*** Elapsed time: %0.6f secs\n" % elapsed) |
||
370 | |||
371 | b1ec912d | Bob Lantz | def do_links( self, _line ): |
372 | c265deed | Bob Lantz | "Report on links"
|
373 | for link in self.mn.links: |
||
374 | print link, link.status()
|
||
375 | |||
376 | b57e5d93 | Brian O'Connor | def do_switch( self, line ): |
377 | "Starts or stops a switch"
|
||
378 | args = line.split() |
||
379 | if len(args) != 2: |
||
380 | b1ec912d | Bob Lantz | error( 'invalid number of args: switch <switch name>'
|
381 | '{start, stop}\n' )
|
||
382 | b57e5d93 | Brian O'Connor | return
|
383 | sw = args[ 0 ]
|
||
384 | command = args[ 1 ]
|
||
385 | 7a3159c9 | Bob Lantz | if sw not in self.mn or self.mn.get( sw ) not in self.mn.switches: |
386 | b57e5d93 | Brian O'Connor | error( 'invalid switch: %s\n' % args[ 1 ] ) |
387 | else:
|
||
388 | sw = args[ 0 ]
|
||
389 | command = args[ 1 ]
|
||
390 | if command == 'start': |
||
391 | self.mn.get( sw ).start( self.mn.controllers ) |
||
392 | elif command == 'stop': |
||
393 | 23dfbe4a | Brian O'Connor | self.mn.get( sw ).stop( deleteIntfs=False ) |
394 | b57e5d93 | Brian O'Connor | else:
|
395 | 7a3159c9 | Bob Lantz | error( 'invalid command: '
|
396 | 'switch <switch name> {start, stop}\n' )
|
||
397 | b57e5d93 | Brian O'Connor | |
398 | 114dcd56 | Brandon Heller | def default( self, line ): |
399 | """Called on an input line when the command prefix is not recognized.
|
||
400 | Overridden to run shell commands when a node is the first CLI argument.
|
||
401 | Past the first CLI argument, node names are automatically replaced with
|
||
402 | 64c451e0 | Bob Lantz | corresponding IP addrs."""
|
403 | 31b43002 | Bob Lantz | |
404 | 114dcd56 | Brandon Heller | first, args, line = self.parseline( line )
|
405 | |||
406 | 8e04a9f8 | Brian O'Connor | if first in self.mn: |
407 | e9a835ac | Brian O'Connor | if not args: |
408 | print "*** Enter a command for node: %s <cmd>" % first |
||
409 | return
|
||
410 | 8e04a9f8 | Brian O'Connor | node = self.mn[ first ]
|
411 | e9a835ac | Brian O'Connor | rest = args.split( ' ' )
|
412 | 114dcd56 | Brandon Heller | # Substitute IP addresses for node names in command
|
413 | c7e86f93 | Brian O'Connor | # If updateIP() returns None, then use node name
|
414 | rest = [ self.mn[ arg ].defaultIntf().updateIP() or arg |
||
415 | 8e04a9f8 | Brian O'Connor | if arg in self.mn else arg |
416 | edf60032 | Brandon Heller | for arg in rest ] |
417 | 114dcd56 | Brandon Heller | rest = ' '.join( rest )
|
418 | # Run cmd on node:
|
||
419 | 549f1ebc | Bob Lantz | node.sendCmd( rest ) |
420 | 82b72072 | Bob Lantz | self.waitForNode( node )
|
421 | 114dcd56 | Brandon Heller | else:
|
422 | e9a835ac | Brian O'Connor | error( '*** Unknown command: %s\n' % line )
|
423 | 114dcd56 | Brandon Heller | |
424 | 82b72072 | Bob Lantz | def waitForNode( self, node ): |
425 | 549f1ebc | Bob Lantz | "Wait for a node to finish, and print its output."
|
426 | f800e512 | Bob Lantz | # Pollers
|
427 | nodePoller = poll() |
||
428 | nodePoller.register( node.stdout ) |
||
429 | bothPoller = poll() |
||
430 | cece39e4 | Bob Lantz | bothPoller.register( self.stdin, POLLIN )
|
431 | bothPoller.register( node.stdout, POLLIN ) |
||
432 | f800e512 | Bob Lantz | if self.isatty(): |
433 | # Buffer by character, so that interactive
|
||
434 | # commands sort of work
|
||
435 | quietRun( 'stty -icanon min 1' )
|
||
436 | while True: |
||
437 | try:
|
||
438 | bothPoller.poll() |
||
439 | 5c24e186 | Bob Lantz | # XXX BL: this doesn't quite do what we want.
|
440 | if False and self.inputFile: |
||
441 | 9d0dbe48 | Bob Lantz | key = self.inputFile.read( 1 ) |
442 | if key is not '': |
||
443 | 549f1ebc | Bob Lantz | node.write( key ) |
444 | 9d0dbe48 | Bob Lantz | else:
|
445 | self.inputFile = None |
||
446 | 82b72072 | Bob Lantz | if isReadable( self.inPoller ): |
447 | f800e512 | Bob Lantz | key = self.stdin.read( 1 ) |
448 | node.write( key ) |
||
449 | 82b72072 | Bob Lantz | if isReadable( nodePoller ):
|
450 | f800e512 | Bob Lantz | data = node.monitor() |
451 | e555f83c | Bob Lantz | output( data ) |
452 | f800e512 | Bob Lantz | if not node.waiting: |
453 | break
|
||
454 | except KeyboardInterrupt: |
||
455 | 549f1ebc | Bob Lantz | # There is an at least one race condition here, since
|
456 | # it's possible to interrupt ourselves after we've
|
||
457 | # read data but before it has been printed.
|
||
458 | f800e512 | Bob Lantz | node.sendInt() |
459 | e91b2815 | Bob Lantz | |
460 | c5e8f09b | cody burkard | def precmd( self, line ): |
461 | "allow for comments in the cli"
|
||
462 | if '#' in line: |
||
463 | line = line.split( '#' )[ 0 ] |
||
464 | return line
|
||
465 | |||
466 | 549f1ebc | Bob Lantz | |
467 | 82b72072 | Bob Lantz | # Helper functions
|
468 | |||
469 | def isReadable( poller ): |
||
470 | "Check whether a Poll object has a readable fd."
|
||
471 | for fdmask in poller.poll( 0 ): |
||
472 | mask = fdmask[ 1 ]
|
||
473 | if mask & POLLIN:
|
||
474 | return True |