Commit ab4832d1 authored by Bernardo Dal Seno's avatar Bernardo Dal Seno

Add QA for policy-instance interactions

Violations on policy changes are checked.
Signed-off-by: default avatarBernardo Dal Seno <bdalseno@google.com>
Reviewed-by: default avatarGuido Trotter <ultrotter@google.com>
parent b3f3aa3d
......@@ -484,6 +484,48 @@ def RunExclusiveStorageTests():
qa_config.ReleaseNode(node)
def _BuildSpecDict(par, mn, st, mx):
return {par: {"min": mn, "std": st, "max": mx}}
def TestIPolicyPlainInstance():
"""Test instance policy interaction with instances"""
params = ["mem-size", "cpu-count", "disk-count", "disk-size", "nic-count"]
if not qa_config.IsTemplateSupported(constants.DT_PLAIN):
print "Template %s not supported" % constants.DT_PLAIN
return
# This test assumes that the group policy is empty
(_, old_specs) = qa_cluster.TestClusterSetISpecs({})
node = qa_config.AcquireNode()
try:
instance = qa_instance.TestInstanceAddWithPlainDisk([node])
try:
policyerror = [constants.CV_EINSTANCEPOLICY]
for par in params:
qa_cluster.AssertClusterVerify()
(iminval, imaxval) = qa_instance.GetInstanceSpec(instance["name"], par)
# Some specs must be multiple of 4
new_spec = _BuildSpecDict(par, imaxval + 4, imaxval + 4, imaxval + 4)
qa_cluster.TestClusterSetISpecs(new_spec)
qa_cluster.AssertClusterVerify(warnings=policyerror)
if iminval > 0:
# Some specs must be multiple of 4
if iminval >= 4:
upper = iminval - 4
else:
upper = iminval - 1
new_spec = _BuildSpecDict(par, 0, upper, upper)
qa_cluster.TestClusterSetISpecs(new_spec)
qa_cluster.AssertClusterVerify(warnings=policyerror)
qa_cluster.TestClusterSetISpecs(old_specs)
qa_instance.TestInstanceRemove(instance)
finally:
qa_config.ReleaseInstance(instance)
finally:
qa_config.ReleaseNode(node)
def RunInstanceTests():
"""Create and exercise instances."""
instance_tests = [
......@@ -612,6 +654,8 @@ def RunQa():
qa_config.ReleaseNode(pnode)
RunExclusiveStorageTests()
RunTestIf(["cluster-instance-policy", "instance-add-plain-disk"],
TestIPolicyPlainInstance)
# Test removing instance with offline drbd secondary
if qa_config.TestEnabled("instance-remove-drbd-offline"):
......
......@@ -119,6 +119,7 @@
"cluster-redist-conf": true,
"cluster-repair-disk-sizes": true,
"cluster-exclusive-storage": true,
"cluster-instance-policy": true,
"haskell-confd": true,
"htools": true,
......
......@@ -96,20 +96,33 @@ def _GetBoolClusterField(field):
# Cluster-verify errors (date, "ERROR", then error code)
_CVERROR_RE = re.compile(r"^[\w\s:]+\s+- ERROR:([A-Z0-9_-]+):")
_CVERROR_RE = re.compile(r"^[\w\s:]+\s+- (ERROR|WARNING):([A-Z0-9_-]+):")
def _GetCVErrorCodes(cvout):
ret = set()
errs = set()
warns = set()
for l in cvout.splitlines():
m = _CVERROR_RE.match(l)
if m:
ecode = m.group(1)
ret.add(ecode)
return ret
etype = m.group(1)
ecode = m.group(2)
if etype == "ERROR":
errs.add(ecode)
elif etype == "WARNING":
warns.add(ecode)
return (errs, warns)
def AssertClusterVerify(fail=False, errors=None):
def _CheckVerifyErrors(actual, expected, etype):
exp_codes = compat.UniqueFrozenset(e for (_, e, _) in expected)
if not actual.issuperset(exp_codes):
missing = exp_codes.difference(actual)
raise qa_error.Error("Cluster-verify didn't return these expected"
" %ss: %s" % (etype, utils.CommaJoin(missing)))
def AssertClusterVerify(fail=False, errors=None, warnings=None):
"""Run cluster-verify and check the result
@type fail: bool
......@@ -118,19 +131,20 @@ def AssertClusterVerify(fail=False, errors=None):
@param errors: List of CV_XXX errors that are expected; if specified, all the
errors listed must appear in cluster-verify output. A non-empty value
implies C{fail=True}.
@type warnings: list of tuples
@param warnings: Same as C{errors} but for warnings.
"""
cvcmd = "gnt-cluster verify"
mnode = qa_config.GetMasterNode()
if errors:
if errors or warnings:
cvout = GetCommandOutput(mnode["primary"], cvcmd + " --error-codes",
fail=True)
actual = _GetCVErrorCodes(cvout)
expected = compat.UniqueFrozenset(e for (_, e, _) in errors)
if not actual.issuperset(expected):
missing = expected.difference(actual)
raise qa_error.Error("Cluster-verify didn't return these expected"
" errors: %s" % utils.CommaJoin(missing))
fail=(fail or errors))
(act_errs, act_warns) = _GetCVErrorCodes(cvout)
if errors:
_CheckVerifyErrors(act_errs, errors, "error")
if warnings:
_CheckVerifyErrors(act_warns, warnings, "warning")
else:
AssertCommand(cvcmd, fail=fail, node=mnode)
......
......@@ -153,19 +153,33 @@ def _DestroyInstanceVolumes(instance):
AssertCommand(["lvremove", "-f"] + vols, node=node)
def _GetBoolInstanceField(instance, field):
"""Get the Boolean value of a field of an instance.
def _GetInstanceField(instance, field):
"""Get the value of a field of an instance.
@type instance: string
@param instance: Instance name
@type field: string
@param field: Name of the field
@rtype: string
"""
master = qa_config.GetMasterNode()
infocmd = utils.ShellQuoteArgs(["gnt-instance", "list", "--no-headers",
"-o", field, instance])
info_out = qa_utils.GetCommandOutput(master["primary"], infocmd).strip()
"--units", "m", "-o", field, instance])
return qa_utils.GetCommandOutput(master["primary"], infocmd).strip()
def _GetBoolInstanceField(instance, field):
"""Get the Boolean value of a field of an instance.
@type instance: string
@param instance: Instance name
@type field: string
@param field: Name of the field
@rtype: bool
"""
info_out = _GetInstanceField(instance, field)
if info_out == "Y":
return True
elif info_out == "N":
......@@ -175,6 +189,59 @@ def _GetBoolInstanceField(instance, field):
" %s" % (field, instance, info_out))
def _GetNumInstanceField(instance, field):
"""Get a numeric value of a field of an instance.
@type instance: string
@param instance: Instance name
@type field: string
@param field: Name of the field
@rtype: int or float
"""
info_out = _GetInstanceField(instance, field)
try:
ret = int(info_out)
except ValueError:
try:
ret = float(info_out)
except ValueError:
raise qa_error.Error("Field %s of instance %s has a non-numeric value:"
" %s" % (field, instance, info_out))
return ret
def GetInstanceSpec(instance, spec):
"""Return the current spec for the given parameter.
@type instance: string
@param instance: Instance name
@type spec: string
@param spec: one of the supported parameters: "mem-size", "cpu-count",
"disk-count", "disk-size", "nic-count"
@rtype: tuple
@return: (minspec, maxspec); minspec and maxspec can be different only for
memory and disk size
"""
specmap = {
"mem-size": ["be/minmem", "be/maxmem"],
"cpu-count": ["vcpus"],
"disk-count": ["disk.count"],
"disk-size": ["disk.size/ "],
"nic-count": ["nic.count"],
}
# For disks, first we need the number of disks
if spec == "disk-size":
(numdisk, _) = GetInstanceSpec(instance, "disk-count")
fields = ["disk.size/%s" % k for k in range(0, numdisk)]
else:
assert spec in specmap, "%s not in %s" % (spec, specmap)
fields = specmap[spec]
values = [_GetNumInstanceField(instance, f) for f in fields]
return (min(values), max(values))
def IsFailoverSupported(instance):
templ = qa_config.GetInstanceTemplate(instance)
return templ in constants.DTS_MIRRORED
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment