X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/99a11adc459e00eab1ce46e455c445f77c7ab80c..398fd4f6560ee66eceaf6969c56f755f0b4f29c1:/tools/ganeti-listrunner?ds=sidebyside diff --git a/tools/ganeti-listrunner b/tools/ganeti-listrunner index 1f63c1d..061d53b 100755 --- a/tools/ganeti-listrunner +++ b/tools/ganeti-listrunner @@ -49,11 +49,11 @@ Security considerations: """ -# pylint: disable-msg=C0103 +# pylint: disable=C0103 # C0103: Invalid name ganeti-listrunner import errno -import getopt +import optparse import getpass import logging import os @@ -64,11 +64,22 @@ import sys import time import traceback -import paramiko +try: + import paramiko +except ImportError: + print >> sys.stderr, \ + ("The \"paramiko\" module could not be imported. Install it from your" + " distribution's repository. The package is usually named" + " \"python-paramiko\".") + sys.exit(1) REMOTE_PATH_BASE = "/tmp/listrunner" +USAGE = ("%prog -l logdir {-c command | -x /path/to/file} [-b batch_size]" + " {-f hostfile|-h hosts} [-u username]" + " [-p password_file | -A]") + def LogDirUseable(logdir): """Ensure log file directory is available and usable.""" @@ -89,22 +100,6 @@ def LogDirUseable(logdir): return False -def ShowHelp(executable): - """Print short usage information.""" - print ("usage: %s -l logdir [-c|-x] value [-b batch_size]" - " [-f hostfile|-h hosts] [-u username]" - " [-p password_file]" % executable) - print """ -l logdir to write logfiles to - -x executable to run on remote host(s) - -c shell command to run on remote host(s) - -f hostlist file (one host per line) - -a optional auxiliary file to upload (can be given multiple times) - -b batch-size, how many hosts to process in parallel [15] - -h comma-separated list of hosts or single hostname - -u username used to connect [root] - -p password used to authenticate""" - - def GetTimeStamp(timestamp=None): """Return ISO8601 timestamp. @@ -243,7 +238,7 @@ def SetupSshConnection(host, username, password, use_agent, logfile): log = logging.getLogger(transport.get_log_channel()) log.addHandler(handler) - transport.connect(username=username, **kwargs) # pylint: disable-msg=W0142 + transport.connect(username=username, **kwargs) # pylint: disable=W0142 WriteLog("ssh connection established using %s" % desc, logfile) # strange ... when establishing the session and the immediately # setting up the channels for sftp & shell from that, it sometimes @@ -279,7 +274,7 @@ def UploadFiles(connection, executable, filelist, logfile): sftp = paramiko.SFTPClient.from_transport(connection) sftp.mkdir(remote_dir, mode=0700) for item in filelist: - remote_file = "%s/%s" % (remote_dir, item.split("/").pop()) + remote_file = "%s/%s" % (remote_dir, os.path.basename(item)) WriteLog("uploading %s to remote %s" % (item, remote_file), logfile) sftp.put(item, remote_file) if item == executable: @@ -299,7 +294,7 @@ def CleanupRemoteDir(connection, upload_dir, filelist, logfile): try: sftp = paramiko.SFTPClient.from_transport(connection) for item in filelist: - fullpath = "%s/%s" % (upload_dir, item.split("/").pop()) + fullpath = "%s/%s" % (upload_dir, os.path.basename(item)) WriteLog("removing remote %s" % fullpath, logfile) sftp.remove(fullpath) sftp.rmdir(upload_dir) @@ -335,15 +330,22 @@ def RunRemoteCommand(connection, command, logfile): ### Read when data is available output = "" while select.select([session], [], []): - data = session.recv(1024) + try: + data = session.recv(1024) + except socket.timeout, err: + data = None + WriteLog("FAILED: socket.timeout %s" % err, logfile) + except socket.error, err: + data = None + WriteLog("FAILED: socket.error %s" % err, logfile) if not data: break output += data select.select([], [], [], .1) WriteLog("SUCCESS: command output follows", logfile) - for line in output.split("\n"): - WriteLog("output = %s" %line, logfile) + for line in output.splitlines(): + WriteLog("output = %s" % line, logfile) WriteLog("command execution completed", logfile) session.close() @@ -351,7 +353,7 @@ def RunRemoteCommand(connection, command, logfile): def HostWorker(logdir, username, password, use_agent, hostname, - executable, command, filelist): + executable, exec_args, command, filelist): """Per-host worker. This function does not return - it's the main code of the childs, @@ -364,6 +366,7 @@ def HostWorker(logdir, username, password, use_agent, hostname, @param use_agent: whether we should instead use an agent @param hostname: the hostname to connect to @param executable: the executable to upload, if not None + @param exec_args: Additional arguments for executable @param command: the command to run @param filelist: auxiliary files to upload @@ -380,8 +383,10 @@ def HostWorker(logdir, username, password, use_agent, hostname, print " %s: uploading files" % hostname upload_dir = UploadFiles(connection, executable, filelist, logfile) - command = "cd %s && ./%s" % (upload_dir, - executable.split("/").pop()) + command = ("cd %s && ./%s" % + (upload_dir, os.path.basename(executable))) + if exec_args: + command += " %s" % exec_args print " %s: executing remote command" % hostname cmd_result = RunRemoteCommand(connection, command, logfile) if cmd_result is True: @@ -417,7 +422,7 @@ def HostWorker(logdir, username, password, use_agent, hostname, def LaunchWorker(child_pids, logdir, username, password, use_agent, hostname, - executable, command, filelist): + executable, exec_args, command, filelist): """Launch the per-host worker. Arguments are the same as for HostWorker, except for child_pids, @@ -431,60 +436,79 @@ def LaunchWorker(child_pids, logdir, username, password, use_agent, hostname, child_pids[pid] = hostname else: HostWorker(logdir, username, password, use_agent, hostname, - executable, command, filelist) + executable, exec_args, command, filelist) + + +def ParseOptions(): + """Parses the command line options. + + In case of command line errors, it will show the usage and exit the + program. + + @return: the options in a tuple + + """ + # resolve because original used -h for hostfile, which conflicts + # with -h for help + parser = optparse.OptionParser(usage="\n%s" % USAGE, + conflict_handler="resolve") + + parser.add_option("-l", dest="logdir", default=None, + help="directory to write logfiles to") + parser.add_option("-x", dest="executable", default=None, + help="executable to run on remote host(s)",) + parser.add_option("-f", dest="hostfile", default=None, + help="hostlist file (one host per line)") + parser.add_option("-h", dest="hostlist", default=None, metavar="HOSTS", + help="comma-separated list of hosts or single hostname",) + parser.add_option("-a", dest="auxfiles", action="append", default=[], + help="optional auxiliary file to upload" + " (can be given multiple times)", + metavar="FILE") + parser.add_option("-c", dest="command", default=None, + help="shell command to run on remote host(s)") + parser.add_option("-b", dest="batch_size", default=15, type="int", + help="batch-size, how many hosts to process" + " in parallel [15]") + parser.add_option("-u", dest="username", default="root", + help="username used to connect [root]") + parser.add_option("-p", dest="password", default=None, + help="password used to authenticate (when not" + " using an agent)") + parser.add_option("-A", dest="use_agent", default=False, action="store_true", + help="instead of password, use keys from an SSH agent") + parser.add_option("--args", dest="exec_args", default=None, + help="Arguments to be passed to executable (-x)") + + opts, args = parser.parse_args() + + if opts.executable and opts.command: + parser.error("Options -x and -c conflict with each other") + if not (opts.executable or opts.command): + parser.error("One of -x and -c must be given") + if opts.command and opts.exec_args: + parser.error("Can't specify arguments when using custom command") + if not opts.logdir: + parser.error("Option -l is required") + if opts.hostfile and opts.hostlist: + parser.error("Options -f and -h conflict with each other") + if not (opts.hostfile or opts.hostlist): + parser.error("One of -f or -h must be given") + if args: + parser.error("This program doesn't take any arguments, passed in: %s" % + ", ".join(args)) + + return (opts.logdir, opts.executable, opts.exec_args, + opts.hostfile, opts.hostlist, + opts.command, opts.use_agent, opts.auxfiles, opts.username, + opts.password, opts.batch_size) def main(): """main.""" - try: - optlist, _ = getopt.getopt(sys.argv[1:], "l:x:h:f:a:c:b:u:p:A") - except getopt.GetoptError, err: - print str(err) - ShowHelp(sys.argv[0]) - sys.exit(2) - - logdir = executable = hostfile = hostlist = command = None - use_agent = False - auxfiles = [] - username = "root" - password = None - batch_size = 15 - for option in optlist: - if option[0] == "-l": - logdir = option[1] - if option[0] == "-x": - executable = option[1] - if option[0] == "-f": - hostfile = option[1] - if option[0] == "-h": - hostlist = option[1] - if option[0] == "-a": - auxfiles.append(option[1]) - if option[0] == "-c": - command = option[1] - if option[0] == "-b": - batch_size = int(option[1]) - if option[0] == "-u": - username = option[1] - if option[0] == "-p": - password = option[1] - if option[0] == "-A": - use_agent = True - - if not (logdir and (executable or command) and (hostfile or hostlist)): - print "error: missing required commandline argument(s)" - ShowHelp(sys.argv[0]) - sys.exit(3) - - if executable and command: - print "error: can run either a command or an executable, not both" - ShowHelp(sys.argv[0]) - sys.exit(3) - - if hostlist and hostfile: - print "error: specify either -f or -h arguments, not both" - ShowHelp(sys.argv[0]) - sys.exit(3) + (logdir, executable, exec_args, hostfile, hostlist, + command, use_agent, auxfiles, username, + password, batch_size) = ParseOptions() ### Unbuffered sys.stdout sys.stdout = os.fdopen(1, "w", 0) @@ -527,7 +551,7 @@ def main(): child_pids = {} for hostname in batch: LaunchWorker(child_pids, logdir, username, password, use_agent, hostname, - executable, command, filelist) + executable, exec_args, command, filelist) while child_pids: pid, status = os.wait() @@ -539,7 +563,7 @@ def main(): failures += 1 if hosts: LaunchWorker(child_pids, logdir, username, password, use_agent, - hosts.pop(0), executable, command, filelist) + hosts.pop(0), executable, exec_args, command, filelist) print print "All done, %s successful and %s failed hosts" % (successes, failures)