import subprocess
import random
import tempfile
+import operator
try:
import functools
from ganeti import constants
from ganeti import ht
from ganeti import pathutils
+from ganeti import vcluster
import qa_config
import qa_error
#: Unique ID per QA run
_RUN_UUID = utils.NewUUID()
+#: Path to the QA query output log file
+_QA_OUTPUT = pathutils.GetLogFilename("qa-output")
+
(INST_DOWN,
INST_UP) = range(500, 502)
raise qa_error.Error("%r doesn't match /%r/" % (string, pattern))
-def _GetName(entity, key):
+def _GetName(entity, fn):
"""Tries to get name of an entity.
@type entity: string or dict
- @type key: string
- @param key: Dictionary key containing name
+ @param fn: Function retrieving name from entity
"""
if isinstance(entity, basestring):
result = entity
- elif isinstance(entity, dict):
- result = entity[key]
else:
- raise qa_error.Error("Expected string or dictionary, got %s: %s" %
- (type(entity), entity))
+ result = fn(entity)
if not ht.TNonEmptyString(result):
raise Exception("Invalid name '%s'" % result)
(cmdstr, nodename, rcode))
-def AssertCommand(cmd, fail=False, node=None):
+def AssertCommand(cmd, fail=False, node=None, log_cmd=True):
"""Checks that a remote command succeeds.
@param cmd: either a string (the command to execute) or a list (to
@param node: if passed, it should be the node on which the command
should be executed, instead of the master node (can be either a
dict or a string)
+ @param log_cmd: if False, the command won't be logged (simply passed to
+ StartSSH)
@return: the return code of the command
@raise qa_error.Error: if the command fails when it shouldn't or vice versa
if node is None:
node = qa_config.GetMasterNode()
- nodename = _GetName(node, "primary")
+ nodename = _GetName(node, operator.attrgetter("primary"))
if isinstance(cmd, basestring):
cmdstr = cmd
else:
cmdstr = utils.ShellQuoteArgs(cmd)
- rcode = StartSSH(nodename, cmdstr).wait()
+ rcode = StartSSH(nodename, cmdstr, log_cmd=log_cmd).wait()
_AssertRetCode(rcode, fail, cmdstr, nodename)
return rcode
+def AssertRedirectedCommand(cmd, fail=False, node=None, log_cmd=True):
+ """Executes a command with redirected output.
+
+ The log will go to the qa-output log file in the ganeti log
+ directory on the node where the command is executed. The fail and
+ node parameters are passed unchanged to AssertCommand.
+
+ @param cmd: the command to be executed, as a list; a string is not
+ supported
+
+ """
+ if not isinstance(cmd, list):
+ raise qa_error.Error("Non-list passed to AssertRedirectedCommand")
+ ofile = utils.ShellQuote(_QA_OUTPUT)
+ cmdstr = utils.ShellQuoteArgs(cmd)
+ AssertCommand("echo ---- $(date) %s ---- >> %s" % (cmdstr, ofile),
+ fail=False, node=node, log_cmd=False)
+ return AssertCommand(cmdstr + " >> %s" % ofile,
+ fail=fail, node=node, log_cmd=log_cmd)
+
+
def GetSSHCommand(node, cmd, strict=True, opts=None, tty=None):
"""Builds SSH command to be executed.
spath = _MULTIPLEXERS[node][0]
args.append("-oControlPath=%s" % spath)
args.append("-oControlMaster=no")
- args.append(node)
- if cmd:
- args.append(cmd)
+
+ (vcluster_master, vcluster_basedir) = \
+ qa_config.GetVclusterSettings()
+
+ if vcluster_master:
+ args.append(vcluster_master)
+ args.append("%s/%s/cmd" % (vcluster_basedir, node))
+
+ if cmd:
+ # For virtual clusters the whole command must be wrapped using the "cmd"
+ # script, as that script sets a number of environment variables. If the
+ # command contains shell meta characters the whole command needs to be
+ # quoted.
+ args.append(utils.ShellQuote(cmd))
+ else:
+ args.append(node)
+
+ if cmd:
+ args.append(cmd)
return args
-def StartLocalCommand(cmd, _nolog_opts=False, **kwargs):
+def StartLocalCommand(cmd, _nolog_opts=False, log_cmd=True, **kwargs):
"""Starts a local command.
"""
- if _nolog_opts:
- pcmd = [i for i in cmd if not i.startswith("-")]
- else:
- pcmd = cmd
- print "Command: %s" % utils.ShellQuoteArgs(pcmd)
+ if log_cmd:
+ if _nolog_opts:
+ pcmd = [i for i in cmd if not i.startswith("-")]
+ else:
+ pcmd = cmd
+ print "Command: %s" % utils.ShellQuoteArgs(pcmd)
return subprocess.Popen(cmd, shell=False, **kwargs)
-def StartSSH(node, cmd, strict=True):
+def StartSSH(node, cmd, strict=True, log_cmd=True):
"""Starts SSH.
"""
return StartLocalCommand(GetSSHCommand(node, cmd, strict=strict),
- _nolog_opts=True)
+ _nolog_opts=True, log_cmd=log_cmd)
def StartMultiplexer(node):
p = StartLocalCommand(GetSSHCommand(node, cmd, tty=tty),
stdout=subprocess.PIPE)
rcode = p.wait()
- _AssertRetCode(rcode, fail, node, cmd)
+ _AssertRetCode(rcode, fail, cmd, node)
return p.stdout.read()
anymore.
"""
+ vpath = MakeNodePath(node, path)
+
cmd = ("tmp=$(tempfile --prefix .gnt --directory=$(dirname %s)) && "
"[[ -f \"$tmp\" ]] && "
"cp %s $tmp && "
- "echo $tmp") % (utils.ShellQuote(path), utils.ShellQuote(path))
+ "echo $tmp") % (utils.ShellQuote(vpath), utils.ShellQuote(vpath))
# Return temporary filename
- return GetCommandOutput(node, cmd).strip()
+ result = GetCommandOutput(node, cmd).strip()
+
+ print "Backup filename: %s" % result
+
+ return result
def _ResolveName(cmd, key):
"""
master = qa_config.GetMasterNode()
- output = GetCommandOutput(master["primary"], utils.ShellQuoteArgs(cmd))
+ output = GetCommandOutput(master.primary, utils.ShellQuoteArgs(cmd))
for line in output.splitlines():
(lkey, lvalue) = line.split(":", 1)
if lkey == key:
"""Gets the full name of a node.
"""
- return _ResolveName(["gnt-node", "info", node["primary"]],
+ return _ResolveName(["gnt-node", "info", node.primary],
"Node name")
# Get list of all instances
cmd = ["gnt-instance", "list", "--separator=:", "--no-headers",
"--output=name,pnode,snodes"]
- output = GetCommandOutput(master["primary"], utils.ShellQuoteArgs(cmd))
+ output = GetCommandOutput(master.primary, utils.ShellQuoteArgs(cmd))
instances = []
for line in output.splitlines():
if names:
cmd.extend(names)
- return GetCommandOutput(master["primary"],
+ return GetCommandOutput(master.primary,
utils.ShellQuoteArgs(cmd)).splitlines()
# Test a number of field combinations
for testfields in _SelectQueryFields(rnd, fields):
- AssertCommand([cmd, "list", "--output", ",".join(testfields)])
+ AssertRedirectedCommand([cmd, "list", "--output", ",".join(testfields)])
if namefield is not None:
namelist_fn = compat.partial(_List, cmd, [namefield])
fail=True)
# Check exit code for listing unknown field
- AssertEqual(AssertCommand([cmd, "list", "--output=field/does/not/exist"],
- fail=True),
+ AssertEqual(AssertRedirectedCommand([cmd, "list",
+ "--output=field/does/not/exist"],
+ fail=True),
constants.EXIT_UNKNOWN_FIELD)
master = qa_config.GetMasterNode()
# Listing fields
- AssertCommand([cmd, "list-fields"])
- AssertCommand([cmd, "list-fields"] + fields)
+ AssertRedirectedCommand([cmd, "list-fields"])
+ AssertRedirectedCommand([cmd, "list-fields"] + fields)
# Check listed fields (all, must be sorted)
realcmd = [cmd, "list-fields", "--separator=|", "--no-headers"]
- output = GetCommandOutput(master["primary"],
+ output = GetCommandOutput(master.primary,
utils.ShellQuoteArgs(realcmd)).splitlines()
AssertEqual([line.split("|", 1)[0] for line in output],
utils.NiceSort(fields))
"""
master = qa_config.GetMasterNode()
- tmp_hosts = UploadData(master["primary"], "", mode=0644)
+ tmp_hosts = UploadData(master.primary, "", mode=0644)
data = []
for localhost in ("::1", "127.0.0.1"):
"""
master = qa_config.GetMasterNode()
- tmp_hosts = UploadData(master["primary"], "", mode=0644)
+ tmp_hosts = UploadData(master.primary, "", mode=0644)
quoted_tmp_hosts = utils.ShellQuote(tmp_hosts)
sed_data = " ".join(hostnames)
"""Check if instance is running or not.
"""
- instance_name = _GetName(instance, "name")
+ instance_name = _GetName(instance, operator.attrgetter("name"))
script = qa_config.GetInstanceCheckScript()
if not script:
master_node = qa_config.GetMasterNode()
# Build command to connect to master node
- master_ssh = GetSSHCommand(master_node["primary"], "--")
+ master_ssh = GetSSHCommand(master_node.primary, "--")
if running:
running_shellval = "1"
"""Gets group names which shouldn't exist on the cluster.
@param count: Number of groups to get
- @rtype: list
+ @rtype: integer
+
+ """
+ return GetNonexistentEntityNames(count, "groups", "group")
+
+
+def GetNonexistentEntityNames(count, name_config, name_prefix):
+ """Gets entity names which shouldn't exist on the cluster.
+
+ The actualy names can refer to arbitrary entities (for example
+ groups, networks).
+
+ @param count: Number of names to get
+ @rtype: integer
+ @param name_config: name of the leaf in the config containing
+ this entity's configuration, including a 'inexistent-'
+ element
+ @rtype: string
+ @param name_prefix: prefix of the entity's names, used to compose
+ the default values; for example for groups, the prefix is
+ 'group' and the generated names are then group1, group2, ...
+ @rtype: string
"""
- groups = qa_config.get("groups", {})
+ entities = qa_config.get(name_config, {})
- default = ["group1", "group2", "group3"]
+ default = [name_prefix + str(i) for i in range(count)]
assert count <= len(default)
- candidates = groups.get("inexistent-groups", default)[:count]
+ name_config_inexistent = "inexistent-" + name_config
+ candidates = entities.get(name_config_inexistent, default)[:count]
if len(candidates) < count:
- raise Exception("At least %s non-existent groups are needed" % count)
+ raise Exception("At least %s non-existent %s are needed" %
+ (count, name_config))
return candidates
+
+
+def MakeNodePath(node, path):
+ """Builds an absolute path for a virtual node.
+
+ @type node: string or L{qa_config._QaNode}
+ @param node: Node
+ @type path: string
+ @param path: Path without node-specific prefix
+
+ """
+ (_, basedir) = qa_config.GetVclusterSettings()
+
+ if isinstance(node, basestring):
+ name = node
+ else:
+ name = node.primary
+
+ if basedir:
+ assert path.startswith("/")
+ return "%s%s" % (vcluster.MakeNodeRoot(basedir, name), path)
+ else:
+ return path