X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/d0a44ec007613bebea5f41245d47d731ea366e55..335c14dc934643f5fb148bbceeb822f83d933d25:/qa/ganeti-qa.py diff --git a/qa/ganeti-qa.py b/qa/ganeti-qa.py index 5ffd44f..c58b959 100755 --- a/qa/ganeti-qa.py +++ b/qa/ganeti-qa.py @@ -26,9 +26,10 @@ # pylint: disable=C0103 # due to invalid name -import sys +import copy import datetime import optparse +import sys import qa_cluster import qa_config @@ -48,7 +49,9 @@ import qa_utils from ganeti import utils from ganeti import rapi # pylint: disable=W0611 from ganeti import constants +from ganeti import pathutils +from ganeti.http.auth import ParsePasswordFile import ganeti.rapi.client # pylint: disable=W0611 from ganeti.rapi.client import UsesRapiClient @@ -122,19 +125,48 @@ def RunEnvTests(): RunTestIf("env", qa_env.TestGanetiCommands) -def SetupCluster(rapi_user, rapi_secret): +def _LookupRapiSecret(rapi_user): + """Find the RAPI secret for the given user. + + @param rapi_user: Login user + @return: Login secret for the user + + """ + CTEXT = "{CLEARTEXT}" + master = qa_config.GetMasterNode() + cmd = ["cat", qa_utils.MakeNodePath(master, pathutils.RAPI_USERS_FILE)] + file_content = qa_utils.GetCommandOutput(master.primary, + utils.ShellQuoteArgs(cmd)) + users = ParsePasswordFile(file_content) + entry = users.get(rapi_user) + if not entry: + raise qa_error.Error("User %s not found in RAPI users file" % rapi_user) + secret = entry.password + if secret.upper().startswith(CTEXT): + secret = secret[len(CTEXT):] + elif secret.startswith("{"): + raise qa_error.Error("Unsupported password schema for RAPI user %s:" + " not a clear text password" % rapi_user) + return secret + + +def SetupCluster(rapi_user): """Initializes the cluster. @param rapi_user: Login user for RAPI - @param rapi_secret: Login secret for RAPI + @return: Login secret for RAPI """ + rapi_secret = utils.GenerateSecret() RunTestIf("create-cluster", qa_cluster.TestClusterInit, rapi_user, rapi_secret) if not qa_config.TestEnabled("create-cluster"): # If the cluster is already in place, we assume that exclusive-storage is # already set according to the configuration qa_config.SetExclusiveStorage(qa_config.get("exclusive-storage", False)) + if qa_rapi.Enabled(): + # To support RAPI on an existing cluster we have to find out the secret + rapi_secret = _LookupRapiSecret(rapi_user) # Test on empty cluster RunTestIf("node-list", qa_node.TestNodeList) @@ -161,6 +193,8 @@ def SetupCluster(rapi_user, rapi_secret): RunTestIf("node-info", qa_node.TestNodeInfo) + return rapi_secret + def RunClusterTests(): """Runs tests related to gnt-cluster. @@ -177,7 +211,7 @@ def RunClusterTests(): ("cluster-modify", qa_cluster.TestClusterModifyISpecs), ("cluster-modify", qa_cluster.TestClusterModifyBe), ("cluster-modify", qa_cluster.TestClusterModifyDisk), - ("cluster-modify", qa_cluster.TestClusterModifyStorageTypes), + ("cluster-modify", qa_cluster.TestClusterModifyDiskTemplates), ("cluster-rename", qa_cluster.TestClusterRename), ("cluster-info", qa_cluster.TestClusterVersion), ("cluster-info", qa_cluster.TestClusterInfo), @@ -267,6 +301,8 @@ def RunCommonInstanceTests(instance): RunTestIf(["instance-console", qa_rapi.Enabled], qa_rapi.TestRapiInstanceConsole, instance) + RunTestIf("instance-device-names", qa_instance.TestInstanceDeviceNames, + instance) DOWN_TESTS = qa_config.Either([ "instance-reinstall", "instance-rename", @@ -364,7 +400,11 @@ def RunExportImportTests(instance, inodes): @param inodes: current nodes of the instance """ - if qa_config.TestEnabled("instance-export"): + # FIXME: export explicitly bails out on file based storage. other non-lvm + # based storage types are untested, though. Also note that import could still + # work, but is deeply embedded into the "export" case. + if (qa_config.TestEnabled("instance-export") and + instance.disk_template != constants.DT_FILE): RunTest(qa_instance.TestInstanceExportNoTarget, instance) pnode = inodes[0] @@ -387,7 +427,10 @@ def RunExportImportTests(instance, inodes): finally: expnode.Release() - if qa_config.TestEnabled([qa_rapi.Enabled, "inter-cluster-instance-move"]): + # FIXME: inter-cluster-instance-move crashes on file based instances :/ + # See Issue 414. + if (qa_config.TestEnabled([qa_rapi.Enabled, "inter-cluster-instance-move"]) + and instance.disk_template != constants.DT_FILE): newinst = qa_config.AcquireInstance() try: tnode = qa_config.AcquireNode(exclude=inodes) @@ -514,33 +557,52 @@ def RunExclusiveStorageTests(): def _BuildSpecDict(par, mn, st, mx): - return {par: {"min": mn, "std": st, "max": mx}} + return { + constants.ISPECS_MINMAX: [{ + constants.ISPECS_MIN: {par: mn}, + constants.ISPECS_MAX: {par: mx}, + }], + constants.ISPECS_STD: {par: st}, + } + + +def _BuildDoubleSpecDict(index, par, mn, st, mx): + new_spec = { + constants.ISPECS_MINMAX: [{}, {}], + } + if st is not None: + new_spec[constants.ISPECS_STD] = {par: st} + new_spec[constants.ISPECS_MINMAX][index] = { + constants.ISPECS_MIN: {par: mn}, + constants.ISPECS_MAX: {par: mx}, + } + return new_spec def TestIPolicyPlainInstance(): """Test instance policy interaction with instances""" - params = ["mem-size", "cpu-count", "disk-count", "disk-size", "nic-count"] + params = ["memory-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({}) + (_, old_specs) = qa_cluster.TestClusterSetISpecs() + # We also assume to have only one min/max bound + assert len(old_specs[constants.ISPECS_MINMAX]) == 1 node = qa_config.AcquireNode() try: - # Log of policy changes, list of tuples: (change, policy_violated) + # Log of policy changes, list of tuples: + # (full_change, incremental_change, policy_violated) history = [] 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) - history.append((new_spec, True)) - qa_cluster.TestClusterSetISpecs(new_spec) - qa_cluster.AssertClusterVerify(warnings=policyerror) + history.append((None, new_spec, True)) if iminval > 0: # Some specs must be multiple of 4 if iminval >= 4: @@ -548,19 +610,57 @@ def TestIPolicyPlainInstance(): else: upper = iminval - 1 new_spec = _BuildSpecDict(par, 0, upper, upper) - history.append((new_spec, True)) - qa_cluster.TestClusterSetISpecs(new_spec) + history.append((None, new_spec, True)) + history.append((old_specs, None, False)) + + # Test with two instance specs + double_specs = copy.deepcopy(old_specs) + double_specs[constants.ISPECS_MINMAX] = \ + double_specs[constants.ISPECS_MINMAX] * 2 + (par1, par2) = params[0:2] + (_, imaxval1) = qa_instance.GetInstanceSpec(instance.name, par1) + (_, imaxval2) = qa_instance.GetInstanceSpec(instance.name, par2) + old_minmax = old_specs[constants.ISPECS_MINMAX][0] + history.extend([ + (double_specs, None, False), + # The first min/max limit is being violated + (None, + _BuildDoubleSpecDict(0, par1, imaxval1 + 4, imaxval1 + 4, + imaxval1 + 4), + False), + # Both min/max limits are being violated + (None, + _BuildDoubleSpecDict(1, par2, imaxval2 + 4, None, imaxval2 + 4), + True), + # The second min/max limit is being violated + (None, + _BuildDoubleSpecDict(0, par1, + old_minmax[constants.ISPECS_MIN][par1], + old_specs[constants.ISPECS_STD][par1], + old_minmax[constants.ISPECS_MAX][par1]), + False), + (old_specs, None, False), + ]) + + # Apply the changes, and check policy violations after each change + qa_cluster.AssertClusterVerify() + for (new_specs, diff_specs, failed) in history: + qa_cluster.TestClusterSetISpecs(new_specs=new_specs, + diff_specs=diff_specs) + if failed: qa_cluster.AssertClusterVerify(warnings=policyerror) - qa_cluster.TestClusterSetISpecs(old_specs) - history.append((old_specs, False)) + else: + qa_cluster.AssertClusterVerify() + qa_instance.TestInstanceRemove(instance) finally: instance.Release() # Now we replay the same policy changes, and we expect that the instance # cannot be created for the cases where we had a policy violation above - for (change, failed) in history: - qa_cluster.TestClusterSetISpecs(change) + for (new_specs, diff_specs, failed) in history: + qa_cluster.TestClusterSetISpecs(new_specs=new_specs, + diff_specs=diff_specs) if failed: qa_instance.TestInstanceAddWithPlainDisk([node], fail=True) # Instance creation with no policy violation has been tested already @@ -568,6 +668,30 @@ def TestIPolicyPlainInstance(): node.Release() +def IsExclusiveStorageInstanceTestEnabled(): + test_name = "exclusive-storage-instance-tests" + if qa_config.TestEnabled(test_name): + vgname = qa_config.get("vg-name", constants.DEFAULT_VG) + vgscmd = utils.ShellQuoteArgs([ + "vgs", "--noheadings", "-o", "pv_count", vgname, + ]) + nodes = qa_config.GetConfig()["nodes"] + for node in nodes: + try: + pvnum = int(qa_utils.GetCommandOutput(node.primary, vgscmd)) + except Exception, e: + msg = ("Cannot get the number of PVs on %s, needed by '%s': %s" % + (node.primary, test_name, e)) + raise qa_error.Error(msg) + if pvnum < 2: + raise qa_error.Error("Node %s has not enough PVs (%s) to run '%s'" % + (node.primary, pvnum, test_name)) + res = True + else: + res = False + return res + + def RunInstanceTests(): """Create and exercise instances.""" instance_tests = [ @@ -578,8 +702,8 @@ def RunInstanceTests(): ("instance-add-diskless", constants.DT_DISKLESS, qa_instance.TestInstanceAddDiskless, 1), ("instance-add-file", constants.DT_FILE, - qa_instance.TestInstanceAddFile, 1), - ] + qa_instance.TestInstanceAddFile, 1) + ] for (test_name, templ, create_fun, num_nodes) in instance_tests: if (qa_config.TestEnabled(test_name) and @@ -601,6 +725,8 @@ def RunInstanceTests(): RunTest(qa_instance.TestInstanceConvertDiskToPlain, instance, inodes) RunTest(qa_instance.TestInstanceStartup, instance) + RunTestIf("instance-modify-disks", + qa_instance.TestInstanceModifyDisks, instance) RunCommonInstanceTests(instance) if qa_config.TestEnabled("instance-modify-primary"): othernode = qa_config.AcquireNode() @@ -625,13 +751,13 @@ def RunQa(): """ rapi_user = "ganeti-qa" - rapi_secret = utils.GenerateSecret() RunEnvTests() - SetupCluster(rapi_user, rapi_secret) + rapi_secret = SetupCluster(rapi_user) - # Load RAPI certificate - qa_rapi.Setup(rapi_user, rapi_secret) + if qa_rapi.Enabled(): + # Load RAPI certificate + qa_rapi.Setup(rapi_user, rapi_secret) RunClusterTests() RunOsTests() @@ -680,7 +806,7 @@ def RunQa(): config_list = [ ("default-instance-tests", lambda: None, lambda _: None), - ("exclusive-storage-instance-tests", + (IsExclusiveStorageInstanceTestEnabled, lambda: qa_cluster.TestSetExclStorCluster(True), qa_cluster.TestSetExclStorCluster), ] @@ -718,6 +844,10 @@ def RunQa(): RunTestIf(["cluster-instance-policy", "instance-add-plain-disk"], TestIPolicyPlainInstance) + RunTestIf( + "instance-add-restricted-by-disktemplates", + qa_instance.TestInstanceCreationRestrictedByDiskTemplates) + # Test removing instance with offline drbd secondary if qa_config.TestEnabled(["instance-remove-drbd-offline", "instance-add-drbd-disk"]):