Commit 0e79564a authored by Bernardo Dal Seno's avatar Bernardo Dal Seno
Browse files

gnt-cluster info uses a revised format

The code is more modular and the output is YAML-compliant.

Added function in QA that uses PyYAML to parse the command output, and QA
is updated to take advantage of it (instead of using lots of complicated
Signed-off-by: default avatarBernardo Dal Seno <>
Reviewed-by: default avatarHelga Velroyen <>
parent 4d99964c
......@@ -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)
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"]))
tags = "(none)"
ToStdout("Tags: %s", tags)
ToStdout("Default hypervisor: %s", result["default_hypervisor"])
ToStdout("Enabled hypervisors: %s",
ToStdout("Hypervisor parameters:")
ToStdout("OS-specific hypervisor parameters:")
ToStdout("OS parameters:")
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",
ToStdout(" - master netdev: %s", result["master_netdev"])
ToStdout(" - master netmask: %s", result["master_netmask"])
ToStdout(" - use external master IP address setup script: %s",
ToStdout(" - lvm volume group: %s", result["volume_group_name"])
if result["reserved_lvs"]:
reserved_lvs = utils.CommaJoin(result["reserved_lvs"])
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",
ToStdout(" - maintenance of node health: %s",
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",
ToStdout(" - enabled storage types: %s",
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",
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",
("Hypervisor parameters", _FormatGroupedParams(result["hvparams"])),
("OS-specific hypervisor parameters",
("OS parameters", _FormatGroupedParams(result["osparams"])),
("Hidden OSes", utils.CommaJoin(result["hidden_os"])),
("Blacklisted OSes", utils.CommaJoin(result["blacklisted_os"])),
("Cluster parameters", [
("candidate pool size",
("master netdev", result["master_netdev"]),
("master netmask", result["master_netmask"]),
("use external master IP address setup 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",
("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",
_FormatGroupedParams(result["ipolicy"][key], roman=opts.roman_integers))
for key in constants.IPOLICY_ISPECS
] +
("enabled disk templates",
] +
(key, result["ipolicy"][key])
for key in constants.IPOLICY_PARAMETERS
return 0
......@@ -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"field") == field:
# Make sure that ignoring the hierarchy doesn't cause a double match
assert ret is None
ret = ("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 = _IPOLICY_PARAM_RE.match(line)
if m:
policy[] =
m = _ISPEC_VALUE_RE.match(line)
if m:
assert curr_spec
par =
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] =
assert end_ispec_re is not None
if end_ispec_re.match(line):
inside_policy = False
d = ret_specs.setdefault(par, {})
d[key] = pval
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(
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))
......@@ -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
import functools
......@@ -343,6 +344,21 @@ def GetCommandOutput(node, cmd, tty=None, fail=False):
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,
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.
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