Commit b31393a1 authored by Iustin Pop's avatar Iustin Pop
Browse files

Merge branch 'devel-2.4' into stable-2.4



* devel-2.4: (23 commits)
  Fix pylint warnings
  Change the list formatting to a 'special' chars
  Add support for merging node groups
  Add option to rename groups on conflict
  Fix minor docstring typo
  Fix HV/OS parameter validation on non-vm nodes
  NodeQuery: mark live fields as UNAVAIL for non-vm_capable nodes
  NodeQuery: don't query non-vm_capable nodes
  Remove superfluous redundant requirement
  Don't remove master_candidate flag from merged nodes
  Use a consistent ECID base
  listrunner: convert from getopt to optparse
  listrunner: fix agent usage
  Revert "Disable the cluster-merge tool for the moment"
  Fix cluster-merging by not stopping noded
  Fix error msg for instances on offline nodes
  Minor reordering to match param order
  cluster verify and instance disks on offline nodes
  Cluster verify and N+1 warnings for offline nodes
  Handle gnt-instance shutdown --all for empty clusters
  Use gnt-node add --force-join to add foreign nodes
  Add --force-join option to gnt-node add
  Fix iterating over node groups

Of the above commits present in the devel-2.4 branch, only the “Add
--force-join option to gnt-node add” is a potential issue, but this
has been QA-ed successfully. The other fixes are split in three
groups:

- non-core changes (cluster-merge, listrunner)
- trivial fixes (docstrings, etc.)
- bugs that we want fixed

