diff --git a/lib/client/gnt_cluster.py b/lib/client/gnt_cluster.py index b5e1b77534f3a2517e3435e0cf8c20d213b59182..a332c2d876d0241689ca14d5a7bb63fbc9a488c6 100644 --- a/lib/client/gnt_cluster.py +++ b/lib/client/gnt_cluster.py @@ -367,24 +367,24 @@ def ShowClusterMaster(opts, args): return 0 -def _PrintGroupedParams(paramsdict, level=1, roman=False): - """Print Grouped parameters (be, nic, disk) by group. +def _FormatGroupedParams(paramsdict, roman=False): + """Format Grouped parameters (be, nic, disk) by group. @type paramsdict: dict of dicts @param paramsdict: {group: {param: value, ...}, ...} - @type level: int - @param level: Level of indention + @rtype: dict of dicts + @return: copy of the input dictionaries with strings as values """ - indent = " " * level - for item, val in sorted(paramsdict.items()): + ret = {} + for (item, val) in paramsdict.items(): if isinstance(val, dict): - ToStdout("%s- %s:", indent, item) - _PrintGroupedParams(val, level=level + 1, roman=roman) + ret[item] = _FormatGroupedParams(val, roman=roman) elif roman and isinstance(val, int): - ToStdout("%s %s: %s", indent, item, compat.TryToRoman(val)) + ret[item] = compat.TryToRoman(val) else: - ToStdout("%s %s: %s", indent, item, val) + ret[item] = str(val) + return ret def ShowClusterConfig(opts, args): @@ -400,91 +400,97 @@ def ShowClusterConfig(opts, args): cl = GetClient(query=True) result = cl.QueryClusterInfo() - ToStdout("Cluster name: %s", result["name"]) - ToStdout("Cluster UUID: %s", result["uuid"]) - - ToStdout("Creation time: %s", utils.FormatTime(result["ctime"])) - ToStdout("Modification time: %s", utils.FormatTime(result["mtime"])) - - ToStdout("Master node: %s", result["master"]) - - ToStdout("Architecture (this node): %s (%s)", - result["architecture"][0], result["architecture"][1]) - if result["tags"]: tags = utils.CommaJoin(utils.NiceSort(result["tags"])) else: tags = "(none)" - - ToStdout("Tags: %s", tags) - - ToStdout("Default hypervisor: %s", result["default_hypervisor"]) - ToStdout("Enabled hypervisors: %s", - utils.CommaJoin(result["enabled_hypervisors"])) - - ToStdout("Hypervisor parameters:") - _PrintGroupedParams(result["hvparams"]) - - ToStdout("OS-specific hypervisor parameters:") - _PrintGroupedParams(result["os_hvp"]) - - ToStdout("OS parameters:") - _PrintGroupedParams(result["osparams"]) - - ToStdout("Hidden OSes: %s", utils.CommaJoin(result["hidden_os"])) - ToStdout("Blacklisted OSes: %s", utils.CommaJoin(result["blacklisted_os"])) - - ToStdout("Cluster parameters:") - ToStdout(" - candidate pool size: %s", - compat.TryToRoman(result["candidate_pool_size"], - convert=opts.roman_integers)) - ToStdout(" - master netdev: %s", result["master_netdev"]) - ToStdout(" - master netmask: %s", result["master_netmask"]) - ToStdout(" - use external master IP address setup script: %s", - result["use_external_mip_script"]) - ToStdout(" - lvm volume group: %s", result["volume_group_name"]) if result["reserved_lvs"]: reserved_lvs = utils.CommaJoin(result["reserved_lvs"]) else: reserved_lvs = "(none)" - ToStdout(" - lvm reserved volumes: %s", reserved_lvs) - ToStdout(" - drbd usermode helper: %s", result["drbd_usermode_helper"]) - ToStdout(" - file storage path: %s", result["file_storage_dir"]) - ToStdout(" - shared file storage path: %s", - result["shared_file_storage_dir"]) - ToStdout(" - maintenance of node health: %s", - result["maintain_node_health"]) - ToStdout(" - uid pool: %s", uidpool.FormatUidPool(result["uid_pool"])) - ToStdout(" - default instance allocator: %s", result["default_iallocator"]) - ToStdout(" - primary ip version: %d", result["primary_ip_version"]) - ToStdout(" - preallocation wipe disks: %s", result["prealloc_wipe_disks"]) - ToStdout(" - OS search path: %s", utils.CommaJoin(pathutils.OS_SEARCH_PATH)) - ToStdout(" - ExtStorage Providers search path: %s", - utils.CommaJoin(pathutils.ES_SEARCH_PATH)) - ToStdout(" - enabled storage types: %s", - utils.CommaJoin(result["enabled_storage_types"])) - - ToStdout("Default node parameters:") - _PrintGroupedParams(result["ndparams"], roman=opts.roman_integers) - - ToStdout("Default instance parameters:") - _PrintGroupedParams(result["beparams"], roman=opts.roman_integers) - - ToStdout("Default nic parameters:") - _PrintGroupedParams(result["nicparams"], roman=opts.roman_integers) - - ToStdout("Default disk parameters:") - _PrintGroupedParams(result["diskparams"], roman=opts.roman_integers) - - ToStdout("Instance policy - limits for instances:") - for key in constants.IPOLICY_ISPECS: - ToStdout(" - %s", key) - _PrintGroupedParams(result["ipolicy"][key], roman=opts.roman_integers) - ToStdout(" - enabled disk templates: %s", - utils.CommaJoin(result["ipolicy"][constants.IPOLICY_DTS])) - for key in constants.IPOLICY_PARAMETERS: - ToStdout(" - %s: %s", key, result["ipolicy"][key]) + info = [ + ("Cluster name", result["name"]), + ("Cluster UUID", result["uuid"]), + + ("Creation time", utils.FormatTime(result["ctime"])), + ("Modification time", utils.FormatTime(result["mtime"])), + + ("Master node", result["master"]), + + ("Architecture (this node)", + "%s (%s)" % (result["architecture"][0], result["architecture"][1])), + + ("Tags", tags), + + ("Default hypervisor", result["default_hypervisor"]), + ("Enabled hypervisors", + utils.CommaJoin(result["enabled_hypervisors"])), + + ("Hypervisor parameters", _FormatGroupedParams(result["hvparams"])), + + ("OS-specific hypervisor parameters", + _FormatGroupedParams(result["os_hvp"])), + + ("OS parameters", _FormatGroupedParams(result["osparams"])), + + ("Hidden OSes", utils.CommaJoin(result["hidden_os"])), + ("Blacklisted OSes", utils.CommaJoin(result["blacklisted_os"])), + + ("Cluster parameters", [ + ("candidate pool size", + compat.TryToRoman(result["candidate_pool_size"], + convert=opts.roman_integers)), + ("master netdev", result["master_netdev"]), + ("master netmask", result["master_netmask"]), + ("use external master IP address setup script", + result["use_external_mip_script"]), + ("lvm volume group", result["volume_group_name"]), + ("lvm reserved volumes", reserved_lvs), + ("drbd usermode helper", result["drbd_usermode_helper"]), + ("file storage path", result["file_storage_dir"]), + ("shared file storage path", result["shared_file_storage_dir"]), + ("maintenance of node health", result["maintain_node_health"]), + ("uid pool", uidpool.FormatUidPool(result["uid_pool"])), + ("default instance allocator", result["default_iallocator"]), + ("primary ip version", result["primary_ip_version"]), + ("preallocation wipe disks", result["prealloc_wipe_disks"]), + ("OS search path", utils.CommaJoin(pathutils.OS_SEARCH_PATH)), + ("ExtStorage Providers search path", + utils.CommaJoin(pathutils.ES_SEARCH_PATH)), + ("enabled storage types", + utils.CommaJoin(result["enabled_storage_types"])), + ]), + + ("Default node parameters", + _FormatGroupedParams(result["ndparams"], roman=opts.roman_integers)), + + ("Default instance parameters", + _FormatGroupedParams(result["beparams"], roman=opts.roman_integers)), + + ("Default nic parameters", + _FormatGroupedParams(result["nicparams"], roman=opts.roman_integers)), + + ("Default disk parameters", + _FormatGroupedParams(result["diskparams"], roman=opts.roman_integers)), + + ("Instance policy - limits for instances", + [ + (key, + _FormatGroupedParams(result["ipolicy"][key], roman=opts.roman_integers)) + for key in constants.IPOLICY_ISPECS + ] + + [ + ("enabled disk templates", + utils.CommaJoin(result["ipolicy"][constants.IPOLICY_DTS])), + ] + + [ + (key, result["ipolicy"][key]) + for key in constants.IPOLICY_PARAMETERS + ]), + ] + + PrintGenericInfo(info) return 0 diff --git a/qa/qa_cluster.py b/qa/qa_cluster.py index 8d4ff496b64d9038f46cfc7685894b32f5513be1..187ad2381130548b8487c9d5ed890bbb430b9e83 100644 --- a/qa/qa_cluster.py +++ b/qa/qa_cluster.py @@ -63,36 +63,22 @@ def _CheckFileOnAllNodes(filename, content): AssertEqual(qa_utils.GetCommandOutput(node.primary, cmd), content) -# "gnt-cluster info" fields -_CIFIELD_RE = re.compile(r"^[-\s]*(?P<field>[^\s:]+):\s*(?P<value>\S.*)$") +def _GetClusterField(field_path): + """Get the value of a cluster field. - -def _GetBoolClusterField(field): - """Get the Boolean value of a cluster field. - - This function currently assumes that the field name is unique in the cluster - configuration. An assertion checks this assumption. - - @type field: string - @param field: Name of the field - @rtype: bool - @return: The effective value of the field + @type field_path: list of strings + @param field_path: Names of the groups/fields to navigate to get the desired + value, e.g. C{["Default node parameters", "oob_program"]} + @return: The effective value of the field (the actual type depends on the + chosen field) """ - master = qa_config.GetMasterNode() - infocmd = "gnt-cluster info" - info_out = qa_utils.GetCommandOutput(master.primary, infocmd) - ret = None - for l in info_out.splitlines(): - m = _CIFIELD_RE.match(l) - # FIXME: There should be a way to specify a field through a hierarchy - if m and m.group("field") == field: - # Make sure that ignoring the hierarchy doesn't cause a double match - assert ret is None - ret = (m.group("value").lower() == "true") - if ret is not None: - return ret - raise qa_error.Error("Field not found in cluster configuration: %s" % field) + assert isinstance(field_path, list) + assert field_path + ret = qa_utils.GetObjectInfo(["gnt-cluster", "info"]) + for key in field_path: + ret = ret[key] + return ret # Cluster-verify errors (date, "ERROR", then error code) @@ -467,13 +453,6 @@ def TestClusterModifyBe(): AssertCommand(["gnt-cluster", "modify", "-B", bep]) -_START_IPOLICY_RE = re.compile(r"^(\s*)Instance policy") -_START_ISPEC_RE = re.compile(r"^\s+-\s+(std|min|max)") -_VALUE_RE = r"([^\s:][^:]*):\s+(\S.*)$" -_IPOLICY_PARAM_RE = re.compile(r"^\s+-\s+" + _VALUE_RE) -_ISPEC_VALUE_RE = re.compile(r"^\s+" + _VALUE_RE) - - def _GetClusterIPolicy(): """Return the run-time values of the cluster-level instance policy. @@ -484,49 +463,26 @@ def _GetClusterIPolicy(): "min", "max", or "std" """ - mnode = qa_config.GetMasterNode() - info = GetCommandOutput(mnode.primary, "gnt-cluster info") - inside_policy = False - end_ispec_re = None - curr_spec = "" - specs = {} - policy = {} - for line in info.splitlines(): - if inside_policy: - # The order of the matching is important, as some REs overlap - m = _START_ISPEC_RE.match(line) - if m: - curr_spec = m.group(1) - continue - m = _IPOLICY_PARAM_RE.match(line) - if m: - policy[m.group(1)] = m.group(2).strip() - continue - m = _ISPEC_VALUE_RE.match(line) - if m: - assert curr_spec - par = m.group(1) + info = qa_utils.GetObjectInfo(["gnt-cluster", "info"]) + policy = info["Instance policy - limits for instances"] + ret_specs = {} + ret_policy = {} + for (key, val) in policy.items(): + if key in constants.IPOLICY_ISPECS: + for (par, pval) in val.items(): if par == "memory-size": par = "mem-size" - d = specs.setdefault(par, {}) - d[curr_spec] = m.group(2).strip() - continue - assert end_ispec_re is not None - if end_ispec_re.match(line): - inside_policy = False + d = ret_specs.setdefault(par, {}) + d[key] = pval else: - m = _START_IPOLICY_RE.match(line) - if m: - inside_policy = True - # We stop parsing when we find the same indentation level - re_str = r"^\s{%s}\S" % len(m.group(1)) - end_ispec_re = re.compile(re_str) + ret_policy[key] = val + # Sanity checks - assert len(specs) > 0 - good = ("min" in d and "std" in d and "max" in d for d in specs) - assert good, "Missing item in specs: %s" % specs - assert len(policy) > 0 - return (policy, specs) + assert len(ret_specs) > 0 + good = ("min" in d and "std" in d and "max" in d for d in ret_specs) + assert good, "Missing item in specs: %s" % ret_specs + assert len(ret_policy) > 0 + return (ret_policy, ret_specs) def TestClusterModifyIPolicy(): @@ -912,10 +868,11 @@ def TestSetExclStorCluster(newvalue): @return: The old value of exclusive_storage """ - oldvalue = _GetBoolClusterField("exclusive_storage") + es_path = ["Default node parameters", "exclusive_storage"] + oldvalue = _GetClusterField(es_path) AssertCommand(["gnt-cluster", "modify", "--node-parameters", "exclusive_storage=%s" % newvalue]) - effvalue = _GetBoolClusterField("exclusive_storage") + effvalue = _GetClusterField(es_path) if effvalue != newvalue: raise qa_error.Error("exclusive_storage has the wrong value: %s instead" " of %s" % (effvalue, newvalue)) diff --git a/qa/qa_utils.py b/qa/qa_utils.py index e842b15719a5f9db30033678f67f21bf35d1cc53..a61bfbf06eca313a311ab27c5fc18f40a9fffe5b 100644 --- a/qa/qa_utils.py +++ b/qa/qa_utils.py @@ -23,13 +23,14 @@ """ +import operator import os +import random import re -import sys import subprocess -import random +import sys import tempfile -import operator +import yaml try: import functools @@ -343,6 +344,21 @@ def GetCommandOutput(node, cmd, tty=None, fail=False): return p.stdout.read() +def GetObjectInfo(infocmd): + """Get and parse information about a Ganeti object. + + @type infocmd: list of strings + @param infocmd: command to be executed, e.g. ["gnt-cluster", "info"] + @return: the information parsed, appropriately stored in dictionaries, + lists... + + """ + master = qa_config.GetMasterNode() + cmdline = utils.ShellQuoteArgs(infocmd) + info_out = GetCommandOutput(master.primary, cmdline) + return yaml.load(info_out) + + def UploadFile(node, src): """Uploads a file to a node and returns the filename.