#
#
-# Copyright (C) 2007, 2011 Google Inc.
+# Copyright (C) 2007, 2011, 2012 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
import random
import tempfile
+try:
+ import functools
+except ImportError, err:
+ raise ImportError("Python 2.5 or higher is required: %s" % err)
+
from ganeti import utils
from ganeti import compat
from ganeti import constants
+from ganeti import ht
import qa_config
import qa_error
_MULTIPLEXERS = {}
+#: Unique ID per QA run
+_RUN_UUID = utils.NewUUID()
+
+
+(INST_DOWN,
+ INST_UP) = range(500, 502)
+
+(FIRST_ARG,
+ RETURN_VALUE) = range(1000, 1002)
+
def _SetupColours():
"""Initializes the colour constants.
"""
- # pylint: disable-msg=W0603
+ # pylint: disable=W0603
# due to global usage
global _INFO_SEQ, _WARNING_SEQ, _ERROR_SEQ, _RESET_SEQ
"""
if item not in sequence:
- raise qa_error.Error('%r not in %r' % (item, sequence))
+ raise qa_error.Error("%r not in %r" % (item, sequence))
+
+
+def AssertNotIn(item, sequence):
+ """Raises an error when item is in sequence.
+
+ """
+ if item in sequence:
+ raise qa_error.Error("%r in %r" % (item, sequence))
def AssertEqual(first, second):
"""
if not first == second:
- raise qa_error.Error('%r == %r' % (first, second))
+ raise qa_error.Error("%r == %r" % (first, second))
def AssertNotEqual(first, second):
"""
if not first != second:
- raise qa_error.Error('%r != %r' % (first, second))
+ raise qa_error.Error("%r != %r" % (first, second))
def AssertMatch(string, pattern):
raise qa_error.Error("%r doesn't match /%r/" % (string, pattern))
+def _GetName(entity, key):
+ """Tries to get name of an entity.
+
+ @type entity: string or dict
+ @type key: string
+ @param key: Dictionary key containing name
+
+ """
+ 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))
+
+ if not ht.TNonEmptyString(result):
+ raise Exception("Invalid name '%s'" % result)
+
+ return result
+
+
def AssertCommand(cmd, fail=False, node=None):
"""Checks that a remote command succeeds.
if node is None:
node = qa_config.GetMasterNode()
- if isinstance(node, basestring):
- nodename = node
- else:
- nodename = node["primary"]
+ nodename = _GetName(node, "primary")
if isinstance(cmd, basestring):
cmdstr = cmd
return rcode
-def GetSSHCommand(node, cmd, strict=True, opts=None, tty=True):
+def GetSSHCommand(node, cmd, strict=True, opts=None, tty=None):
"""Builds SSH command to be executed.
@type node: string
@param strict: whether to enable strict host key checking
@type opts: list
@param opts: list of additional options
- @type tty: Bool
- @param tty: If we should use tty
+ @type tty: boolean or None
+ @param tty: if we should use tty; if None, will be auto-detected
"""
- args = ["ssh", "-oEscapeChar=none", "-oBatchMode=yes", "-l", "root"]
+ args = ["ssh", "-oEscapeChar=none", "-oBatchMode=yes", "-lroot"]
+
+ if tty is None:
+ tty = sys.stdout.isatty()
if tty:
args.append("-t")
if strict:
- tmp = 'yes'
+ tmp = "yes"
else:
- tmp = 'no'
- args.append('-oStrictHostKeyChecking=%s' % tmp)
- args.append('-oClearAllForwardings=yes')
- args.append('-oForwardAgent=yes')
+ tmp = "no"
+ 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("-oControlPath=%s" % spath)
+ args.append("-oControlMaster=no")
args.append(node)
if cmd:
args.append(cmd)
return args
-def StartLocalCommand(cmd, **kwargs):
+def StartLocalCommand(cmd, _nolog_opts=False, **kwargs):
"""Starts a local command.
"""
- print "Command: %s" % utils.ShellQuoteArgs(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)
"""Starts SSH.
"""
- return StartLocalCommand(GetSSHCommand(node, cmd, strict=strict))
+ return StartLocalCommand(GetSSHCommand(node, cmd, strict=strict),
+ _nolog_opts=True)
def StartMultiplexer(node):
utils.RemoveFile(sname)
-def GetCommandOutput(node, cmd, tty=True):
+def GetCommandOutput(node, cmd, tty=None):
"""Returns the output of a command executed on the given node.
"""
'cat > "${tmp}" && '
'echo "${tmp}"') % mode
- f = open(src, 'r')
+ f = open(src, "r")
try:
p = subprocess.Popen(GetSSHCommand(node, cmd), shell=False, stdin=f,
stdout=subprocess.PIPE)
"""
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)
+ (lkey, lvalue) = line.split(":", 1)
if lkey == key:
return lvalue.lstrip()
raise KeyError("Key not found")
@param instance: Instance name
"""
- return _ResolveName(['gnt-instance', 'info', instance],
- 'Instance name')
+ return _ResolveName(["gnt-instance", "info", instance],
+ "Instance name")
def ResolveNodeName(node):
"""Gets the full name of a node.
"""
- return _ResolveName(['gnt-node', 'info', node['primary']],
- 'Node name')
+ return _ResolveName(["gnt-node", "info", node["primary"]],
+ "Node name")
def GetNodeInstances(node, secondaries=False):
node_name = ResolveNodeName(node)
# Get list of all instances
- cmd = ['gnt-instance', 'list', '--separator=:', '--no-headers',
- '--output=name,pnode,snodes']
- output = GetCommandOutput(master['primary'], utils.ShellQuoteArgs(cmd))
+ cmd = ["gnt-instance", "list", "--separator=:", "--no-headers",
+ "--output=name,pnode,snodes"]
+ output = GetCommandOutput(master["primary"], utils.ShellQuoteArgs(cmd))
instances = []
for line in output.splitlines():
- (name, pnode, snodes) = line.split(':', 2)
+ (name, pnode, snodes) = line.split(":", 2)
if ((not secondaries and pnode == node_name) or
- (secondaries and node_name in snodes.split(','))):
+ (secondaries and node_name in snodes.split(","))):
instances.append(name)
return instances
"""
master = qa_config.GetMasterNode()
- cmd = [listcmd, "list", "--separator=|", "--no-header",
+ cmd = [listcmd, "list", "--separator=|", "--no-headers",
"--output", ",".join(fields)]
if names:
utils.ShellQuoteArgs(cmd)).splitlines()
-def GenericQueryTest(cmd, fields):
+def GenericQueryTest(cmd, fields, namefield="name", test_unknown=True):
"""Runs a number of tests on query commands.
@param cmd: Command name
for testfields in _SelectQueryFields(rnd, fields):
AssertCommand([cmd, "list", "--output", ",".join(testfields)])
- namelist_fn = compat.partial(_List, cmd, ["name"])
+ if namefield is not None:
+ namelist_fn = compat.partial(_List, cmd, [namefield])
- # When no names were requested, the list must be sorted
- names = namelist_fn(None)
- AssertEqual(names, utils.NiceSort(names))
+ # When no names were requested, the list must be sorted
+ names = namelist_fn(None)
+ AssertEqual(names, utils.NiceSort(names))
- # When requesting specific names, the order must be kept
- revnames = list(reversed(names))
- AssertEqual(namelist_fn(revnames), revnames)
+ # When requesting specific names, the order must be kept
+ revnames = list(reversed(names))
+ AssertEqual(namelist_fn(revnames), revnames)
- randnames = list(names)
- rnd.shuffle(randnames)
- AssertEqual(namelist_fn(randnames), randnames)
+ randnames = list(names)
+ rnd.shuffle(randnames)
+ AssertEqual(namelist_fn(randnames), randnames)
- # Listing unknown items must fail
- AssertCommand([cmd, "list", "this.name.certainly.does.not.exist"], fail=True)
+ if test_unknown:
+ # Listing unknown items must fail
+ AssertCommand([cmd, "list", "this.name.certainly.does.not.exist"],
+ fail=True)
# Check exit code for listing unknown field
AssertEqual(AssertCommand([cmd, "list", "--output=field/does/not/exist"],
FormatWarning = lambda text: _FormatWithColor(text, _WARNING_SEQ)
FormatError = lambda text: _FormatWithColor(text, _ERROR_SEQ)
FormatInfo = lambda text: _FormatWithColor(text, _INFO_SEQ)
+
+
+def AddToEtcHosts(hostnames):
+ """Adds hostnames to /etc/hosts.
+
+ @param hostnames: List of hostnames first used A records, all other CNAMEs
+
+ """
+ master = qa_config.GetMasterNode()
+ tmp_hosts = UploadData(master["primary"], "", mode=0644)
+
+ quoted_tmp_hosts = utils.ShellQuote(tmp_hosts)
+ data = []
+ for localhost in ("::1", "127.0.0.1"):
+ data.append("%s %s" % (localhost, " ".join(hostnames)))
+
+ try:
+ AssertCommand(("cat /etc/hosts > %s && echo -e '%s' >> %s && mv %s"
+ " /etc/hosts") % (quoted_tmp_hosts, "\\n".join(data),
+ quoted_tmp_hosts, quoted_tmp_hosts))
+ except qa_error.Error:
+ AssertCommand(["rm", tmp_hosts])
+
+
+def RemoveFromEtcHosts(hostnames):
+ """Remove hostnames from /etc/hosts.
+
+ @param hostnames: List of hostnames first used A records, all other CNAMEs
+
+ """
+ master = qa_config.GetMasterNode()
+ tmp_hosts = UploadData(master["primary"], "", mode=0644)
+ quoted_tmp_hosts = utils.ShellQuote(tmp_hosts)
+
+ sed_data = " ".join(hostnames)
+ try:
+ AssertCommand(("sed -e '/^\(::1\|127\.0\.0\.1\)\s\+%s/d' /etc/hosts > %s"
+ " && mv %s /etc/hosts") % (sed_data, quoted_tmp_hosts,
+ quoted_tmp_hosts))
+ except qa_error.Error:
+ AssertCommand(["rm", tmp_hosts])
+
+
+def RunInstanceCheck(instance, running):
+ """Check if instance is running or not.
+
+ """
+ instance_name = _GetName(instance, "name")
+
+ script = qa_config.GetInstanceCheckScript()
+ if not script:
+ return
+
+ master_node = qa_config.GetMasterNode()
+
+ # Build command to connect to master node
+ master_ssh = GetSSHCommand(master_node["primary"], "--")
+
+ if running:
+ running_shellval = "1"
+ running_text = ""
+ else:
+ running_shellval = ""
+ running_text = "not "
+
+ print FormatInfo("Checking if instance '%s' is %srunning" %
+ (instance_name, running_text))
+
+ args = [script, instance_name]
+ env = {
+ "PATH": constants.HOOKS_PATH,
+ "RUN_UUID": _RUN_UUID,
+ "MASTER_SSH": utils.ShellQuoteArgs(master_ssh),
+ "INSTANCE_NAME": instance_name,
+ "INSTANCE_RUNNING": running_shellval,
+ }
+
+ result = os.spawnve(os.P_WAIT, script, args, env)
+ if result != 0:
+ raise qa_error.Error("Instance check failed with result %s" % result)
+
+
+def _InstanceCheckInner(expected, instarg, args, result):
+ """Helper function used by L{InstanceCheck}.
+
+ """
+ if instarg == FIRST_ARG:
+ instance = args[0]
+ elif instarg == RETURN_VALUE:
+ instance = result
+ else:
+ raise Exception("Invalid value '%s' for instance argument" % instarg)
+
+ if expected in (INST_DOWN, INST_UP):
+ RunInstanceCheck(instance, (expected == INST_UP))
+ elif expected is not None:
+ raise Exception("Invalid value '%s'" % expected)
+
+
+def InstanceCheck(before, after, instarg):
+ """Decorator to check instance status before and after test.
+
+ @param before: L{INST_DOWN} if instance must be stopped before test,
+ L{INST_UP} if instance must be running before test, L{None} to not check.
+ @param after: L{INST_DOWN} if instance must be stopped after test,
+ L{INST_UP} if instance must be running after test, L{None} to not check.
+ @param instarg: L{FIRST_ARG} to use first argument to test as instance (a
+ dictionary), L{RETURN_VALUE} to use return value (disallows pre-checks)
+
+ """
+ def decorator(fn):
+ @functools.wraps(fn)
+ def wrapper(*args, **kwargs):
+ _InstanceCheckInner(before, instarg, args, NotImplemented)
+
+ result = fn(*args, **kwargs)
+
+ _InstanceCheckInner(after, instarg, args, result)
+
+ return result
+ return wrapper
+ return decorator