As such, instead of cherry-picking only individual patches, I propose
that we unify stable and devel 2.4 and make a new RC out of the
result.
Signed-off-by: default avatarIustin Pop <iustin@google.com>
Reviewed-by: default avatarMichael Hanselmann <hansmi@google.com>
parents 0e265161 9b945588
...@@ -23,8 +23,8 @@ clusters into. ...@@ -23,8 +23,8 @@ clusters into.
The usage of ``cluster-merge`` is as follows:: The usage of ``cluster-merge`` is as follows::
cluster-merge [--debug|--verbose] [--watcher-pause-period SECONDS] <cluster> \ cluster-merge [--debug|--verbose] [--watcher-pause-period SECONDS] \
<cluster...> [--groups [merge|rename]] <cluster> [<cluster...>]
You can provide multiple clusters. The tool will then go over every You can provide multiple clusters. The tool will then go over every
cluster in serial and perform the steps to merge it into the invoking cluster in serial and perform the steps to merge it into the invoking
...@@ -39,6 +39,15 @@ These options can be used to control the behaviour of the tool: ...@@ -39,6 +39,15 @@ These options can be used to control the behaviour of the tool:
``--watcher-pause-period`` ``--watcher-pause-period``
Define the period of time in seconds the watcher shall be disabled, Define the period of time in seconds the watcher shall be disabled,
default is 1800 seconds (30 minutes). default is 1800 seconds (30 minutes).
``--groups``
This option controls how ``cluster-merge`` handles duplicate node
group names on the merging clusters. If ``merge`` is specified then
all node groups with the same name will be merged into one. If
``rename`` is specified, then conflicting node groups on the remove
clusters will have their cluster name appended to the group name. If
this option is not speicifed, then ``cluster-merge`` will refuse to
continue if it finds conflicting group names, otherwise it will
proceed as normal.
Rollback Rollback
......
...@@ -294,9 +294,6 @@ failover: ...@@ -294,9 +294,6 @@ failover:
determined by the serial number on the configuration and determined by the serial number on the configuration and
highest job ID on the job queue) highest job ID on the job queue)
- there is not even a single node having a newer
configuration file
- if we are not failing over (but just starting), the - if we are not failing over (but just starting), the
quorum agrees that we are the designated master quorum agrees that we are the designated master
......
...@@ -104,6 +104,7 @@ __all__ = [ ...@@ -104,6 +104,7 @@ __all__ = [
"NEW_RAPI_CERT_OPT", "NEW_RAPI_CERT_OPT",
"NEW_SECONDARY_OPT", "NEW_SECONDARY_OPT",
"NIC_PARAMS_OPT", "NIC_PARAMS_OPT",
"NODE_FORCE_JOIN_OPT",
"NODE_LIST_OPT", "NODE_LIST_OPT",
"NODE_PLACEMENT_OPT", "NODE_PLACEMENT_OPT",
"NODEGROUP_OPT", "NODEGROUP_OPT",
...@@ -885,6 +886,11 @@ NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check", ...@@ -885,6 +886,11 @@ NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
default=True, action="store_false", default=True, action="store_false",
help="Disable SSH key fingerprint checking") help="Disable SSH key fingerprint checking")
NODE_FORCE_JOIN_OPT = cli_option("--force-join", dest="force_join",
default=False, action="store_true",
help="Force the joining of a node,"
" needed when merging clusters")
MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate", MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
type="bool", default=None, metavar=_YORNO, type="bool", default=None, metavar=_YORNO,
help="Set the master_candidate flag on the node") help="Set the master_candidate flag on the node")
...@@ -2376,17 +2382,23 @@ class _QueryColumnFormatter: ...@@ -2376,17 +2382,23 @@ class _QueryColumnFormatter:
"""Callable class for formatting fields of a query. """Callable class for formatting fields of a query.
""" """
def __init__(self, fn, status_fn): def __init__(self, fn, status_fn, verbose):
"""Initializes this class. """Initializes this class.
@type fn: callable @type fn: callable
@param fn: Formatting function @param fn: Formatting function
@type status_fn: callable @type status_fn: callable
@param status_fn: Function to report fields' status @param status_fn: Function to report fields' status
@type verbose: boolean
@param verbose: whether to use verbose field descriptions or not
""" """
self._fn = fn self._fn = fn
self._status_fn = status_fn self._status_fn = status_fn
if verbose:
self._desc_index = 0
else:
self._desc_index = 1
def __call__(self, data): def __call__(self, data):
"""Returns a field's string representation. """Returns a field's string representation.
...@@ -2403,23 +2415,14 @@ class _QueryColumnFormatter: ...@@ -2403,23 +2415,14 @@ class _QueryColumnFormatter:
assert value is None, \ assert value is None, \
"Found value %r for abnormal status %s" % (value, status) "Found value %r for abnormal status %s" % (value, status)
if status == constants.RS_UNKNOWN: if status in constants.RSS_DESCRIPTION:
return "(unknown)" return constants.RSS_DESCRIPTION[status][self._desc_index]
if status == constants.RS_NODATA:
return "(nodata)"
if status == constants.RS_UNAVAIL:
return "(unavail)"
if status == constants.RS_OFFLINE:
return "(offline)"
raise NotImplementedError("Unknown status %s" % status) raise NotImplementedError("Unknown status %s" % status)
def FormatQueryResult(result, unit=None, format_override=None, separator=None, def FormatQueryResult(result, unit=None, format_override=None, separator=None,
header=False): header=False, verbose=False):
"""Formats data in L{objects.QueryResponse}. """Formats data in L{objects.QueryResponse}.
@type result: L{objects.QueryResponse} @type result: L{objects.QueryResponse}
...@@ -2434,6 +2437,8 @@ def FormatQueryResult(result, unit=None, format_override=None, separator=None, ...@@ -2434,6 +2437,8 @@ def FormatQueryResult(result, unit=None, format_override=None, separator=None,
@param separator: String used to separate fields @param separator: String used to separate fields
@type header: bool @type header: bool
@param header: Whether to output header row @param header: Whether to output header row
@type verbose: boolean
@param verbose: whether to use verbose field descriptions or not
""" """
if unit is None: if unit is None:
...@@ -2456,7 +2461,8 @@ def FormatQueryResult(result, unit=None, format_override=None, separator=None, ...@@ -2456,7 +2461,8 @@ def FormatQueryResult(result, unit=None, format_override=None, separator=None,
assert fdef.title and fdef.name assert fdef.title and fdef.name
(fn, align_right) = _GetColumnFormatter(fdef, format_override, unit) (fn, align_right) = _GetColumnFormatter(fdef, format_override, unit)
columns.append(TableColumn(fdef.title, columns.append(TableColumn(fdef.title,
_QueryColumnFormatter(fn, _RecordStatus), _QueryColumnFormatter(fn, _RecordStatus,
verbose),
align_right)) align_right))
table = FormatTable(result.data, columns, header, separator) table = FormatTable(result.data, columns, header, separator)
...@@ -2505,7 +2511,7 @@ def _WarnUnknownFields(fdefs): ...@@ -2505,7 +2511,7 @@ def _WarnUnknownFields(fdefs):
def GenericList(resource, fields, names, unit, separator, header, cl=None, def GenericList(resource, fields, names, unit, separator, header, cl=None,
format_override=None): format_override=None, verbose=False):
"""Generic implementation for listing all items of a resource. """Generic implementation for listing all items of a resource.
@param resource: One of L{constants.QR_OP_LUXI} @param resource: One of L{constants.QR_OP_LUXI}
...@@ -2524,6 +2530,8 @@ def GenericList(resource, fields, names, unit, separator, header, cl=None, ...@@ -2524,6 +2530,8 @@ def GenericList(resource, fields, names, unit, separator, header, cl=None,
@type format_override: dict @type format_override: dict
@param format_override: Dictionary for overriding field formatting functions, @param format_override: Dictionary for overriding field formatting functions,
indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY} indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
@type verbose: boolean
@param verbose: whether to use verbose field descriptions or not
""" """
if cl is None: if cl is None:
...@@ -2538,7 +2546,8 @@ def GenericList(resource, fields, names, unit, separator, header, cl=None, ...@@ -2538,7 +2546,8 @@ def GenericList(resource, fields, names, unit, separator, header, cl=None,
(status, data) = FormatQueryResult(response, unit=unit, separator=separator, (status, data) = FormatQueryResult(response, unit=unit, separator=separator,
header=header, header=header,
format_override=format_override) format_override=format_override,
verbose=verbose)
for line in data: for line in data:
ToStdout(line) ToStdout(line)
......
...@@ -503,7 +503,7 @@ def ListLocks(opts, args): # pylint: disable-msg=W0613 ...@@ -503,7 +503,7 @@ def ListLocks(opts, args): # pylint: disable-msg=W0613
while True: while True:
ret = GenericList(constants.QR_LOCK, selected_fields, None, None, ret = GenericList(constants.QR_LOCK, selected_fields, None, None,
opts.separator, not opts.no_headers, opts.separator, not opts.no_headers,
format_override=fmtoverride) format_override=fmtoverride, verbose=opts.verbose)
if ret != constants.EXIT_SUCCESS: if ret != constants.EXIT_SUCCESS:
return ret return ret
...@@ -575,7 +575,8 @@ commands = { ...@@ -575,7 +575,8 @@ commands = {
TestJobqueue, ARGS_NONE, [PRIORITY_OPT], TestJobqueue, ARGS_NONE, [PRIORITY_OPT],
"", "Test a few aspects of the job queue"), "", "Test a few aspects of the job queue"),
"locks": ( "locks": (
ListLocks, ARGS_NONE, [NOHDR_OPT, SEP_OPT, FIELDS_OPT, INTERVAL_OPT], ListLocks, ARGS_NONE,
[NOHDR_OPT, SEP_OPT, FIELDS_OPT, INTERVAL_OPT, VERBOSE_OPT],
"[--interval N]", "Show a list of locks in the master daemon"), "[--interval N]", "Show a list of locks in the master daemon"),
} }
......
# #
# #
# Copyright (C) 2010 Google Inc. # Copyright (C) 2010, 2011 Google Inc.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by
...@@ -101,7 +101,7 @@ def ListGroups(opts, args): ...@@ -101,7 +101,7 @@ def ListGroups(opts, args):
return GenericList(constants.QR_GROUP, desired_fields, args, None, return GenericList(constants.QR_GROUP, desired_fields, args, None,
opts.separator, not opts.no_headers, opts.separator, not opts.no_headers,
format_override=fmtoverride) format_override=fmtoverride, verbose=opts.verbose)
def ListGroupFields(opts, args): def ListGroupFields(opts, args):
...@@ -121,7 +121,7 @@ def ListGroupFields(opts, args): ...@@ -121,7 +121,7 @@ def ListGroupFields(opts, args):
def SetGroupParams(opts, args): def SetGroupParams(opts, args):
"""Modifies a node group's parameters. """Modifies a node group's parameters.
@param opts: the command line options seletect by the user @param opts: the command line options selected by the user
@type args: list @type args: list
@param args: should contain only one element, the node group name @param args: should contain only one element, the node group name
...@@ -189,7 +189,7 @@ commands = { ...@@ -189,7 +189,7 @@ commands = {
"<group_name> <node>...", "Assign nodes to a group"), "<group_name> <node>...", "Assign nodes to a group"),
"list": ( "list": (
ListGroups, ARGS_MANY_GROUPS, ListGroups, ARGS_MANY_GROUPS,
[NOHDR_OPT, SEP_OPT, FIELDS_OPT], [NOHDR_OPT, SEP_OPT, FIELDS_OPT, VERBOSE_OPT],
"[<group_name>...]", "[<group_name>...]",
"Lists the node groups in the cluster. The available fields can be shown" "Lists the node groups in the cluster. The available fields can be shown"
" using the \"list-fields\" command (see the man page for details)." " using the \"list-fields\" command (see the man page for details)."
......
...@@ -217,6 +217,9 @@ def GenericManyOps(operation, fn): ...@@ -217,6 +217,9 @@ def GenericManyOps(operation, fn):
cl = GetClient() cl = GetClient()
inames = _ExpandMultiNames(opts.multi_mode, args, client=cl) inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
if not inames: if not inames:
if opts.multi_mode == _SHUTDOWN_CLUSTER:
ToStdout("Cluster is empty, no instances to shutdown")
return 0
raise errors.OpPrereqError("Selection filter does not match" raise errors.OpPrereqError("Selection filter does not match"
" any instances", errors.ECODE_INVAL) " any instances", errors.ECODE_INVAL)
multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1 multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
...@@ -254,7 +257,7 @@ def ListInstances(opts, args): ...@@ -254,7 +257,7 @@ def ListInstances(opts, args):
return GenericList(constants.QR_INSTANCE, selected_fields, args, opts.units, return GenericList(constants.QR_INSTANCE, selected_fields, args, opts.units,
opts.separator, not opts.no_headers, opts.separator, not opts.no_headers,
format_override=fmtoverride) format_override=fmtoverride, verbose=opts.verbose)
def ListInstanceFields(opts, args): def ListInstanceFields(opts, args):
...@@ -1395,7 +1398,7 @@ commands = { ...@@ -1395,7 +1398,7 @@ commands = {
"Show information on the specified instance(s)"), "Show information on the specified instance(s)"),
'list': ( 'list': (
ListInstances, ARGS_MANY_INSTANCES, ListInstances, ARGS_MANY_INSTANCES,
[NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT], [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT],
"[<instance>...]", "[<instance>...]",
"Lists the instances and their status. The available fields can be shown" "Lists the instances and their status. The available fields can be shown"
" using the \"list-fields\" command (see the man page for details)." " using the \"list-fields\" command (see the man page for details)."
......
...@@ -134,6 +134,8 @@ def _RunSetupSSH(options, nodes): ...@@ -134,6 +134,8 @@ def _RunSetupSSH(options, nodes):
cmd.append("--verbose") cmd.append("--verbose")
if not options.ssh_key_check: if not options.ssh_key_check:
cmd.append("--no-ssh-key-check") cmd.append("--no-ssh-key-check")
if options.force_join:
cmd.append("--force-join")
cmd.extend(nodes) cmd.extend(nodes)
...@@ -221,7 +223,7 @@ def ListNodes(opts, args): ...@@ -221,7 +223,7 @@ def ListNodes(opts, args):
return GenericList(constants.QR_NODE, selected_fields, args, opts.units, return GenericList(constants.QR_NODE, selected_fields, args, opts.units,
opts.separator, not opts.no_headers, opts.separator, not opts.no_headers,
format_override=fmtoverride) format_override=fmtoverride, verbose=opts.verbose)
def ListNodeFields(opts, args): def ListNodeFields(opts, args):
...@@ -715,10 +717,11 @@ def SetNodeParams(opts, args): ...@@ -715,10 +717,11 @@ def SetNodeParams(opts, args):
commands = { commands = {
'add': ( 'add': (
AddNode, [ArgHost(min=1, max=1)], AddNode, [ArgHost(min=1, max=1)],
[SECONDARY_IP_OPT, READD_OPT, NOSSH_KEYCHECK_OPT, NONODE_SETUP_OPT, [SECONDARY_IP_OPT, READD_OPT, NOSSH_KEYCHECK_OPT, NODE_FORCE_JOIN_OPT,
VERBOSE_OPT, NODEGROUP_OPT, PRIORITY_OPT, CAPAB_MASTER_OPT, NONODE_SETUP_OPT, VERBOSE_OPT, NODEGROUP_OPT, PRIORITY_OPT,
CAPAB_VM_OPT, NODE_PARAMS_OPT], CAPAB_MASTER_OPT, CAPAB_VM_OPT, NODE_PARAMS_OPT],
"[-s ip] [--readd] [--no-ssh-key-check] [--no-node-setup] [--verbose] " "[-s ip] [--readd] [--no-ssh-key-check] [--force-join]"
" [--no-node-setup] [--verbose]"
" <node_name>", " <node_name>",
"Add a node to the cluster"), "Add a node to the cluster"),
'evacuate': ( 'evacuate': (
...@@ -744,7 +747,7 @@ commands = { ...@@ -744,7 +747,7 @@ commands = {
"[<node_name>...]", "Show information about the node(s)"), "[<node_name>...]", "Show information about the node(s)"),
'list': ( 'list': (
ListNodes, ARGS_MANY_NODES, ListNodes, ARGS_MANY_NODES,
[NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT], [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT],
"[nodes...]", "[nodes...]",
"Lists the nodes in the cluster. The available fields can be shown using" "Lists the nodes in the cluster. The available fields can be shown using"
" the \"list-fields\" command (see the man page for details)." " the \"list-fields\" command (see the man page for details)."
......
...@@ -1554,7 +1554,7 @@ class LUClusterVerify(LogicalUnit): ...@@ -1554,7 +1554,7 @@ class LUClusterVerify(LogicalUnit):
node_current) node_current)
for node, n_img in node_image.items(): for node, n_img in node_image.items():
if (not node == node_current): if node != node_current:
test = instance in n_img.instances test = instance in n_img.instances
_ErrorIf(test, self.EINSTANCEWRONGNODE, instance, _ErrorIf(test, self.EINSTANCEWRONGNODE, instance,
"instance should not run on node %s", node) "instance should not run on node %s", node)
...@@ -1564,7 +1564,11 @@ class LUClusterVerify(LogicalUnit): ...@@ -1564,7 +1564,11 @@ class LUClusterVerify(LogicalUnit):
for idx, (success, status) in enumerate(disks)] for idx, (success, status) in enumerate(disks)]
for nname, success, bdev_status, idx in diskdata: for nname, success, bdev_status, idx in diskdata:
_ErrorIf(instanceconfig.admin_up and not success, # the 'ghost node' construction in Exec() ensures that we have a
# node here
snode = node_image[nname]
bad_snode = snode.ghost or snode.offline
_ErrorIf(instanceconfig.admin_up and not success and not bad_snode,
self.EINSTANCEFAULTYDISK, instance, self.EINSTANCEFAULTYDISK, instance,
"couldn't retrieve status for disk/%s on %s: %s", "couldn't retrieve status for disk/%s on %s: %s",
idx, nname, bdev_status) idx, nname, bdev_status)
...@@ -1622,6 +1626,12 @@ class LUClusterVerify(LogicalUnit): ...@@ -1622,6 +1626,12 @@ class LUClusterVerify(LogicalUnit):
# WARNING: we currently take into account down instances as well # WARNING: we currently take into account down instances as well
# as up ones, considering that even if they're down someone # as up ones, considering that even if they're down someone
# might want to start them even in the event of a node failure. # might want to start them even in the event of a node failure.
if n_img.offline:
# we're skipping offline nodes from the N+1 warning, since
# most likely we don't have good memory infromation from them;
# we already list instances living on such nodes, and that's
# enough warning
continue
for prinode, instances in n_img.sbp.items(): for prinode, instances in n_img.sbp.items():
needed_mem = 0 needed_mem = 0
for instance in instances: for instance in instances:
...@@ -2290,8 +2300,8 @@ class LUClusterVerify(LogicalUnit): ...@@ -2290,8 +2300,8 @@ class LUClusterVerify(LogicalUnit):
self.ENODERPC, pnode, "instance %s, connection to" self.ENODERPC, pnode, "instance %s, connection to"
" primary node failed", instance) " primary node failed", instance)
if pnode_img.offline: _ErrorIf(pnode_img.offline, self.EINSTANCEBADNODE, instance,
inst_nodes_offline.append(pnode) "instance lives on offline node %s", inst_config.primary_node)
# If the instance is non-redundant we cannot survive losing its primary # If the instance is non-redundant we cannot survive losing its primary
# node, so we are not N+1 compliant. On the other hand we have no disk # node, so we are not N+1 compliant. On the other hand we have no disk
...@@ -2340,7 +2350,7 @@ class LUClusterVerify(LogicalUnit): ...@@ -2340,7 +2350,7 @@ class LUClusterVerify(LogicalUnit):
# warn that the instance lives on offline nodes # warn that the instance lives on offline nodes
_ErrorIf(inst_nodes_offline, self.EINSTANCEBADNODE, instance, _ErrorIf(inst_nodes_offline, self.EINSTANCEBADNODE, instance,
"instance lives on offline node(s) %s", "instance has offline secondary node(s) %s",
utils.CommaJoin(inst_nodes_offline)) utils.CommaJoin(inst_nodes_offline))
# ... or ghost/non-vm_capable nodes # ... or ghost/non-vm_capable nodes
for node in inst_config.all_nodes: for node in inst_config.all_nodes:
...@@ -3622,7 +3632,10 @@ class _NodeQuery(_QueryBase): ...@@ -3622,7 +3632,10 @@ class _NodeQuery(_QueryBase):
# Gather data as requested # Gather data as requested
if query.NQ_LIVE in self.requested_data: if query.NQ_LIVE in self.requested_data:
node_data = lu.rpc.call_node_info(nodenames, lu.cfg.GetVGName(), # filter out non-vm_capable nodes
toquery_nodes = [name for name in nodenames if all_info[name].vm_capable]
node_data = lu.rpc.call_node_info(toquery_nodes, lu.cfg.GetVGName(),
lu.cfg.GetHypervisorType()) lu.cfg.GetHypervisorType())
live_data = dict((name, nresult.payload) live_data = dict((name, nresult.payload)
for (name, nresult) in node_data.items() for (name, nresult) in node_data.items()
...@@ -6808,6 +6821,21 @@ def _ComputeDiskSize(disk_template, disks): ...@@ -6808,6 +6821,21 @@ def _ComputeDiskSize(disk_template, disks):
return req_size_dict[disk_template] return req_size_dict[disk_template]
def _FilterVmNodes(lu, nodenames):
"""Filters out non-vm_capable nodes from a list.
@type lu: L{LogicalUnit}
@param lu: the logical unit for which we check
@type nodenames: list
@param nodenames: the list of nodes on which we should check
@rtype: list
@return: the list of vm-capable nodes
"""
vm_nodes = frozenset(lu.cfg.GetNonVmCapableNodeList())
return [name for name in nodenames if name not in vm_nodes]
def _CheckHVParams(lu, nodenames, hvname, hvparams): def _CheckHVParams(lu, nodenames, hvname, hvparams):
"""Hypervisor parameter validation. """Hypervisor parameter validation.
...@@ -6825,6 +6853,7 @@ def _CheckHVParams(lu, nodenames, hvname, hvparams): ...@@ -6825,6 +6853,7 @@ def _CheckHVParams(lu, nodenames, hvname, hvparams):
@raise errors.OpPrereqError: if the parameters are not valid @raise errors.OpPrereqError: if the parameters are not valid
""" """
nodenames = _FilterVmNodes(lu, nodenames)
hvinfo = lu.rpc.call_hypervisor_validate_params(nodenames, hvinfo = lu.rpc.call_hypervisor_validate_params(nodenames,
hvname, hvname,
hvparams) hvparams)
...@@ -6852,6 +6881,7 @@ def _CheckOSParams(lu, required, nodenames, osname, osparams): ...@@ -6852,6 +6881,7 @@ def _CheckOSParams(lu, required, nodenames, osname, osparams):
@raise errors.OpPrereqError: if the parameters are not valid @raise errors.OpPrereqError: if the parameters are not valid
""" """
nodenames = _FilterVmNodes(lu, nodenames)
result = lu.rpc.call_os_validate(required, nodenames, osname, result = lu.rpc.call_os_validate(required, nodenames, osname,
[constants.OS_VALIDATE_PARAMETERS], [constants.OS_VALIDATE_PARAMETERS],
osparams) osparams)
......
...@@ -1029,6 +1029,14 @@ RS_ALL = frozenset([ ...@@ -1029,6 +1029,14 @@ RS_ALL = frozenset([
RS_OFFLINE, RS_OFFLINE,
]) ])
#: Dictionary with special field cases and their verbose/terse formatting
RSS_DESCRIPTION = {
RS_UNKNOWN: ("(unknown)", "??"),
RS_NODATA: ("(nodata)", "?"),
RS_OFFLINE: ("(offline)", "*"),
RS_UNAVAIL: ("(unavail)", "-"),
}
# max dynamic devices # max dynamic devices
MAX_NICS = 8 MAX_NICS = 8
MAX_DISKS = 16 MAX_DISKS = 16
......
...@@ -487,6 +487,7 @@ _NODE_SIMPLE_FIELDS = { ...@@ -487,6 +487,7 @@ _NODE_SIMPLE_FIELDS = {
#: Fields requiring talking to the node #: Fields requiring talking to the node
# Note that none of these are available for non-vm_capable nodes
_NODE_LIVE_FIELDS = { _NODE_LIVE_FIELDS = {
"bootid": ("BootID", QFT_TEXT, "bootid"), "bootid": ("BootID", QFT_TEXT, "bootid"),
"cnodes": ("CNodes", QFT_NUMBER, "cpu_nodes"), "cnodes": ("CNodes", QFT_NUMBER, "cpu_nodes"),
...@@ -577,6 +578,9 @@ def _GetLiveNodeField(field, kind, ctx, node): ...@@ -577,6 +578,9 @@ def _GetLiveNodeField(field, kind, ctx, node):
if node.offline: if node.offline:
return _FS_OFFLINE return _FS_OFFLINE
if not node.vm_capable:
return _FS_UNAVAIL
if not ctx.curlive_data: if not ctx.curlive_data:
return _FS_NODATA return _FS_NODATA
......
...@@ -179,3 +179,38 @@ Common daemon functionality ...@@ -179,3 +179,38 @@ Common daemon functionality
All Ganeti daemons re-open the log file(s) when sent a SIGHUP signal. All Ganeti daemons re-open the log file(s) when sent a SIGHUP signal.
**logrotate**(8) can be used to rotate Ganeti's log files. **logrotate**(8) can be used to rotate Ganeti's log files.
Common field formatting
-----------------------
Multiple ganeti commands use the same framework for tabular listing of
resources (e.g. **gnt-instance list**, **gnt-node list**, **gnt-group
list**, **gnt-debug locks**, etc.). For these commands, special states
are denoted via a special symbol (in terse mode) or a string (in
verbose mode):
*, (offline)
The node in question is marked offline, and thus it cannot be