diff --git a/qa/ganeti-qa.py b/qa/ganeti-qa.py index 3d61634146e8471f96cbe1d3454f3d8112b22850..6dc909dcf75bb89e3d6124270c447568626d1d8f 100755 --- a/qa/ganeti-qa.py +++ b/qa/ganeti-qa.py @@ -1,7 +1,7 @@ #!/usr/bin/python -u # -# Copyright (C) 2007, 2008, 2009, 2010 Google Inc. +# Copyright (C) 2007, 2008, 2009, 2010, 2011 Google Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -354,30 +354,10 @@ def RunHardwareFailureTests(instance, pnode, snode): pnode, snode) -@rapi.client.UsesRapiClient -def main(): - """Main program. +def RunQa(): + """Main QA body. """ - parser = optparse.OptionParser(usage="%prog [options] <config-file>") - parser.add_option('--yes-do-it', dest='yes_do_it', - action="store_true", - help="Really execute the tests") - (qa_config.options, args) = parser.parse_args() - - if len(args) == 1: - (config_file, ) = args - else: - parser.error("Wrong number of arguments.") - - if not qa_config.options.yes_do_it: - print ("Executing this script irreversibly destroys any Ganeti\n" - "configuration on all nodes involved. If you really want\n" - "to start testing, supply the --yes-do-it option.") - sys.exit(1) - - qa_config.Load(config_file) - rapi_user = "ganeti-qa" rapi_secret = utils.GenerateSecret() @@ -476,5 +456,35 @@ def main(): RunTestIf("cluster-destroy", qa_cluster.TestClusterDestroy) +@rapi.client.UsesRapiClient +def main(): + """Main program. + + """ + parser = optparse.OptionParser(usage="%prog [options] <config-file>") + parser.add_option('--yes-do-it', dest='yes_do_it', + action="store_true", + help="Really execute the tests") + (qa_config.options, args) = parser.parse_args() + + if len(args) == 1: + (config_file, ) = args + else: + parser.error("Wrong number of arguments.") + + if not qa_config.options.yes_do_it: + print ("Executing this script irreversibly destroys any Ganeti\n" + "configuration on all nodes involved. If you really want\n" + "to start testing, supply the --yes-do-it option.") + sys.exit(1) + + qa_config.Load(config_file) + + qa_utils.StartMultiplexer(qa_config.GetMasterNode()["primary"]) + try: + RunQa() + finally: + qa_utils.CloseMultiplexers() + if __name__ == '__main__': main() diff --git a/qa/qa_utils.py b/qa/qa_utils.py index 110f4a38c1e38a1468a08257618ceb697a7db0a3..20856f9d420f68b99d3d6e4f1709eacb54a27e80 100644 --- a/qa/qa_utils.py +++ b/qa/qa_utils.py @@ -1,7 +1,7 @@ # # -# Copyright (C) 2007 Google Inc. +# Copyright (C) 2007, 2011 Google Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -28,6 +28,7 @@ import re import sys import subprocess import random +import tempfile from ganeti import utils from ganeti import compat @@ -42,6 +43,8 @@ _WARNING_SEQ = None _ERROR_SEQ = None _RESET_SEQ = None +_MULTIPLEXERS = {} + def _SetupColours(): """Initializes the colour constants. @@ -143,15 +146,18 @@ def AssertCommand(cmd, fail=False, node=None): return rcode -def GetSSHCommand(node, cmd, strict=True): +def GetSSHCommand(node, cmd, strict=True, opts=None): """Builds SSH command to be executed. @type node: string @param node: node the command should run on @type cmd: string - @param cmd: command to be executed in the node + @param cmd: command to be executed in the node; if None or empty + string, no command will be executed @type strict: boolean @param strict: whether to enable strict host key checking + @type opts: list + @param opts: list of additional options """ args = [ 'ssh', '-oEscapeChar=none', '-oBatchMode=yes', '-l', 'root', '-t' ] @@ -163,8 +169,15 @@ def GetSSHCommand(node, cmd, strict=True): args.append('-oStrictHostKeyChecking=%s' % tmp) args.append('-oClearAllForwardings=yes') args.append('-oForwardAgent=yes') + if opts: + args.extend(opts) + if node in _MULTIPLEXERS: + spath = _MULTIPLEXERS[node][0] + args.append('-oControlPath=%s' % spath) + args.append('-oControlMaster=no') args.append(node) - args.append(cmd) + if cmd: + args.append(cmd) return args @@ -184,6 +197,34 @@ def StartSSH(node, cmd, strict=True): return StartLocalCommand(GetSSHCommand(node, cmd, strict=strict)) +def StartMultiplexer(node): + """Starts a multiplexer command. + + @param node: the node for which to open the multiplexer + + """ + if node in _MULTIPLEXERS: + return + + # Note: yes, we only need mktemp, since we'll remove the file anyway + sname = tempfile.mktemp(prefix="ganeti-qa-multiplexer.") + utils.RemoveFile(sname) + opts = ["-N", "-oControlPath=%s" % sname, "-oControlMaster=yes"] + print "Created socket at %s" % sname + child = StartLocalCommand(GetSSHCommand(node, None, opts=opts)) + _MULTIPLEXERS[node] = (sname, child) + + +def CloseMultiplexers(): + """Closes all current multiplexers and cleans up. + + """ + for node in _MULTIPLEXERS.keys(): + (sname, child) = _MULTIPLEXERS.pop(node) + utils.KillProcess(child.pid, timeout=10, waitpid=True) + utils.RemoveFile(sname) + + def GetCommandOutput(node, cmd): """Returns the output of a command executed on the given node.