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

Unified 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 side-by-side diff with in-line comments
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 »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: third_party/pexpect/examples/hive.py
diff --git a/third_party/pexpect/examples/hive.py b/third_party/pexpect/examples/hive.py
new file mode 100644
index 0000000000000000000000000000000000000000..00ddbeaba9715f5cd1eeacb4859b8f474d07f45e
--- /dev/null
+++ b/third_party/pexpect/examples/hive.py
@@ -0,0 +1,466 @@
+#!/usr/bin/env python
+
+'''hive -- Hive Shell
+
+This lets you ssh to a group of servers and control them as if they were one.
+Each command you enter is sent to each host in parallel. The response of each
+host is collected and printed. In normal synchronous mode Hive will wait for
+each host to return the shell command line prompt. The shell prompt is used to
+sync output.
+
+Example:
+
+ $ hive.py --sameuser --samepass host1.example.com host2.example.net
+ username: myusername
+ password:
+ connecting to host1.example.com - OK
+ connecting to host2.example.net - OK
+ targetting hosts: 192.168.1.104 192.168.1.107
+ CMD (? for help) > uptime
+ =======================================================================
+ host1.example.com
+ -----------------------------------------------------------------------
+ uptime
+ 23:49:55 up 74 days, 5:14, 2 users, load average: 0.15, 0.05, 0.01
+ =======================================================================
+ host2.example.net
+ -----------------------------------------------------------------------
+ uptime
+ 23:53:02 up 1 day, 13:36, 2 users, load average: 0.50, 0.40, 0.46
+ =======================================================================
+
+Other Usage Examples:
+
+1. You will be asked for your username and password for each host.
+
+ hive.py host1 host2 host3 ... hostN
+
+2. You will be asked once for your username and password.
+ This will be used for each host.
+
+ hive.py --sameuser --samepass host1 host2 host3 ... hostN
+
+3. Give a username and password on the command-line:
+
+ hive.py user1:pass2@host1 user2:pass2@host2 ... userN:passN@hostN
+
+You can use an extended host notation to specify username, password, and host
+instead of entering auth information interactively. Where you would enter a
+host name use this format:
+
+ username:password@host
+
+This assumes that ':' is not part of the password. If your password contains a
+':' then you can use '\\:' to indicate a ':' and '\\\\' to indicate a single
+'\\'. Remember that this information will appear in the process listing. Anyone
+on your machine can see this auth information. This is not secure.
+
+This is a crude script that begs to be multithreaded. But it serves its
+purpose.
+
+PEXPECT LICENSE
+
+ This license is approved by the OSI and FSF as GPL-compatible.
+ http://opensource.org/licenses/isc-license.txt
+
+ Copyright (c) 2012, Noah Spurrier <noah@noah.org>
+ PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY
+ PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE
+ COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES.
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+'''
+
+from __future__ import print_function
+
+from __future__ import absolute_import
+
+# TODO add feature to support username:password@host combination
+# TODO add feature to log each host output in separate file
+
+import sys
+import os
+import re
+import optparse
+import time
+import getpass
+import readline
+import atexit
+try:
+ import pexpect
+ import pxssh
+except ImportError:
+ sys.stderr.write("You do not have 'pexpect' installed.\n")
+ sys.stderr.write("On Ubuntu you need the 'python-pexpect' package.\n")
+ sys.stderr.write(" aptitude -y install python-pexpect\n")
+ exit(1)
+
+
+try:
+ raw_input
+except NameError:
+ raw_input = input
+
+
+histfile = os.path.join(os.environ["HOME"], ".hive_history")
+try:
+ readline.read_history_file(histfile)
+except IOError:
+ pass
+atexit.register(readline.write_history_file, histfile)
+
+CMD_HELP='''Hive commands are preceded by a colon : (just think of vi).
+
+:target name1 name2 name3 ...
+
+ set list of hosts to target commands
+
+:target all
+
+ reset list of hosts to target all hosts in the hive.
+
+:to name command
+
+ send a command line to the named host. This is similar to :target, but
+ sends only one command and does not change the list of targets for future
+ commands.
+
+:sync
+
+ set mode to wait for shell prompts after commands are run. This is the
+ default. When Hive first logs into a host it sets a special shell prompt
+ pattern that it can later look for to synchronize output of the hosts. If
+ you 'su' to another user then it can upset the synchronization. If you need
+ to run something like 'su' then use the following pattern:
+
+ CMD (? for help) > :async
+ CMD (? for help) > sudo su - root
+ CMD (? for help) > :prompt
+ CMD (? for help) > :sync
+
+:async
+
+ set mode to not expect command line prompts (see :sync). Afterwards
+ commands are send to target hosts, but their responses are not read back
+ until :sync is run. This is useful to run before commands that will not
+ return with the special shell prompt pattern that Hive uses to synchronize.
+
+:refresh
+
+ refresh the display. This shows the last few lines of output from all hosts.
+ This is similar to resync, but does not expect the promt. This is useful
+ for seeing what hosts are doing during long running commands.
+
+:resync
+
+ This is similar to :sync, but it does not change the mode. It looks for the
+ prompt and thus consumes all input from all targetted hosts.
+
+:prompt
+
+ force each host to reset command line prompt to the special pattern used to
+ synchronize all the hosts. This is useful if you 'su' to a different user
+ where Hive would not know the prompt to match.
+
+:send my text
+
+ This will send the 'my text' wihtout a line feed to the targetted hosts.
+ This output of the hosts is not automatically synchronized.
+
+:control X
+
+ This will send the given control character to the targetted hosts.
+ For example, ":control c" will send ASCII 3.
+
+:exit
+
+ This will exit the hive shell.
+
+'''
+
+def login (args, cli_username=None, cli_password=None):
+
+ # I have to keep a separate list of host names because Python dicts are not ordered.
+ # I want to keep the same order as in the args list.
+ host_names = []
+ hive_connect_info = {}
+ hive = {}
+ # build up the list of connection information (hostname, username, password, port)
+ for host_connect_string in args:
+ hcd = parse_host_connect_string (host_connect_string)
+ hostname = hcd['hostname']
+ port = hcd['port']
+ if port == '':
+ port = None
+ if len(hcd['username']) > 0:
+ username = hcd['username']
+ elif cli_username is not None:
+ username = cli_username
+ else:
+ username = raw_input('%s username: ' % hostname)
+ if len(hcd['password']) > 0:
+ password = hcd['password']
+ elif cli_password is not None:
+ password = cli_password
+ else:
+ password = getpass.getpass('%s password: ' % hostname)
+ host_names.append(hostname)
+ hive_connect_info[hostname] = (hostname, username, password, port)
+ # build up the list of hive connections using the connection information.
+ for hostname in host_names:
+ print('connecting to', hostname)
+ try:
+ fout = file("log_"+hostname, "w")
+ hive[hostname] = pxssh.pxssh()
+ # Disable host key checking.
+ hive[hostname].SSH_OPTS = (hive[hostname].SSH_OPTS
+ + " -o 'StrictHostKeyChecking=no'"
+ + " -o 'UserKnownHostsFile /dev/null' ")
+ hive[hostname].force_password = True
+ hive[hostname].login(*hive_connect_info[hostname])
+ print(hive[hostname].before)
+ hive[hostname].logfile = fout
+ print('- OK')
+ except Exception as e:
+ print('- ERROR', end=' ')
+ print(str(e))
+ print('Skipping', hostname)
+ hive[hostname] = None
+ return host_names, hive
+
+def main ():
+
+ global options, args, CMD_HELP
+
+ rows = 24
+ cols = 80
+
+ if options.sameuser:
+ cli_username = raw_input('username: ')
+ else:
+ cli_username = None
+
+ if options.samepass:
+ cli_password = getpass.getpass('password: ')
+ else:
+ cli_password = None
+
+ host_names, hive = login(args, cli_username, cli_password)
+
+ synchronous_mode = True
+ target_hostnames = host_names[:]
+ print('targetting hosts:', ' '.join(target_hostnames))
+ while True:
+ cmd = raw_input('CMD (? for help) > ')
+ cmd = cmd.strip()
+ if cmd=='?' or cmd==':help' or cmd==':h':
+ print(CMD_HELP)
+ continue
+ elif cmd==':refresh':
+ refresh (hive, target_hostnames, timeout=0.5)
+ for hostname in target_hostnames:
+ print('/' + '=' * (cols - 2))
+ print('| ' + hostname)
+ print('\\' + '-' * (cols - 2))
+ if hive[hostname] is None:
+ print('# DEAD: %s' % hostname)
+ else:
+ print(hive[hostname].before)
+ print('#' * 79)
+ continue
+ elif cmd==':resync':
+ resync (hive, target_hostnames, timeout=0.5)
+ for hostname in target_hostnames:
+ print('/' + '=' * (cols - 2))
+ print('| ' + hostname)
+ print('\\' + '-' * (cols - 2))
+ if hive[hostname] is None:
+ print('# DEAD: %s' % hostname)
+ else:
+ print(hive[hostname].before)
+ print('#' * 79)
+ continue
+ elif cmd==':sync':
+ synchronous_mode = True
+ resync (hive, target_hostnames, timeout=0.5)
+ continue
+ elif cmd==':async':
+ synchronous_mode = False
+ continue
+ elif cmd==':prompt':
+ for hostname in target_hostnames:
+ try:
+ if hive[hostname] is not None:
+ hive[hostname].set_unique_prompt()
+ except Exception as e:
+ print("Had trouble communicating with %s, so removing it from the target list." % hostname)
+ print(str(e))
+ hive[hostname] = None
+ continue
+ elif cmd[:5] == ':send':
+ cmd, txt = cmd.split(None,1)
+ for hostname in target_hostnames:
+ try:
+ if hive[hostname] is not None:
+ hive[hostname].send(txt)
+ except Exception as e:
+ print("Had trouble communicating with %s, so removing it from the target list." % hostname)
+ print(str(e))
+ hive[hostname] = None
+ continue
+ elif cmd[:3] == ':to':
+ cmd, hostname, txt = cmd.split(None,2)
+ print('/' + '=' * (cols - 2))
+ print('| ' + hostname)
+ print('\\' + '-' * (cols - 2))
+ if hive[hostname] is None:
+ print('# DEAD: %s' % hostname)
+ continue
+ try:
+ hive[hostname].sendline (txt)
+ hive[hostname].prompt(timeout=2)
+ print(hive[hostname].before)
+ except Exception as e:
+ print("Had trouble communicating with %s, so removing it from the target list." % hostname)
+ print(str(e))
+ hive[hostname] = None
+ continue
+ elif cmd[:7] == ':expect':
+ cmd, pattern = cmd.split(None,1)
+ print('looking for', pattern)
+ try:
+ for hostname in target_hostnames:
+ if hive[hostname] is not None:
+ hive[hostname].expect(pattern)
+ print(hive[hostname].before)
+ except Exception as e:
+ print("Had trouble communicating with %s, so removing it from the target list." % hostname)
+ print(str(e))
+ hive[hostname] = None
+ continue
+ elif cmd[:7] == ':target':
+ target_hostnames = cmd.split()[1:]
+ if len(target_hostnames) == 0 or target_hostnames[0] == all:
+ target_hostnames = host_names[:]
+ print('targetting hosts:', ' '.join(target_hostnames))
+ continue
+ elif cmd == ':exit' or cmd == ':q' or cmd == ':quit':
+ break
+ elif cmd[:8] == ':control' or cmd[:5] == ':ctrl' :
+ cmd, c = cmd.split(None,1)
+ if ord(c)-96 < 0 or ord(c)-96 > 255:
+ print('/' + '=' * (cols - 2))
+ print('| Invalid character. Must be [a-zA-Z], @, [, ], \\, ^, _, or ?')
+ print('\\' + '-' * (cols - 2))
+ continue
+ for hostname in target_hostnames:
+ try:
+ if hive[hostname] is not None:
+ hive[hostname].sendcontrol(c)
+ except Exception as e:
+ print("Had trouble communicating with %s, so removing it from the target list." % hostname)
+ print(str(e))
+ hive[hostname] = None
+ continue
+ elif cmd == ':esc':
+ for hostname in target_hostnames:
+ if hive[hostname] is not None:
+ hive[hostname].send(chr(27))
+ continue
+ #
+ # Run the command on all targets in parallel
+ #
+ for hostname in target_hostnames:
+ try:
+ if hive[hostname] is not None:
+ hive[hostname].sendline (cmd)
+ except Exception as e:
+ print("Had trouble communicating with %s, so removing it from the target list." % hostname)
+ print(str(e))
+ hive[hostname] = None
+
+ #
+ # print the response for each targeted host.
+ #
+ if synchronous_mode:
+ for hostname in target_hostnames:
+ try:
+ print('/' + '=' * (cols - 2))
+ print('| ' + hostname)
+ print('\\' + '-' * (cols - 2))
+ if hive[hostname] is None:
+ print('# DEAD: %s' % hostname)
+ else:
+ hive[hostname].prompt(timeout=2)
+ print(hive[hostname].before)
+ except Exception as e:
+ print("Had trouble communicating with %s, so removing it from the target list." % hostname)
+ print(str(e))
+ hive[hostname] = None
+ print('#' * 79)
+
+def refresh (hive, hive_names, timeout=0.5):
+
+ '''This waits for the TIMEOUT on each host.
+ '''
+
+ # TODO This is ideal for threading.
+ for hostname in hive_names:
+ if hive[hostname] is not None:
+ hive[hostname].expect([pexpect.TIMEOUT,pexpect.EOF],timeout=timeout)
+
+def resync (hive, hive_names, timeout=2, max_attempts=5):
+
+ '''This waits for the shell prompt for each host in an effort to try to get
+ them all to the same state. The timeout is set low so that hosts that are
+ already at the prompt will not slow things down too much. If a prompt match
+ is made for a hosts then keep asking until it stops matching. This is a
+ best effort to consume all input if it printed more than one prompt. It's
+ kind of kludgy. Note that this will always introduce a delay equal to the
+ timeout for each machine. So for 10 machines with a 2 second delay you will
+ get AT LEAST a 20 second delay if not more. '''
+
+ # TODO This is ideal for threading.
+ for hostname in hive_names:
+ if hive[hostname] is not None:
+ for attempts in range(0, max_attempts):
+ if not hive[hostname].prompt(timeout=timeout):
+ break
+
+def parse_host_connect_string (hcs):
+
+ '''This parses a host connection string in the form
+ username:password@hostname:port. All fields are options expcet hostname. A
+ dictionary is returned with all four keys. Keys that were not included are
+ set to empty strings ''. Note that if your password has the '@' character
+ then you must backslash escape it. '''
+
+ if '@' in hcs:
+ p = re.compile (r'(?P<username>[^@:]*)(:?)(?P<password>.*)(?!\\)@(?P<hostname>[^:]*):?(?P<port>[0-9]*)')
+ else:
+ p = re.compile (r'(?P<username>)(?P<password>)(?P<hostname>[^:]*):?(?P<port>[0-9]*)')
+ m = p.search (hcs)
+ d = m.groupdict()
+ d['password'] = d['password'].replace('\\@','@')
+ return d
+
+if __name__ == '__main__':
+ start_time = time.time()
+ parser = optparse.OptionParser(formatter=optparse.TitledHelpFormatter(), usage=globals()['__doc__'], version='$Id: hive.py 533 2012-10-20 02:19:33Z noah $',conflict_handler="resolve")
+ parser.add_option ('-v', '--verbose', action='store_true', default=False, help='verbose output')
+ parser.add_option ('--samepass', action='store_true', default=False, help='Use same password for each login.')
+ parser.add_option ('--sameuser', action='store_true', default=False, help='Use same username for each login.')
+ (options, args) = parser.parse_args()
+ if len(args) < 1:
+ parser.error ('missing argument')
+ if options.verbose: print(time.asctime())
+ main()
+ if options.verbose: print(time.asctime())
+ if options.verbose: print('TOTAL TIME IN MINUTES:', end=' ')
+ if options.verbose: print((time.time() - start_time) / 60.0)
« 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