Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(896)

Side by Side Diff: third_party/pexpect/examples/hive.py

Issue 1398903002: Add third_party/pexpect (Closed) Base URL: https://chromium.googlesource.com/crashpad/crashpad@end-to-end-test
Patch Set: Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « third_party/pexpect/examples/ftp.py ('k') | third_party/pexpect/examples/monitor.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/env python
2
3 '''hive -- Hive Shell
4
5 This lets you ssh to a group of servers and control them as if they were one.
6 Each command you enter is sent to each host in parallel. The response of each
7 host is collected and printed. In normal synchronous mode Hive will wait for
8 each host to return the shell command line prompt. The shell prompt is used to
9 sync output.
10
11 Example:
12
13 $ hive.py --sameuser --samepass host1.example.com host2.example.net
14 username: myusername
15 password:
16 connecting to host1.example.com - OK
17 connecting to host2.example.net - OK
18 targetting hosts: 192.168.1.104 192.168.1.107
19 CMD (? for help) > uptime
20 =======================================================================
21 host1.example.com
22 -----------------------------------------------------------------------
23 uptime
24 23:49:55 up 74 days, 5:14, 2 users, load average: 0.15, 0.05, 0.01
25 =======================================================================
26 host2.example.net
27 -----------------------------------------------------------------------
28 uptime
29 23:53:02 up 1 day, 13:36, 2 users, load average: 0.50, 0.40, 0.46
30 =======================================================================
31
32 Other Usage Examples:
33
34 1. You will be asked for your username and password for each host.
35
36 hive.py host1 host2 host3 ... hostN
37
38 2. You will be asked once for your username and password.
39 This will be used for each host.
40
41 hive.py --sameuser --samepass host1 host2 host3 ... hostN
42
43 3. Give a username and password on the command-line:
44
45 hive.py user1:pass2@host1 user2:pass2@host2 ... userN:passN@hostN
46
47 You can use an extended host notation to specify username, password, and host
48 instead of entering auth information interactively. Where you would enter a
49 host name use this format:
50
51 username:password@host
52
53 This assumes that ':' is not part of the password. If your password contains a
54 ':' then you can use '\\:' to indicate a ':' and '\\\\' to indicate a single
55 '\\'. Remember that this information will appear in the process listing. Anyone
56 on your machine can see this auth information. This is not secure.
57
58 This is a crude script that begs to be multithreaded. But it serves its
59 purpose.
60
61 PEXPECT LICENSE
62
63 This license is approved by the OSI and FSF as GPL-compatible.
64 http://opensource.org/licenses/isc-license.txt
65
66 Copyright (c) 2012, Noah Spurrier <noah@noah.org>
67 PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY
68 PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE
69 COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES.
70 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
71 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
72 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
73 ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
74 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
75 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
76 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
77
78 '''
79
80 from __future__ import print_function
81
82 from __future__ import absolute_import
83
84 # TODO add feature to support username:password@host combination
85 # TODO add feature to log each host output in separate file
86
87 import sys
88 import os
89 import re
90 import optparse
91 import time
92 import getpass
93 import readline
94 import atexit
95 try:
96 import pexpect
97 import pxssh
98 except ImportError:
99 sys.stderr.write("You do not have 'pexpect' installed.\n")
100 sys.stderr.write("On Ubuntu you need the 'python-pexpect' package.\n")
101 sys.stderr.write(" aptitude -y install python-pexpect\n")
102 exit(1)
103
104
105 try:
106 raw_input
107 except NameError:
108 raw_input = input
109
110
111 histfile = os.path.join(os.environ["HOME"], ".hive_history")
112 try:
113 readline.read_history_file(histfile)
114 except IOError:
115 pass
116 atexit.register(readline.write_history_file, histfile)
117
118 CMD_HELP='''Hive commands are preceded by a colon : (just think of vi).
119
120 :target name1 name2 name3 ...
121
122 set list of hosts to target commands
123
124 :target all
125
126 reset list of hosts to target all hosts in the hive.
127
128 :to name command
129
130 send a command line to the named host. This is similar to :target, but
131 sends only one command and does not change the list of targets for future
132 commands.
133
134 :sync
135
136 set mode to wait for shell prompts after commands are run. This is the
137 default. When Hive first logs into a host it sets a special shell prompt
138 pattern that it can later look for to synchronize output of the hosts. If
139 you 'su' to another user then it can upset the synchronization. If you need
140 to run something like 'su' then use the following pattern:
141
142 CMD (? for help) > :async
143 CMD (? for help) > sudo su - root
144 CMD (? for help) > :prompt
145 CMD (? for help) > :sync
146
147 :async
148
149 set mode to not expect command line prompts (see :sync). Afterwards
150 commands are send to target hosts, but their responses are not read back
151 until :sync is run. This is useful to run before commands that will not
152 return with the special shell prompt pattern that Hive uses to synchronize.
153
154 :refresh
155
156 refresh the display. This shows the last few lines of output from all hosts.
157 This is similar to resync, but does not expect the promt. This is useful
158 for seeing what hosts are doing during long running commands.
159
160 :resync
161
162 This is similar to :sync, but it does not change the mode. It looks for the
163 prompt and thus consumes all input from all targetted hosts.
164
165 :prompt
166
167 force each host to reset command line prompt to the special pattern used to
168 synchronize all the hosts. This is useful if you 'su' to a different user
169 where Hive would not know the prompt to match.
170
171 :send my text
172
173 This will send the 'my text' wihtout a line feed to the targetted hosts.
174 This output of the hosts is not automatically synchronized.
175
176 :control X
177
178 This will send the given control character to the targetted hosts.
179 For example, ":control c" will send ASCII 3.
180
181 :exit
182
183 This will exit the hive shell.
184
185 '''
186
187 def login (args, cli_username=None, cli_password=None):
188
189 # I have to keep a separate list of host names because Python dicts are not ordered.
190 # I want to keep the same order as in the args list.
191 host_names = []
192 hive_connect_info = {}
193 hive = {}
194 # build up the list of connection information (hostname, username, password, port)
195 for host_connect_string in args:
196 hcd = parse_host_connect_string (host_connect_string)
197 hostname = hcd['hostname']
198 port = hcd['port']
199 if port == '':
200 port = None
201 if len(hcd['username']) > 0:
202 username = hcd['username']
203 elif cli_username is not None:
204 username = cli_username
205 else:
206 username = raw_input('%s username: ' % hostname)
207 if len(hcd['password']) > 0:
208 password = hcd['password']
209 elif cli_password is not None:
210 password = cli_password
211 else:
212 password = getpass.getpass('%s password: ' % hostname)
213 host_names.append(hostname)
214 hive_connect_info[hostname] = (hostname, username, password, port)
215 # build up the list of hive connections using the connection information.
216 for hostname in host_names:
217 print('connecting to', hostname)
218 try:
219 fout = file("log_"+hostname, "w")
220 hive[hostname] = pxssh.pxssh()
221 # Disable host key checking.
222 hive[hostname].SSH_OPTS = (hive[hostname].SSH_OPTS
223 + " -o 'StrictHostKeyChecking=no'"
224 + " -o 'UserKnownHostsFile /dev/null' ")
225 hive[hostname].force_password = True
226 hive[hostname].login(*hive_connect_info[hostname])
227 print(hive[hostname].before)
228 hive[hostname].logfile = fout
229 print('- OK')
230 except Exception as e:
231 print('- ERROR', end=' ')
232 print(str(e))
233 print('Skipping', hostname)
234 hive[hostname] = None
235 return host_names, hive
236
237 def main ():
238
239 global options, args, CMD_HELP
240
241 rows = 24
242 cols = 80
243
244 if options.sameuser:
245 cli_username = raw_input('username: ')
246 else:
247 cli_username = None
248
249 if options.samepass:
250 cli_password = getpass.getpass('password: ')
251 else:
252 cli_password = None
253
254 host_names, hive = login(args, cli_username, cli_password)
255
256 synchronous_mode = True
257 target_hostnames = host_names[:]
258 print('targetting hosts:', ' '.join(target_hostnames))
259 while True:
260 cmd = raw_input('CMD (? for help) > ')
261 cmd = cmd.strip()
262 if cmd=='?' or cmd==':help' or cmd==':h':
263 print(CMD_HELP)
264 continue
265 elif cmd==':refresh':
266 refresh (hive, target_hostnames, timeout=0.5)
267 for hostname in target_hostnames:
268 print('/' + '=' * (cols - 2))
269 print('| ' + hostname)
270 print('\\' + '-' * (cols - 2))
271 if hive[hostname] is None:
272 print('# DEAD: %s' % hostname)
273 else:
274 print(hive[hostname].before)
275 print('#' * 79)
276 continue
277 elif cmd==':resync':
278 resync (hive, target_hostnames, timeout=0.5)
279 for hostname in target_hostnames:
280 print('/' + '=' * (cols - 2))
281 print('| ' + hostname)
282 print('\\' + '-' * (cols - 2))
283 if hive[hostname] is None:
284 print('# DEAD: %s' % hostname)
285 else:
286 print(hive[hostname].before)
287 print('#' * 79)
288 continue
289 elif cmd==':sync':
290 synchronous_mode = True
291 resync (hive, target_hostnames, timeout=0.5)
292 continue
293 elif cmd==':async':
294 synchronous_mode = False
295 continue
296 elif cmd==':prompt':
297 for hostname in target_hostnames:
298 try:
299 if hive[hostname] is not None:
300 hive[hostname].set_unique_prompt()
301 except Exception as e:
302 print("Had trouble communicating with %s, so removing it fro m the target list." % hostname)
303 print(str(e))
304 hive[hostname] = None
305 continue
306 elif cmd[:5] == ':send':
307 cmd, txt = cmd.split(None,1)
308 for hostname in target_hostnames:
309 try:
310 if hive[hostname] is not None:
311 hive[hostname].send(txt)
312 except Exception as e:
313 print("Had trouble communicating with %s, so removing it fro m the target list." % hostname)
314 print(str(e))
315 hive[hostname] = None
316 continue
317 elif cmd[:3] == ':to':
318 cmd, hostname, txt = cmd.split(None,2)
319 print('/' + '=' * (cols - 2))
320 print('| ' + hostname)
321 print('\\' + '-' * (cols - 2))
322 if hive[hostname] is None:
323 print('# DEAD: %s' % hostname)
324 continue
325 try:
326 hive[hostname].sendline (txt)
327 hive[hostname].prompt(timeout=2)
328 print(hive[hostname].before)
329 except Exception as e:
330 print("Had trouble communicating with %s, so removing it from th e target list." % hostname)
331 print(str(e))
332 hive[hostname] = None
333 continue
334 elif cmd[:7] == ':expect':
335 cmd, pattern = cmd.split(None,1)
336 print('looking for', pattern)
337 try:
338 for hostname in target_hostnames:
339 if hive[hostname] is not None:
340 hive[hostname].expect(pattern)
341 print(hive[hostname].before)
342 except Exception as e:
343 print("Had trouble communicating with %s, so removing it from th e target list." % hostname)
344 print(str(e))
345 hive[hostname] = None
346 continue
347 elif cmd[:7] == ':target':
348 target_hostnames = cmd.split()[1:]
349 if len(target_hostnames) == 0 or target_hostnames[0] == all:
350 target_hostnames = host_names[:]
351 print('targetting hosts:', ' '.join(target_hostnames))
352 continue
353 elif cmd == ':exit' or cmd == ':q' or cmd == ':quit':
354 break
355 elif cmd[:8] == ':control' or cmd[:5] == ':ctrl' :
356 cmd, c = cmd.split(None,1)
357 if ord(c)-96 < 0 or ord(c)-96 > 255:
358 print('/' + '=' * (cols - 2))
359 print('| Invalid character. Must be [a-zA-Z], @, [, ], \\, ^, _, or ?')
360 print('\\' + '-' * (cols - 2))
361 continue
362 for hostname in target_hostnames:
363 try:
364 if hive[hostname] is not None:
365 hive[hostname].sendcontrol(c)
366 except Exception as e:
367 print("Had trouble communicating with %s, so removing it fro m the target list." % hostname)
368 print(str(e))
369 hive[hostname] = None
370 continue
371 elif cmd == ':esc':
372 for hostname in target_hostnames:
373 if hive[hostname] is not None:
374 hive[hostname].send(chr(27))
375 continue
376 #
377 # Run the command on all targets in parallel
378 #
379 for hostname in target_hostnames:
380 try:
381 if hive[hostname] is not None:
382 hive[hostname].sendline (cmd)
383 except Exception as e:
384 print("Had trouble communicating with %s, so removing it from th e target list." % hostname)
385 print(str(e))
386 hive[hostname] = None
387
388 #
389 # print the response for each targeted host.
390 #
391 if synchronous_mode:
392 for hostname in target_hostnames:
393 try:
394 print('/' + '=' * (cols - 2))
395 print('| ' + hostname)
396 print('\\' + '-' * (cols - 2))
397 if hive[hostname] is None:
398 print('# DEAD: %s' % hostname)
399 else:
400 hive[hostname].prompt(timeout=2)
401 print(hive[hostname].before)
402 except Exception as e:
403 print("Had trouble communicating with %s, so removing it fro m the target list." % hostname)
404 print(str(e))
405 hive[hostname] = None
406 print('#' * 79)
407
408 def refresh (hive, hive_names, timeout=0.5):
409
410 '''This waits for the TIMEOUT on each host.
411 '''
412
413 # TODO This is ideal for threading.
414 for hostname in hive_names:
415 if hive[hostname] is not None:
416 hive[hostname].expect([pexpect.TIMEOUT,pexpect.EOF],timeout=timeout)
417
418 def resync (hive, hive_names, timeout=2, max_attempts=5):
419
420 '''This waits for the shell prompt for each host in an effort to try to get
421 them all to the same state. The timeout is set low so that hosts that are
422 already at the prompt will not slow things down too much. If a prompt match
423 is made for a hosts then keep asking until it stops matching. This is a
424 best effort to consume all input if it printed more than one prompt. It's
425 kind of kludgy. Note that this will always introduce a delay equal to the
426 timeout for each machine. So for 10 machines with a 2 second delay you will
427 get AT LEAST a 20 second delay if not more. '''
428
429 # TODO This is ideal for threading.
430 for hostname in hive_names:
431 if hive[hostname] is not None:
432 for attempts in range(0, max_attempts):
433 if not hive[hostname].prompt(timeout=timeout):
434 break
435
436 def parse_host_connect_string (hcs):
437
438 '''This parses a host connection string in the form
439 username:password@hostname:port. All fields are options expcet hostname. A
440 dictionary is returned with all four keys. Keys that were not included are
441 set to empty strings ''. Note that if your password has the '@' character
442 then you must backslash escape it. '''
443
444 if '@' in hcs:
445 p = re.compile (r'(?P<username>[^@:]*)(:?)(?P<password>.*)(?!\\)@(?P<hos tname>[^:]*):?(?P<port>[0-9]*)')
446 else:
447 p = re.compile (r'(?P<username>)(?P<password>)(?P<hostname>[^:]*):?(?P<p ort>[0-9]*)')
448 m = p.search (hcs)
449 d = m.groupdict()
450 d['password'] = d['password'].replace('\\@','@')
451 return d
452
453 if __name__ == '__main__':
454 start_time = time.time()
455 parser = optparse.OptionParser(formatter=optparse.TitledHelpFormatter(), usa ge=globals()['__doc__'], version='$Id: hive.py 533 2012-10-20 02:19:33Z noah $', conflict_handler="resolve")
456 parser.add_option ('-v', '--verbose', action='store_true', default=False, he lp='verbose output')
457 parser.add_option ('--samepass', action='store_true', default=False, help='U se same password for each login.')
458 parser.add_option ('--sameuser', action='store_true', default=False, help='U se same username for each login.')
459 (options, args) = parser.parse_args()
460 if len(args) < 1:
461 parser.error ('missing argument')
462 if options.verbose: print(time.asctime())
463 main()
464 if options.verbose: print(time.asctime())
465 if options.verbose: print('TOTAL TIME IN MINUTES:', end=' ')
466 if options.verbose: print((time.time() - start_time) / 60.0)
OLDNEW
« no previous file with comments | « third_party/pexpect/examples/ftp.py ('k') | third_party/pexpect/examples/monitor.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698