#
#
-# Copyright (C) 2006, 2007, 2008, 2009, 2010 Google Inc.
+# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Google Inc.
#
# 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
import socket
import tempfile
import shutil
+import itertools
from ganeti import ssh
from ganeti import utils
from ganeti import compat
from ganeti import masterd
from ganeti import netutils
-from ganeti import ht
from ganeti import query
from ganeti import qlang
+from ganeti import opcodes
import ganeti.masterd.instance # pylint: disable-msg=W0611
-# Common opcode attributes
-#: output fields for a query operation
-_POutputFields = ("output_fields", ht.NoDefault, ht.TListOf(ht.TNonEmptyString))
+def _SupportsOob(cfg, node):
+ """Tells if node supports OOB.
+ @type cfg: L{config.ConfigWriter}
+ @param cfg: The cluster configuration
+ @type node: L{objects.Node}
+ @param node: The node
+ @return: The OOB script if supported or an empty string otherwise
-#: the shutdown timeout
-_PShutdownTimeout = ("shutdown_timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT,
- ht.TPositiveInt)
-
-#: the force parameter
-_PForce = ("force", False, ht.TBool)
-
-#: a required instance name (for single-instance LUs)
-_PInstanceName = ("instance_name", ht.NoDefault, ht.TNonEmptyString)
-
-#: Whether to ignore offline nodes
-_PIgnoreOfflineNodes = ("ignore_offline_nodes", False, ht.TBool)
-
-#: a required node name (for single-node LUs)
-_PNodeName = ("node_name", ht.NoDefault, ht.TNonEmptyString)
-
-#: a required node group name (for single-group LUs)
-_PGroupName = ("group_name", ht.NoDefault, ht.TNonEmptyString)
-
-#: the migration type (live/non-live)
-_PMigrationMode = ("mode", None,
- ht.TOr(ht.TNone, ht.TElemOf(constants.HT_MIGRATION_MODES)))
-
-#: the obsolete 'live' mode (boolean)
-_PMigrationLive = ("live", None, ht.TMaybeBool)
+ """
+ return cfg.GetNdParams(node)[constants.ND_OOB_PROGRAM]
# End types
@ivar dry_run_result: the value (if any) that will be returned to the caller
in dry-run mode (signalled by opcode dry_run parameter)
- @cvar _OP_PARAMS: a list of opcode attributes, the default values
- they should get if not already defined, and types they must match
"""
HPATH = None
HTYPE = None
- _OP_PARAMS = []
REQ_BGL = True
def __init__(self, processor, op, context, rpc):
# Tasklets
self.tasklets = None
- # The new kind-of-type-system
- op_id = self.op.OP_ID
- for attr_name, aval, test in self._OP_PARAMS:
- if not hasattr(op, attr_name):
- if aval == ht.NoDefault:
- raise errors.OpPrereqError("Required parameter '%s.%s' missing" %
- (op_id, attr_name), errors.ECODE_INVAL)
- else:
- if callable(aval):
- dval = aval()
- else:
- dval = aval
- setattr(self.op, attr_name, dval)
- attr_val = getattr(op, attr_name)
- if test == ht.NoType:
- # no tests here
- continue
- if not callable(test):
- raise errors.ProgrammerError("Validation for parameter '%s.%s' failed,"
- " given type is not a proper type (%s)" %
- (op_id, attr_name, test))
- if not test(attr_val):
- logging.error("OpCode %s, parameter %s, has invalid type %s/value %s",
- self.op.OP_ID, attr_name, type(attr_val), attr_val)
- raise errors.OpPrereqError("Parameter '%s.%s' fails validation" %
- (op_id, attr_name), errors.ECODE_INVAL)
+ # Validate opcode parameters and set defaults
+ self.op.Validate(True)
self.CheckArguments()
@return: List of L{objects.QueryFieldDefinition}
"""
- if fields is None:
- # Client requests all fields
- fdefs = query.GetAllFields(cls.FIELDS.values())
- else:
- fdefs = query.Query(cls.FIELDS, fields).GetFields()
-
- return objects.QueryFieldsResponse(fields=fdefs).ToDict()
+ return query.QueryFields(cls.FIELDS, fields)
def ExpandNames(self, lu):
"""Expand names for this query.
"""Collect data and execute query.
"""
- data = self._GetQueryData(lu)
-
- return objects.QueryResponse(data=self.query.Query(data),
- fields=self.query.GetFields()).ToDict()
+ return query.GetQueryResponse(self.query, self._GetQueryData(lu))
def OldStyleQuery(self, lu):
"""Collect data and execute query.
raise errors.OpExecError(msg)
-def _RequireFileStorage():
- """Checks that file storage is enabled.
-
- @raise errors.OpPrereqError: when file storage is disabled
-
- """
- if not constants.ENABLE_FILE_STORAGE:
- raise errors.OpPrereqError("File storage disabled at configure time",
- errors.ECODE_INVAL)
-
-
-def _CheckDiskTemplate(template):
- """Ensure a given disk template is valid.
-
- """
- if template not in constants.DISK_TEMPLATES:
- msg = ("Invalid disk template name '%s', valid templates are: %s" %
- (template, utils.CommaJoin(constants.DISK_TEMPLATES)))
- raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
- if template == constants.DT_FILE:
- _RequireFileStorage()
- return True
-
-
-def _CheckStorageType(storage_type):
- """Ensure a given storage type is valid.
-
- """
- if storage_type not in constants.VALID_STORAGE_TYPES:
- raise errors.OpPrereqError("Unknown storage type: %s" % storage_type,
- errors.ECODE_INVAL)
- if storage_type == constants.ST_FILE:
- _RequireFileStorage()
- return True
-
-
def _GetClusterDomainSecret():
"""Reads the cluster domain secret.
"""Build a list of nic information tuples.
This list is suitable to be passed to _BuildInstanceHookEnv or as a return
- value in LUQueryInstanceData.
+ value in LUInstanceQueryData.
@type lu: L{LogicalUnit}
@param lu: the logical unit on whose behalf we execute
" iallocator.")
-class LUPostInitCluster(LogicalUnit):
+class LUClusterPostInit(LogicalUnit):
"""Logical unit for running hooks after cluster initialization.
"""
return True
-class LUDestroyCluster(LogicalUnit):
+class LUClusterDestroy(LogicalUnit):
"""Logical unit for destroying the cluster.
"""
def _VerifyCertificate(filename):
- """Verifies a certificate for LUVerifyCluster.
+ """Verifies a certificate for LUClusterVerify.
@type filename: string
@param filename: Path to PEM file
cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
utils.ReadFile(filename))
except Exception, err: # pylint: disable-msg=W0703
- return (LUVerifyCluster.ETYPE_ERROR,
+ return (LUClusterVerify.ETYPE_ERROR,
"Failed to load X509 certificate %s: %s" % (filename, err))
(errcode, msg) = \
if errcode is None:
return (None, fnamemsg)
elif errcode == utils.CERT_WARNING:
- return (LUVerifyCluster.ETYPE_WARNING, fnamemsg)
+ return (LUClusterVerify.ETYPE_WARNING, fnamemsg)
elif errcode == utils.CERT_ERROR:
- return (LUVerifyCluster.ETYPE_ERROR, fnamemsg)
+ return (LUClusterVerify.ETYPE_ERROR, fnamemsg)
raise errors.ProgrammerError("Unhandled certificate error code %r" % errcode)
-class LUVerifyCluster(LogicalUnit):
+class LUClusterVerify(LogicalUnit):
"""Verifies the cluster status.
"""
HPATH = "cluster-verify"
HTYPE = constants.HTYPE_CLUSTER
- _OP_PARAMS = [
- ("skip_checks", ht.EmptyList,
- ht.TListOf(ht.TElemOf(constants.VERIFY_OPTIONAL_CHECKS))),
- ("verbose", False, ht.TBool),
- ("error_codes", False, ht.TBool),
- ("debug_simulate_errors", False, ht.TBool),
- ]
REQ_BGL = False
TCLUSTER = "cluster"
EINSTANCEMISSINGDISK = (TINSTANCE, "EINSTANCEMISSINGDISK")
EINSTANCEFAULTYDISK = (TINSTANCE, "EINSTANCEFAULTYDISK")
EINSTANCEWRONGNODE = (TINSTANCE, "EINSTANCEWRONGNODE")
+ EINSTANCESPLITGROUPS = (TINSTANCE, "EINSTANCESPLITGROUPS")
ENODEDRBD = (TNODE, "ENODEDRBD")
ENODEDRBDHELPER = (TNODE, "ENODEDRBDHELPER")
ENODEFILECHECK = (TNODE, "ENODEFILECHECK")
ENODEVERSION = (TNODE, "ENODEVERSION")
ENODESETUP = (TNODE, "ENODESETUP")
ENODETIME = (TNODE, "ENODETIME")
+ ENODEOOBPATH = (TNODE, "ENODEOOBPATH")
ETYPE_FIELD = "code"
ETYPE_ERROR = "ERROR"
_ErrorIf(test, self.ENODEHV, node,
"hypervisor %s verify failure: '%s'", hv_name, hv_result)
+ hvp_result = nresult.get(constants.NV_HVPARAMS, None)
+ if ninfo.vm_capable and isinstance(hvp_result, list):
+ for item, hv_name, hv_result in hvp_result:
+ _ErrorIf(True, self.ENODEHV, node,
+ "hypervisor %s parameter verify failure (source %s): %s",
+ hv_name, item, hv_result)
+
test = nresult.get(constants.NV_NODESETUP,
["Missing NODESETUP results"])
_ErrorIf(test, self.ENODESETUP, node, "node setup error: %s",
needed_mem += bep[constants.BE_MEMORY]
test = n_img.mfree < needed_mem
self._ErrorIf(test, self.ENODEN1, node,
- "not enough memory on to accommodate"
- " failovers should peer node %s fail", prinode)
+ "not enough memory to accomodate instance failovers"
+ " should node %s fail", prinode)
def _VerifyNodeFiles(self, ninfo, nresult, file_list, local_cksum,
master_files):
"OSes present on reference node %s but missing on this node: %s",
base.name, utils.CommaJoin(missing))
+ def _VerifyOob(self, ninfo, nresult):
+ """Verifies out of band functionality of a node.
+
+ @type ninfo: L{objects.Node}
+ @param ninfo: the node to check
+ @param nresult: the remote results for the node
+
+ """
+ node = ninfo.name
+ # We just have to verify the paths on master and/or master candidates
+ # as the oob helper is invoked on the master
+ if ((ninfo.master_candidate or ninfo.master_capable) and
+ constants.NV_OOB_PATHS in nresult):
+ for path_result in nresult[constants.NV_OOB_PATHS]:
+ self._ErrorIf(path_result, self.ENODEOOBPATH, node, path_result)
+
def _UpdateNodeVolumes(self, ninfo, nresult, nimg, vg_name):
"""Verifies and updates the node volume data.
@param node_image: Node objects
@type instanceinfo: dict of (name, L{objects.Instance})
@param instanceinfo: Instance objects
+ @rtype: {instance: {node: [(succes, payload)]}}
+ @return: a dictionary of per-instance dictionaries with nodes as
+ keys and disk information as values; the disk information is a
+ list of tuples (success, payload)
"""
_ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
node_disks = {}
node_disks_devonly = {}
+ diskless_instances = set()
+ diskless = constants.DT_DISKLESS
for nname in nodelist:
+ node_instances = list(itertools.chain(node_image[nname].pinst,
+ node_image[nname].sinst))
+ diskless_instances.update(inst for inst in node_instances
+ if instanceinfo[inst].disk_template == diskless)
disks = [(inst, disk)
- for instlist in [node_image[nname].pinst,
- node_image[nname].sinst]
- for inst in instlist
+ for inst in node_instances
for disk in instanceinfo[inst].disks]
if not disks:
instdisk = {}
for (nname, nres) in result.items():
- if nres.offline:
- # Ignore offline node
- continue
-
disks = node_disks[nname]
- msg = nres.fail_msg
- _ErrorIf(msg, self.ENODERPC, nname,
- "while getting disk information: %s", nres.fail_msg)
- if msg:
+ if nres.offline:
# No data from this node
- data = len(disks) * [None]
+ data = len(disks) * [(False, "node offline")]
else:
- data = nres.payload
+ msg = nres.fail_msg
+ _ErrorIf(msg, self.ENODERPC, nname,
+ "while getting disk information: %s", msg)
+ if msg:
+ # No data from this node
+ data = len(disks) * [(False, msg)]
+ else:
+ data = []
+ for idx, i in enumerate(nres.payload):
+ if isinstance(i, (tuple, list)) and len(i) == 2:
+ data.append(i)
+ else:
+ logging.warning("Invalid result from node %s, entry %d: %s",
+ nname, idx, i)
+ data.append((False, "Invalid result from the remote node"))
for ((inst, _), status) in zip(disks, data):
instdisk.setdefault(inst, {}).setdefault(nname, []).append(status)
+ # Add empty entries for diskless instances.
+ for inst in diskless_instances:
+ assert inst not in instdisk
+ instdisk[inst] = {}
+
assert compat.all(len(statuses) == len(instanceinfo[inst].disks) and
- len(nnames) <= len(instanceinfo[inst].all_nodes)
+ len(nnames) <= len(instanceinfo[inst].all_nodes) and
+ compat.all(isinstance(s, (tuple, list)) and
+ len(s) == 2 for s in statuses)
for inst, nnames in instdisk.items()
for nname, statuses in nnames.items())
+ assert set(instdisk) == set(instanceinfo), "instdisk consistency failure"
return instdisk
+ def _VerifyHVP(self, hvp_data):
+ """Verifies locally the syntax of the hypervisor parameters.
+
+ """
+ for item, hv_name, hv_params in hvp_data:
+ msg = ("hypervisor %s parameters syntax check (source %s): %%s" %
+ (item, hv_name))
+ try:
+ hv_class = hypervisor.GetHypervisor(hv_name)
+ utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
+ hv_class.CheckParameterSyntax(hv_params)
+ except errors.GenericError, err:
+ self._ErrorIf(True, self.ECLUSTERCFG, None, msg % str(err))
+
+
def BuildHooksEnv(self):
"""Build hooks env.
"""Verify integrity of cluster, performing various test on nodes.
"""
+ # This method has too many local variables. pylint: disable-msg=R0914
self.bad = False
_ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
verbose = self.op.verbose
cluster = self.cfg.GetClusterInfo()
nodelist = utils.NiceSort(self.cfg.GetNodeList())
nodeinfo = [self.cfg.GetNodeInfo(nname) for nname in nodelist]
+ nodeinfo_byname = dict(zip(nodelist, nodeinfo))
instancelist = utils.NiceSort(self.cfg.GetInstanceList())
instanceinfo = dict((iname, self.cfg.GetInstanceInfo(iname))
for iname in instancelist)
+ groupinfo = self.cfg.GetAllNodeGroupsInfo()
i_non_redundant = [] # Non redundant instances
i_non_a_balanced = [] # Non auto-balanced instances
n_offline = 0 # Count of offline nodes
local_checksums = utils.FingerprintFiles(file_names)
+ # Compute the set of hypervisor parameters
+ hvp_data = []
+ for hv_name in hypervisors:
+ hvp_data.append(("cluster", hv_name, cluster.GetHVDefaults(hv_name)))
+ for os_name, os_hvp in cluster.os_hvp.items():
+ for hv_name, hv_params in os_hvp.items():
+ if not hv_params:
+ continue
+ full_params = cluster.GetHVDefaults(hv_name, os_name=os_name)
+ hvp_data.append(("os %s" % os_name, hv_name, full_params))
+ # TODO: collapse identical parameter values in a single one
+ for instance in instanceinfo.values():
+ if not instance.hvparams:
+ continue
+ hvp_data.append(("instance %s" % instance.name, instance.hypervisor,
+ cluster.FillHV(instance)))
+ # and verify them locally
+ self._VerifyHVP(hvp_data)
+
feedback_fn("* Gathering data (%d nodes)" % len(nodelist))
node_verify_param = {
constants.NV_FILELIST: file_names,
constants.NV_NODELIST: [node.name for node in nodeinfo
if not node.offline],
constants.NV_HYPERVISOR: hypervisors,
+ constants.NV_HVPARAMS: hvp_data,
constants.NV_NODENETTEST: [(node.name, node.primary_ip,
node.secondary_ip) for node in nodeinfo
if not node.offline],
vm_capable=node.vm_capable))
for node in nodeinfo)
+ # Gather OOB paths
+ oob_paths = []
+ for node in nodeinfo:
+ path = _SupportsOob(self.cfg, node)
+ if path and path not in oob_paths:
+ oob_paths.append(path)
+
+ if oob_paths:
+ node_verify_param[constants.NV_OOB_PATHS] = oob_paths
+
for instance in instancelist:
inst_config = instanceinfo[instance]
self._VerifyNodeFiles(node_i, nresult, file_names, local_checksums,
master_files)
+ self._VerifyOob(node_i, nresult)
+
if nimg.vm_capable:
self._VerifyNodeLVM(node_i, nresult, vg_name)
self._VerifyNodeDrbd(node_i, nresult, instanceinfo, drbd_helper,
# FIXME: does not support file-backed instances
if not inst_config.secondary_nodes:
i_non_redundant.append(instance)
+
_ErrorIf(len(inst_config.secondary_nodes) > 1, self.EINSTANCELAYOUT,
instance, "instance has multiple secondary nodes: %s",
utils.CommaJoin(inst_config.secondary_nodes),
code=self.ETYPE_WARNING)
+ if inst_config.disk_template in constants.DTS_NET_MIRROR:
+ pnode = inst_config.primary_node
+ instance_nodes = utils.NiceSort(inst_config.all_nodes)
+ instance_groups = {}
+
+ for node in instance_nodes:
+ instance_groups.setdefault(nodeinfo_byname[node].group,
+ []).append(node)
+
+ pretty_list = [
+ "%s (group %s)" % (utils.CommaJoin(nodes), groupinfo[group].name)
+ # Sort so that we always list the primary node first.
+ for group, nodes in sorted(instance_groups.items(),
+ key=lambda (_, nodes): pnode in nodes,
+ reverse=True)]
+
+ self._ErrorIf(len(instance_groups) > 1, self.EINSTANCESPLITGROUPS,
+ instance, "instance has primary and secondary nodes in"
+ " different groups: %s", utils.CommaJoin(pretty_list),
+ code=self.ETYPE_WARNING)
+
if not cluster.FillBE(inst_config)[constants.BE_AUTO_BALANCE]:
i_non_a_balanced.append(instance)
return lu_result
-class LUVerifyDisks(NoHooksLU):
+class LUClusterVerifyDisks(NoHooksLU):
"""Verifies the cluster disks status.
"""
"""
result = res_nodes, res_instances, res_missing = {}, [], {}
- nodes = utils.NiceSort(self.cfg.GetNodeList())
- instances = [self.cfg.GetInstanceInfo(name)
- for name in self.cfg.GetInstanceList()]
+ nodes = utils.NiceSort(self.cfg.GetVmCapableNodeList())
+ instances = self.cfg.GetAllInstancesInfo().values()
nv_dict = {}
for inst in instances:
inst_lvs = {}
- if (not inst.admin_up or
- inst.disk_template not in constants.DTS_NET_MIRROR):
+ if not inst.admin_up:
continue
inst.MapLVsByNode(inst_lvs)
# transform { iname: {node: [vol,],},} to {(node, vol): iname}
if not nv_dict:
return result
- vg_names = self.rpc.call_vg_list(nodes)
- vg_names.Raise("Cannot get list of VGs")
-
- for node in nodes:
- # node_volume
- node_res = self.rpc.call_lv_list([node],
- vg_names[node].payload.keys())[node]
+ node_lvs = self.rpc.call_lv_list(nodes, [])
+ for node, node_res in node_lvs.items():
if node_res.offline:
continue
msg = node_res.fail_msg
return result
-class LURepairDiskSizes(NoHooksLU):
+class LUClusterRepairDiskSizes(NoHooksLU):
"""Verifies the cluster disks sizes.
"""
- _OP_PARAMS = [("instances", ht.EmptyList, ht.TListOf(ht.TNonEmptyString))]
REQ_BGL = False
def ExpandNames(self):
return changed
-class LURenameCluster(LogicalUnit):
+class LUClusterRename(LogicalUnit):
"""Rename the cluster.
"""
HPATH = "cluster-rename"
HTYPE = constants.HTYPE_CLUSTER
- _OP_PARAMS = [("name", ht.NoDefault, ht.TNonEmptyString)]
def BuildHooksEnv(self):
"""Build hooks env.
return clustername
-class LUSetClusterParams(LogicalUnit):
+class LUClusterSetParams(LogicalUnit):
"""Change the parameters of the cluster.
"""
HPATH = "cluster-modify"
HTYPE = constants.HTYPE_CLUSTER
- _OP_PARAMS = [
- ("vg_name", None, ht.TMaybeString),
- ("enabled_hypervisors", None,
- ht.TOr(ht.TAnd(ht.TListOf(ht.TElemOf(constants.HYPER_TYPES)), ht.TTrue),
- ht.TNone)),
- ("hvparams", None, ht.TOr(ht.TDictOf(ht.TNonEmptyString, ht.TDict),
- ht.TNone)),
- ("beparams", None, ht.TOr(ht.TDict, ht.TNone)),
- ("os_hvp", None, ht.TOr(ht.TDictOf(ht.TNonEmptyString, ht.TDict),
- ht.TNone)),
- ("osparams", None, ht.TOr(ht.TDictOf(ht.TNonEmptyString, ht.TDict),
- ht.TNone)),
- ("candidate_pool_size", None, ht.TOr(ht.TStrictPositiveInt, ht.TNone)),
- ("uid_pool", None, ht.NoType),
- ("add_uids", None, ht.NoType),
- ("remove_uids", None, ht.NoType),
- ("maintain_node_health", None, ht.TMaybeBool),
- ("prealloc_wipe_disks", None, ht.TMaybeBool),
- ("nicparams", None, ht.TOr(ht.TDict, ht.TNone)),
- ("ndparams", None, ht.TOr(ht.TDict, ht.TNone)),
- ("drbd_helper", None, ht.TOr(ht.TString, ht.TNone)),
- ("default_iallocator", None, ht.TOr(ht.TString, ht.TNone)),
- ("master_netdev", None, ht.TOr(ht.TString, ht.TNone)),
- ("reserved_lvs", None, ht.TOr(ht.TListOf(ht.TNonEmptyString), ht.TNone)),
- ("hidden_os", None, ht.TOr(ht.TListOf(\
- ht.TAnd(ht.TList,
- ht.TIsLength(2),
- ht.TMap(lambda v: v[0], ht.TElemOf(constants.DDMS_VALUES)))),
- ht.TNone)),
- ("blacklisted_os", None, ht.TOr(ht.TListOf(\
- ht.TAnd(ht.TList,
- ht.TIsLength(2),
- ht.TMap(lambda v: v[0], ht.TElemOf(constants.DDMS_VALUES)))),
- ht.TNone)),
- ]
REQ_BGL = False
def CheckArguments(self):
_UploadHelper(lu, vm_nodes, fname)
-class LURedistributeConfig(NoHooksLU):
+class LUClusterRedistConf(NoHooksLU):
"""Force the redistribution of cluster configuration.
This is a very simple LU.
return result
-class LUOutOfBand(NoHooksLU):
+class LUOobCommand(NoHooksLU):
"""Logical unit for OOB handling.
"""
- _OP_PARAMS = [
- _PNodeName,
- ("command", None, ht.TElemOf(constants.OOB_COMMANDS)),
- ("timeout", constants.OOB_TIMEOUT, ht.TInt),
- ]
REG_BGL = False
def CheckPrereq(self):
Any errors are signaled by raising errors.OpPrereqError.
"""
- self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
- node = self.cfg.GetNodeInfo(self.op.node_name)
-
- if node is None:
- raise errors.OpPrereqError("Node %s not found" % self.op.node_name)
-
- self.oob_program = self.cfg.GetNdParams(node)[constants.ND_OOB_PROGRAM]
+ self.nodes = []
+ for node_name in self.op.node_names:
+ node = self.cfg.GetNodeInfo(node_name)
- if not self.oob_program:
- raise errors.OpPrereqError("OOB is not supported for node %s" %
- self.op.node_name)
+ if node is None:
+ raise errors.OpPrereqError("Node %s not found" % node_name,
+ errors.ECODE_NOENT)
+ else:
+ self.nodes.append(node)
- self.op.node_name = node.name
- self.node = node
+ if (self.op.command == constants.OOB_POWER_OFF and not node.offline):
+ raise errors.OpPrereqError(("Cannot power off node %s because it is"
+ " not marked offline") % node_name,
+ errors.ECODE_STATE)
def ExpandNames(self):
"""Gather locks we need.
"""
+ if self.op.node_names:
+ self.op.node_names = [_ExpandNodeName(self.cfg, name)
+ for name in self.op.node_names]
+ else:
+ self.op.node_names = self.cfg.GetNodeList()
+
self.needed_locks = {
- locking.LEVEL_NODE: [self.op.node_name],
+ locking.LEVEL_NODE: self.op.node_names,
}
def Exec(self, feedback_fn):
"""
master_node = self.cfg.GetMasterNode()
+ ret = []
- logging.info("Executing out-of-band command '%s' using '%s' on %s",
- self.op.command, self.oob_program, self.op.node_name)
- result = self.rpc.call_run_oob(master_node, self.oob_program,
- self.op.command, self.op.node_name,
- self.op.timeout)
+ for node in self.nodes:
+ node_entry = [(constants.RS_NORMAL, node.name)]
+ ret.append(node_entry)
- result.Raise("An error occurred on execution of OOB helper")
+ oob_program = _SupportsOob(self.cfg, node)
- self._CheckPayload(result)
+ if not oob_program:
+ node_entry.append((constants.RS_UNAVAIL, None))
+ continue
- if self.op.command == constants.OOB_HEALTH:
- # For health we should log important events
- for item, status in result.payload:
- if status in [constants.OOB_STATUS_WARNING,
- constants.OOB_STATUS_CRITICAL]:
- logging.warning("On node '%s' item '%s' has status '%s'",
- self.op.node_name, item, status)
+ logging.info("Executing out-of-band command '%s' using '%s' on %s",
+ self.op.command, oob_program, node.name)
+ result = self.rpc.call_run_oob(master_node, oob_program,
+ self.op.command, node.name,
+ self.op.timeout)
- return result.payload
+ if result.fail_msg:
+ self.LogWarning("On node '%s' out-of-band RPC failed with: %s",
+ node.name, result.fail_msg)
+ node_entry.append((constants.RS_NODATA, None))
+ else:
+ try:
+ self._CheckPayload(result)
+ except errors.OpExecError, err:
+ self.LogWarning("The payload returned by '%s' is not valid: %s",
+ node.name, err)
+ node_entry.append((constants.RS_NODATA, None))
+ else:
+ if self.op.command == constants.OOB_HEALTH:
+ # For health we should log important events
+ for item, status in result.payload:
+ if status in [constants.OOB_STATUS_WARNING,
+ constants.OOB_STATUS_CRITICAL]:
+ self.LogWarning("On node '%s' item '%s' has status '%s'",
+ node.name, item, status)
+
+ if self.op.command == constants.OOB_POWER_ON:
+ node.powered = True
+ elif self.op.command == constants.OOB_POWER_OFF:
+ node.powered = False
+ elif self.op.command == constants.OOB_POWER_STATUS:
+ powered = result.payload[constants.OOB_POWER_STATUS_POWERED]
+ if powered != node.powered:
+ logging.warning(("Recorded power state (%s) of node '%s' does not"
+ " match actual power state (%s)"), node.powered,
+ node.name, powered)
+
+ # For configuration changing commands we should update the node
+ if self.op.command in (constants.OOB_POWER_ON,
+ constants.OOB_POWER_OFF):
+ self.cfg.Update(node, feedback_fn)
+
+ node_entry.append((constants.RS_NORMAL, result.payload))
+
+ return ret
def _CheckPayload(self, result):
"""Checks if the payload is valid.
if not isinstance(result.payload, list):
errs.append("command 'health' is expected to return a list but got %s" %
type(result.payload))
- for item, status in result.payload:
- if status not in constants.OOB_STATUSES:
- errs.append("health item '%s' has invalid status '%s'" %
- (item, status))
+ else:
+ for item, status in result.payload:
+ if status not in constants.OOB_STATUSES:
+ errs.append("health item '%s' has invalid status '%s'" %
+ (item, status))
if self.op.command == constants.OOB_POWER_STATUS:
if not isinstance(result.payload, dict):
-class LUDiagnoseOS(NoHooksLU):
+class LUOsDiagnose(NoHooksLU):
"""Logical unit for OS diagnose/query.
"""
- _OP_PARAMS = [
- _POutputFields,
- ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
- ]
REQ_BGL = False
_HID = "hidden"
_BLK = "blacklisted"
"""Compute the list of OSes.
"""
- valid_nodes = [node for node in self.cfg.GetOnlineNodeList()]
+ valid_nodes = [node.name
+ for node in self.cfg.GetAllNodesInfo().values()
+ if not node.offline and node.vm_capable]
node_data = self.rpc.call_os_diagnose(valid_nodes)
pol = self._DiagnoseByOS(node_data)
output = []
return output
-class LURemoveNode(LogicalUnit):
+class LUNodeRemove(LogicalUnit):
"""Logical unit for removing a node.
"""
HPATH = "node-remove"
HTYPE = constants.HTYPE_NODE
- _OP_PARAMS = [
- _PNodeName,
- ]
def BuildHooksEnv(self):
"""Build hooks env.
node_to_primary = None
node_to_secondary = None
+ if query.NQ_OOB in self.requested_data:
+ oob_support = dict((name, bool(_SupportsOob(lu.cfg, node)))
+ for name, node in all_info.iteritems())
+ else:
+ oob_support = None
+
if query.NQ_GROUP in self.requested_data:
groups = lu.cfg.GetAllNodeGroupsInfo()
else:
return query.NodeQueryData([all_info[name] for name in nodenames],
live_data, lu.cfg.GetMasterNode(),
- node_to_primary, node_to_secondary, groups)
+ node_to_primary, node_to_secondary, groups,
+ oob_support, lu.cfg.GetClusterInfo())
-class LUQueryNodes(NoHooksLU):
+class LUNodeQuery(NoHooksLU):
"""Logical unit for querying nodes.
"""
# pylint: disable-msg=W0142
- _OP_PARAMS = [
- _POutputFields,
- ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
- ("use_locking", False, ht.TBool),
- ]
REQ_BGL = False
def CheckArguments(self):
return self.nq.OldStyleQuery(self)
-class LUQueryNodeVolumes(NoHooksLU):
+class LUNodeQueryvols(NoHooksLU):
"""Logical unit for getting volumes on node(s).
"""
- _OP_PARAMS = [
- _POutputFields,
- ("nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
- ]
REQ_BGL = False
_FIELDS_DYNAMIC = utils.FieldSet("phys", "vg", "name", "size", "instance")
_FIELDS_STATIC = utils.FieldSet("node")
return output
-class LUQueryNodeStorage(NoHooksLU):
+class LUNodeQueryStorage(NoHooksLU):
"""Logical unit for getting information on storage units on node(s).
"""
_FIELDS_STATIC = utils.FieldSet(constants.SF_NODE)
- _OP_PARAMS = [
- _POutputFields,
- ("nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
- ("storage_type", ht.NoDefault, _CheckStorageType),
- ("name", None, ht.TMaybeString),
- ]
REQ_BGL = False
def CheckArguments(self):
"""Computes the list of instances and their attributes.
"""
+ cluster = lu.cfg.GetClusterInfo()
all_info = lu.cfg.GetAllInstancesInfo()
instance_names = self._GetNames(lu, all_info.keys(), locking.LEVEL_INSTANCE)
instance_list = [all_info[name] for name in instance_names]
- nodes = frozenset([inst.primary_node for inst in instance_list])
+ nodes = frozenset(itertools.chain(*(inst.all_nodes
+ for inst in instance_list)))
hv_list = list(set([inst.hypervisor for inst in instance_list]))
bad_nodes = []
offline_nodes = []
+ wrongnode_inst = set()
# Gather data as requested
- if query.IQ_LIVE in self.requested_data:
+ if self.requested_data & set([query.IQ_LIVE, query.IQ_CONSOLE]):
live_data = {}
node_data = lu.rpc.call_all_instances_info(nodes, hv_list)
for name in nodes:
if result.fail_msg:
bad_nodes.append(name)
elif result.payload:
- live_data.update(result.payload)
+ for inst in result.payload:
+ if all_info[inst].primary_node == name:
+ live_data.update(result.payload)
+ else:
+ wrongnode_inst.add(inst)
# else no instance is alive
else:
live_data = {}
else:
disk_usage = None
+ if query.IQ_CONSOLE in self.requested_data:
+ consinfo = {}
+ for inst in instance_list:
+ if inst.name in live_data:
+ # Instance is running
+ consinfo[inst.name] = _GetInstanceConsole(cluster, inst)
+ else:
+ consinfo[inst.name] = None
+ assert set(consinfo.keys()) == set(instance_names)
+ else:
+ consinfo = None
+
return query.InstanceQueryData(instance_list, lu.cfg.GetClusterInfo(),
disk_usage, offline_nodes, bad_nodes,
- live_data)
-
-
-#: Query type implementations
-_QUERY_IMPL = {
- constants.QR_INSTANCE: _InstanceQuery,
- constants.QR_NODE: _NodeQuery,
- }
-
-
-def _GetQueryImplementation(name):
- """Returns the implemtnation for a query type.
-
- @param name: Query type, must be one of L{constants.QR_OP_QUERY}
-
- """
- try:
- return _QUERY_IMPL[name]
- except KeyError:
- raise errors.OpPrereqError("Unknown query resource '%s'" % name,
- errors.ECODE_INVAL)
+ live_data, wrongnode_inst, consinfo)
class LUQuery(NoHooksLU):
"""
# pylint: disable-msg=W0142
- _OP_PARAMS = [
- ("what", ht.NoDefault, ht.TElemOf(constants.QR_OP_QUERY)),
- ("fields", ht.NoDefault, ht.TListOf(ht.TNonEmptyString)),
- ("filter", None, ht.TOr(ht.TNone,
- ht.TListOf(ht.TOr(ht.TNonEmptyString, ht.TList)))),
- ]
REQ_BGL = False
def CheckArguments(self):
"""
# pylint: disable-msg=W0142
- _OP_PARAMS = [
- ("what", ht.NoDefault, ht.TElemOf(constants.QR_OP_QUERY)),
- ("fields", None, ht.TOr(ht.TNone, ht.TListOf(ht.TNonEmptyString))),
- ]
REQ_BGL = False
def CheckArguments(self):
return self.qcls.FieldsQuery(self.op.fields)
-class LUModifyNodeStorage(NoHooksLU):
+class LUNodeModifyStorage(NoHooksLU):
"""Logical unit for modifying a storage volume on a node.
"""
- _OP_PARAMS = [
- _PNodeName,
- ("storage_type", ht.NoDefault, _CheckStorageType),
- ("name", ht.NoDefault, ht.TNonEmptyString),
- ("changes", ht.NoDefault, ht.TDict),
- ]
REQ_BGL = False
def CheckArguments(self):
(self.op.name, self.op.node_name))
-class LUAddNode(LogicalUnit):
+class LUNodeAdd(LogicalUnit):
"""Logical unit for adding node to the cluster.
"""
HPATH = "node-add"
HTYPE = constants.HTYPE_NODE
- _OP_PARAMS = [
- _PNodeName,
- ("primary_ip", None, ht.NoType),
- ("secondary_ip", None, ht.TMaybeString),
- ("readd", False, ht.TBool),
- ("group", None, ht.TMaybeString),
- ("master_capable", None, ht.TMaybeBool),
- ("vm_capable", None, ht.TMaybeBool),
- ("ndparams", None, ht.TOr(ht.TDict, ht.TNone)),
- ]
_NFLAGS = ["master_capable", "vm_capable"]
def CheckArguments(self):
new_node = self.new_node
node = new_node.name
+ # We adding a new node so we assume it's powered
+ new_node.powered = True
+
# for re-adds, reset the offline/drained/master-candidate flags;
# we need to reset here, otherwise offline would prevent RPC calls
# later in the procedure; this also means that if the re-add
if self.op.ndparams:
new_node.ndparams = self.op.ndparams
+ else:
+ new_node.ndparams = {}
# check connectivity
result = self.rpc.call_version([node])[node]
self.context.AddNode(new_node, self.proc.GetECId())
-class LUSetNodeParams(LogicalUnit):
+class LUNodeSetParams(LogicalUnit):
"""Modifies the parameters of a node.
@cvar _F2R: a dictionary from tuples of flags (mc, drained, offline)
"""
HPATH = "node-modify"
HTYPE = constants.HTYPE_NODE
- _OP_PARAMS = [
- _PNodeName,
- ("master_candidate", None, ht.TMaybeBool),
- ("offline", None, ht.TMaybeBool),
- ("drained", None, ht.TMaybeBool),
- ("auto_promote", False, ht.TBool),
- ("master_capable", None, ht.TMaybeBool),
- ("vm_capable", None, ht.TMaybeBool),
- ("secondary_ip", None, ht.TMaybeString),
- ("ndparams", None, ht.TOr(ht.TDict, ht.TNone)),
- _PForce,
- ]
REQ_BGL = False
(_ROLE_CANDIDATE, _ROLE_DRAINED, _ROLE_OFFLINE, _ROLE_REGULAR) = range(4)
_F2R = {
errors.ECODE_STATE)
if node.master_candidate and self.might_demote and not self.lock_all:
- assert not self.op.auto_promote, "auto-promote set but lock_all not"
+ assert not self.op.auto_promote, "auto_promote set but lock_all not"
# check if after removing the current node, we're missing master
# candidates
(mc_remaining, mc_should, _) = \
self.cfg.GetMasterCandidateStats(exceptions=[node.name])
if mc_remaining < mc_should:
raise errors.OpPrereqError("Not enough master candidates, please"
- " pass auto_promote to allow promotion",
- errors.ECODE_STATE)
+ " pass auto promote option to allow"
+ " promotion", errors.ECODE_STATE)
self.old_flags = old_flags = (node.master_candidate,
node.drained, node.offline)
# Past this point, any flag change to False means a transition
# away from the respective state, as only real changes are kept
+ # TODO: We might query the real power state if it supports OOB
+ if _SupportsOob(self.cfg, node):
+ if self.op.offline is False and not (node.powered or
+ self.op.powered == True):
+ raise errors.OpPrereqError(("Please power on node %s first before you"
+ " can reset offline state") %
+ self.op.node_name)
+ elif self.op.powered is not None:
+ raise errors.OpPrereqError(("Unable to change powered state for node %s"
+ " which does not support out-of-band"
+ " handling") % self.op.node_name)
+
# If we're being deofflined/drained, we'll MC ourself if needed
if (self.op.drained == False or self.op.offline == False or
(self.op.master_capable and not node.master_capable)):
if self.op.ndparams:
node.ndparams = self.new_ndparams
+ if self.op.powered is not None:
+ node.powered = self.op.powered
+
for attr in ["master_capable", "vm_capable"]:
val = getattr(self.op, attr)
if val is not None:
return result
-class LUPowercycleNode(NoHooksLU):
+class LUNodePowercycle(NoHooksLU):
"""Powercycles a node.
"""
- _OP_PARAMS = [
- _PNodeName,
- _PForce,
- ]
REQ_BGL = False
def CheckArguments(self):
return result.payload
-class LUQueryClusterInfo(NoHooksLU):
+class LUClusterQuery(NoHooksLU):
"""Query cluster configuration.
"""
"beparams": cluster.beparams,
"osparams": cluster.osparams,
"nicparams": cluster.nicparams,
+ "ndparams": cluster.ndparams,
"candidate_pool_size": cluster.candidate_pool_size,
"master_netdev": cluster.master_netdev,
"volume_group_name": cluster.volume_group_name,
"reserved_lvs": cluster.reserved_lvs,
"primary_ip_version": primary_ip_version,
"prealloc_wipe_disks": cluster.prealloc_wipe_disks,
+ "hidden_os": cluster.hidden_os,
+ "blacklisted_os": cluster.blacklisted_os,
}
return result
-class LUQueryConfigValues(NoHooksLU):
+class LUClusterConfigQuery(NoHooksLU):
"""Return configuration values.
"""
- _OP_PARAMS = [_POutputFields]
REQ_BGL = False
_FIELDS_DYNAMIC = utils.FieldSet()
_FIELDS_STATIC = utils.FieldSet("cluster_name", "master_node", "drain_flag",
return values
-class LUActivateInstanceDisks(NoHooksLU):
+class LUInstanceActivateDisks(NoHooksLU):
"""Bring up an instance's disks.
"""
- _OP_PARAMS = [
- _PInstanceName,
- ("ignore_size", False, ht.TBool),
- ]
REQ_BGL = False
def ExpandNames(self):
# SyncSource, etc.)
# 1st pass, assemble on all nodes in secondary mode
- for inst_disk in disks:
+ for idx, inst_disk in enumerate(disks):
for node, node_disk in inst_disk.ComputeNodeTree(instance.primary_node):
if ignore_size:
node_disk = node_disk.Copy()
node_disk.UnsetSize()
lu.cfg.SetDiskID(node_disk, node)
- result = lu.rpc.call_blockdev_assemble(node, node_disk, iname, False)
+ result = lu.rpc.call_blockdev_assemble(node, node_disk, iname, False, idx)
msg = result.fail_msg
if msg:
lu.proc.LogWarning("Could not prepare block device %s on node %s"
# FIXME: race condition on drbd migration to primary
# 2nd pass, do only the primary node
- for inst_disk in disks:
+ for idx, inst_disk in enumerate(disks):
dev_path = None
for node, node_disk in inst_disk.ComputeNodeTree(instance.primary_node):
node_disk = node_disk.Copy()
node_disk.UnsetSize()
lu.cfg.SetDiskID(node_disk, node)
- result = lu.rpc.call_blockdev_assemble(node, node_disk, iname, True)
+ result = lu.rpc.call_blockdev_assemble(node, node_disk, iname, True, idx)
msg = result.fail_msg
if msg:
lu.proc.LogWarning("Could not prepare block device %s on node %s"
raise errors.OpExecError("Disk consistency error")
-class LUDeactivateInstanceDisks(NoHooksLU):
+class LUInstanceDeactivateDisks(NoHooksLU):
"""Shutdown an instance's disks.
"""
- _OP_PARAMS = [
- _PInstanceName,
- ]
REQ_BGL = False
def ExpandNames(self):
"""
instance = self.instance
- _SafeShutdownInstanceDisks(self, instance)
+ if self.op.force:
+ _ShutdownInstanceDisks(self, instance)
+ else:
+ _SafeShutdownInstanceDisks(self, instance)
def _SafeShutdownInstanceDisks(lu, instance, disks=None):
if msg:
lu.LogWarning("Could not shutdown block device %s on node %s: %s",
disk.iv_name, node, msg)
- if not ignore_primary or node != instance.primary_node:
+ if ((node == instance.primary_node and not ignore_primary) or
+ (node != instance.primary_node and not result.offline)):
all_result = False
return all_result
or we cannot check the node
"""
- if req_sizes is not None:
- for vg, req_size in req_sizes.iteritems():
- _CheckNodesFreeDiskOnVG(lu, nodenames, vg, req_size)
+ for vg, req_size in req_sizes.items():
+ _CheckNodesFreeDiskOnVG(lu, nodenames, vg, req_size)
def _CheckNodesFreeDiskOnVG(lu, nodenames, vg, requested):
errors.ECODE_NORES)
-class LUStartupInstance(LogicalUnit):
+class LUInstanceStartup(LogicalUnit):
"""Starts an instance.
"""
HPATH = "instance-start"
HTYPE = constants.HTYPE_INSTANCE
- _OP_PARAMS = [
- _PInstanceName,
- _PForce,
- _PIgnoreOfflineNodes,
- ("hvparams", ht.EmptyDict, ht.TDict),
- ("beparams", ht.EmptyDict, ht.TDict),
- ]
REQ_BGL = False
def CheckArguments(self):
raise errors.OpExecError("Could not start instance: %s" % msg)
-class LURebootInstance(LogicalUnit):
+class LUInstanceReboot(LogicalUnit):
"""Reboot an instance.
"""
HPATH = "instance-reboot"
HTYPE = constants.HTYPE_INSTANCE
- _OP_PARAMS = [
- _PInstanceName,
- ("ignore_secondaries", False, ht.TBool),
- ("reboot_type", ht.NoDefault, ht.TElemOf(constants.REBOOT_TYPES)),
- _PShutdownTimeout,
- ]
REQ_BGL = False
def ExpandNames(self):
self.cfg.MarkInstanceUp(instance.name)
-class LUShutdownInstance(LogicalUnit):
+class LUInstanceShutdown(LogicalUnit):
"""Shutdown an instance.
"""
HPATH = "instance-stop"
HTYPE = constants.HTYPE_INSTANCE
- _OP_PARAMS = [
- _PInstanceName,
- _PIgnoreOfflineNodes,
- ("timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT, ht.TPositiveInt),
- ]
REQ_BGL = False
def ExpandNames(self):
_ShutdownInstanceDisks(self, instance)
-class LUReinstallInstance(LogicalUnit):
+class LUInstanceReinstall(LogicalUnit):
"""Reinstall an instance.
"""
HPATH = "instance-reinstall"
HTYPE = constants.HTYPE_INSTANCE
- _OP_PARAMS = [
- _PInstanceName,
- ("os_type", None, ht.TMaybeString),
- ("force_variant", False, ht.TBool),
- ("osparams", None, ht.TOr(ht.TDict, ht.TNone)),
- ]
REQ_BGL = False
def ExpandNames(self):
_ShutdownInstanceDisks(self, inst)
-class LURecreateInstanceDisks(LogicalUnit):
+class LUInstanceRecreateDisks(LogicalUnit):
"""Recreate an instance's missing disks.
"""
HPATH = "instance-recreate-disks"
HTYPE = constants.HTYPE_INSTANCE
- _OP_PARAMS = [
- _PInstanceName,
- ("disks", ht.EmptyList, ht.TListOf(ht.TPositiveInt)),
- ]
REQ_BGL = False
def ExpandNames(self):
_CreateDisks(self, self.instance, to_skip=to_skip)
-class LURenameInstance(LogicalUnit):
+class LUInstanceRename(LogicalUnit):
"""Rename an instance.
"""
HPATH = "instance-rename"
HTYPE = constants.HTYPE_INSTANCE
- _OP_PARAMS = [
- _PInstanceName,
- ("new_name", ht.NoDefault, ht.TNonEmptyString),
- ("ip_check", False, ht.TBool),
- ("name_check", True, ht.TBool),
- ]
def CheckArguments(self):
"""Check arguments.
new_name = self.op.new_name
if self.op.name_check:
hostname = netutils.GetHostname(name=new_name)
+ self.LogInfo("Resolved given name '%s' to '%s'", new_name,
+ hostname.name)
new_name = self.op.new_name = hostname.name
if (self.op.ip_check and
netutils.TcpPing(hostname.ip, constants.DEFAULT_NODED_PORT)):
new_name, errors.ECODE_EXISTS)
def Exec(self, feedback_fn):
- """Reinstall the instance.
+ """Rename the instance.
"""
inst = self.instance
return inst.name
-class LURemoveInstance(LogicalUnit):
+class LUInstanceRemove(LogicalUnit):
"""Remove an instance.
"""
HPATH = "instance-remove"
HTYPE = constants.HTYPE_INSTANCE
- _OP_PARAMS = [
- _PInstanceName,
- ("ignore_failures", False, ht.TBool),
- _PShutdownTimeout,
- ]
REQ_BGL = False
def ExpandNames(self):
lu.remove_locks[locking.LEVEL_INSTANCE] = instance.name
-class LUQueryInstances(NoHooksLU):
+class LUInstanceQuery(NoHooksLU):
"""Logical unit for querying instances.
"""
# pylint: disable-msg=W0142
- _OP_PARAMS = [
- _POutputFields,
- ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
- ("use_locking", False, ht.TBool),
- ]
REQ_BGL = False
def CheckArguments(self):
return self.iq.OldStyleQuery(self)
-class LUFailoverInstance(LogicalUnit):
+class LUInstanceFailover(LogicalUnit):
"""Failover an instance.
"""
HPATH = "instance-failover"
HTYPE = constants.HTYPE_INSTANCE
- _OP_PARAMS = [
- _PInstanceName,
- ("ignore_consistency", False, ht.TBool),
- _PShutdownTimeout,
- ]
REQ_BGL = False
def ExpandNames(self):
(instance.name, target_node, msg))
-class LUMigrateInstance(LogicalUnit):
+class LUInstanceMigrate(LogicalUnit):
"""Migrate an instance.
This is migration without shutting down, compared to the failover,
"""
HPATH = "instance-migrate"
HTYPE = constants.HTYPE_INSTANCE
- _OP_PARAMS = [
- _PInstanceName,
- _PMigrationMode,
- _PMigrationLive,
- ("cleanup", False, ht.TBool),
- ]
-
REQ_BGL = False
def ExpandNames(self):
return env, nl, nl_post
-class LUMoveInstance(LogicalUnit):
+class LUInstanceMove(LogicalUnit):
"""Move an instance by data-copying.
"""
HPATH = "instance-move"
HTYPE = constants.HTYPE_INSTANCE
- _OP_PARAMS = [
- _PInstanceName,
- ("target_node", ht.NoDefault, ht.TNonEmptyString),
- _PShutdownTimeout,
- ]
REQ_BGL = False
def ExpandNames(self):
for idx, disk in enumerate(instance.disks):
self.LogInfo("Copying data for disk %d", idx)
result = self.rpc.call_blockdev_assemble(target_node, disk,
- instance.name, True)
+ instance.name, True, idx)
if result.fail_msg:
self.LogWarning("Can't assemble newly created disk %d: %s",
idx, result.fail_msg)
(instance.name, target_node, msg))
-class LUMigrateNode(LogicalUnit):
+class LUNodeMigrate(LogicalUnit):
"""Migrate all instances from a node.
"""
HPATH = "node-migrate"
HTYPE = constants.HTYPE_NODE
- _OP_PARAMS = [
- _PNodeName,
- _PMigrationMode,
- _PMigrationLive,
- ]
REQ_BGL = False
def ExpandNames(self):
if len(secondary_nodes) != 0:
raise errors.ProgrammerError("Wrong template configuration")
- _RequireFileStorage()
+ opcodes.RequireFileStorage()
for idx, disk in enumerate(disk_info):
disk_index = idx + base_index
"""
node = instance.primary_node
- for idx, device in enumerate(instance.disks):
- lu.LogInfo("* Wiping disk %d", idx)
- logging.info("Wiping disk %d for instance %s", idx, instance.name)
-
- # The wipe size is MIN_WIPE_CHUNK_PERCENT % of the instance disk but
- # MAX_WIPE_CHUNK at max
- wipe_chunk_size = min(constants.MAX_WIPE_CHUNK, device.size / 100.0 *
- constants.MIN_WIPE_CHUNK_PERCENT)
-
- offset = 0
- size = device.size
- last_output = 0
- start_time = time.time()
-
- while offset < size:
- wipe_size = min(wipe_chunk_size, size - offset)
- result = lu.rpc.call_blockdev_wipe(node, device, offset, wipe_size)
- result.Raise("Could not wipe disk %d at offset %d for size %d" %
- (idx, offset, wipe_size))
- now = time.time()
- offset += wipe_size
- if now - last_output >= 60:
- eta = _CalcEta(now - start_time, offset, size)
- lu.LogInfo(" - done: %.1f%% ETA: %s" %
- (offset / float(size) * 100, utils.FormatSeconds(eta)))
- last_output = now
+ logging.info("Pause sync of instance %s disks", instance.name)
+ result = lu.rpc.call_blockdev_pause_resume_sync(node, instance.disks, True)
+
+ for idx, success in enumerate(result.payload):
+ if not success:
+ logging.warn("pause-sync of instance %s for disks %d failed",
+ instance.name, idx)
+
+ try:
+ for idx, device in enumerate(instance.disks):
+ lu.LogInfo("* Wiping disk %d", idx)
+ logging.info("Wiping disk %d for instance %s", idx, instance.name)
+
+ # The wipe size is MIN_WIPE_CHUNK_PERCENT % of the instance disk but
+ # MAX_WIPE_CHUNK at max
+ wipe_chunk_size = min(constants.MAX_WIPE_CHUNK, device.size / 100.0 *
+ constants.MIN_WIPE_CHUNK_PERCENT)
+
+ offset = 0
+ size = device.size
+ last_output = 0
+ start_time = time.time()
+
+ while offset < size:
+ wipe_size = min(wipe_chunk_size, size - offset)
+ result = lu.rpc.call_blockdev_wipe(node, device, offset, wipe_size)
+ result.Raise("Could not wipe disk %d at offset %d for size %d" %
+ (idx, offset, wipe_size))
+ now = time.time()
+ offset += wipe_size
+ if now - last_output >= 60:
+ eta = _CalcEta(now - start_time, offset, size)
+ lu.LogInfo(" - done: %.1f%% ETA: %s" %
+ (offset / float(size) * 100, utils.FormatSeconds(eta)))
+ last_output = now
+ finally:
+ logging.info("Resume sync of instance %s disks", instance.name)
+
+ result = lu.rpc.call_blockdev_pause_resume_sync(node, instance.disks, False)
+
+ for idx, success in enumerate(result.payload):
+ if not success:
+ lu.LogWarning("Warning: Resume sync of disk %d failed. Please have a"
+ " look at the status and troubleshoot the issue.", idx)
+ logging.warn("resume-sync of instance %s for disks %d failed",
+ instance.name, idx)
def _CreateDisks(lu, instance, to_skip=None, target_node=None):
" node %s" % (file_storage_dir, pnode))
# Note: this needs to be kept in sync with adding of disks in
- # LUSetInstanceParams
+ # LUInstanceSetParams
for idx, device in enumerate(instance.disks):
if to_skip and idx in to_skip:
continue
# Required free disk space as a function of disk and swap space
req_size_dict = {
- constants.DT_DISKLESS: None,
+ constants.DT_DISKLESS: {},
constants.DT_PLAIN: _compute(disks, 0),
# 128 MB are added for drbd metadata for each disk
constants.DT_DRBD8: _compute(disks, 128),
- constants.DT_FILE: None,
+ constants.DT_FILE: {},
}
if disk_template not in req_size_dict:
osname, node)
-class LUCreateInstance(LogicalUnit):
+class LUInstanceCreate(LogicalUnit):
"""Create an instance.
"""
HPATH = "instance-add"
HTYPE = constants.HTYPE_INSTANCE
- _OP_PARAMS = [
- _PInstanceName,
- ("mode", ht.NoDefault, ht.TElemOf(constants.INSTANCE_CREATE_MODES)),
- ("start", True, ht.TBool),
- ("wait_for_sync", True, ht.TBool),
- ("ip_check", True, ht.TBool),
- ("name_check", True, ht.TBool),
- ("disks", ht.NoDefault, ht.TListOf(ht.TDict)),
- ("nics", ht.NoDefault, ht.TListOf(ht.TDict)),
- ("hvparams", ht.EmptyDict, ht.TDict),
- ("beparams", ht.EmptyDict, ht.TDict),
- ("osparams", ht.EmptyDict, ht.TDict),
- ("no_install", None, ht.TMaybeBool),
- ("os_type", None, ht.TMaybeString),
- ("force_variant", False, ht.TBool),
- ("source_handshake", None, ht.TOr(ht.TList, ht.TNone)),
- ("source_x509_ca", None, ht.TMaybeString),
- ("source_instance_name", None, ht.TMaybeString),
- ("source_shutdown_timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT,
- ht.TPositiveInt),
- ("src_node", None, ht.TMaybeString),
- ("src_path", None, ht.TMaybeString),
- ("pnode", None, ht.TMaybeString),
- ("snode", None, ht.TMaybeString),
- ("iallocator", None, ht.TMaybeString),
- ("hypervisor", None, ht.TMaybeString),
- ("disk_template", ht.NoDefault, _CheckDiskTemplate),
- ("identify_defaults", False, ht.TBool),
- ("file_driver", None, ht.TOr(ht.TNone, ht.TElemOf(constants.FILE_DRIVER))),
- ("file_storage_dir", None, ht.TMaybeString),
- ]
REQ_BGL = False
def CheckArguments(self):
export_info = self._ReadExportInfo()
self._ReadExportParams(export_info)
- _CheckDiskTemplate(self.op.disk_template)
-
if (not self.cfg.GetVGName() and
self.op.disk_template not in constants.DTS_NOT_LVM):
raise errors.OpPrereqError("Cluster does not support lvm-based"
raise errors.OpPrereqError("LV named %s used by another instance" %
lv_name, errors.ECODE_NOTUNIQUE)
- vg_names = self.rpc.call_vg_list([pnode.name])
+ vg_names = self.rpc.call_vg_list([pnode.name])[pnode.name]
vg_names.Raise("Cannot get VG information from node %s" % pnode.name)
node_lvs = self.rpc.call_lv_list([pnode.name],
- vg_names[pnode.name].payload.keys()
- )[pnode.name]
+ vg_names.payload.keys())[pnode.name]
node_lvs.Raise("Cannot get LV information from node %s" % pnode.name)
node_lvs = node_lvs.payload
return list(iobj.all_nodes)
-class LUConnectConsole(NoHooksLU):
+class LUInstanceConsole(NoHooksLU):
"""Connect to an instance's console.
This is somewhat special in that it returns the command line that
console.
"""
- _OP_PARAMS = [
- _PInstanceName
- ]
REQ_BGL = False
def ExpandNames(self):
logging.debug("Connecting to console of %s on %s", instance.name, node)
- hyper = hypervisor.GetHypervisor(instance.hypervisor)
- cluster = self.cfg.GetClusterInfo()
- # beparams and hvparams are passed separately, to avoid editing the
- # instance and then saving the defaults in the instance itself.
- hvparams = cluster.FillHV(instance)
- beparams = cluster.FillBE(instance)
- console_cmd = hyper.GetShellCommandForConsole(instance, hvparams, beparams)
+ return _GetInstanceConsole(self.cfg.GetClusterInfo(), instance)
+
+
+def _GetInstanceConsole(cluster, instance):
+ """Returns console information for an instance.
+
+ @type cluster: L{objects.Cluster}
+ @type instance: L{objects.Instance}
+ @rtype: dict
+
+ """
+ hyper = hypervisor.GetHypervisor(instance.hypervisor)
+ # beparams and hvparams are passed separately, to avoid editing the
+ # instance and then saving the defaults in the instance itself.
+ hvparams = cluster.FillHV(instance)
+ beparams = cluster.FillBE(instance)
+ console = hyper.GetInstanceConsole(instance, hvparams, beparams)
+
+ assert console.instance == instance.name
+ assert console.Validate()
- # build ssh cmdline
- return self.ssh.BuildCmd(node, "root", console_cmd, batch=True, tty=True)
+ return console.ToDict()
-class LUReplaceDisks(LogicalUnit):
+class LUInstanceReplaceDisks(LogicalUnit):
"""Replace the disks of an instance.
"""
HPATH = "mirrors-replace"
HTYPE = constants.HTYPE_INSTANCE
- _OP_PARAMS = [
- _PInstanceName,
- ("mode", ht.NoDefault, ht.TElemOf(constants.REPLACE_MODES)),
- ("disks", ht.EmptyList, ht.TListOf(ht.TPositiveInt)),
- ("remote_node", None, ht.TMaybeString),
- ("iallocator", None, ht.TMaybeString),
- ("early_release", False, ht.TBool),
- ]
REQ_BGL = False
def CheckArguments(self):
"""Repairs the volume group on a node.
"""
- _OP_PARAMS = [
- _PNodeName,
- ("storage_type", ht.NoDefault, _CheckStorageType),
- ("name", ht.NoDefault, ht.TNonEmptyString),
- ("ignore_consistency", False, ht.TBool),
- ]
REQ_BGL = False
def CheckArguments(self):
(self.op.name, self.op.node_name))
-class LUNodeEvacuationStrategy(NoHooksLU):
+class LUNodeEvacStrategy(NoHooksLU):
"""Computes the node evacuation strategy.
"""
- _OP_PARAMS = [
- ("nodes", ht.NoDefault, ht.TListOf(ht.TNonEmptyString)),
- ("remote_node", None, ht.TMaybeString),
- ("iallocator", None, ht.TMaybeString),
- ]
REQ_BGL = False
def CheckArguments(self):
return result
-class LUGrowDisk(LogicalUnit):
+class LUInstanceGrowDisk(LogicalUnit):
"""Grow a disk of an instance.
"""
HPATH = "disk-grow"
HTYPE = constants.HTYPE_INSTANCE
- _OP_PARAMS = [
- _PInstanceName,
- ("disk", ht.NoDefault, ht.TInt),
- ("amount", ht.NoDefault, ht.TInt),
- ("wait_for_sync", True, ht.TBool),
- ]
REQ_BGL = False
def ExpandNames(self):
# TODO: check the free disk space for file, when that feature
# will be supported
_CheckNodesFreeDiskPerVG(self, nodenames,
- {self.disk.physical_id[0]: self.op.amount})
+ self.disk.ComputeGrowth(self.op.amount))
def Exec(self, feedback_fn):
"""Execute disk grow.
" sync mode was requested.")
-class LUQueryInstanceData(NoHooksLU):
+class LUInstanceQueryData(NoHooksLU):
"""Query runtime instance data.
"""
- _OP_PARAMS = [
- ("instances", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
- ("static", False, ht.TBool),
- ]
REQ_BGL = False
def ExpandNames(self):
return result
-class LUSetInstanceParams(LogicalUnit):
+class LUInstanceSetParams(LogicalUnit):
"""Modifies an instances's parameters.
"""
HPATH = "instance-modify"
HTYPE = constants.HTYPE_INSTANCE
- _OP_PARAMS = [
- _PInstanceName,
- ("nics", ht.EmptyList, ht.TList),
- ("disks", ht.EmptyList, ht.TList),
- ("beparams", ht.EmptyDict, ht.TDict),
- ("hvparams", ht.EmptyDict, ht.TDict),
- ("disk_template", None, ht.TMaybeString),
- ("remote_node", None, ht.TMaybeString),
- ("os_name", None, ht.TMaybeString),
- ("force_variant", False, ht.TBool),
- ("osparams", None, ht.TOr(ht.TDict, ht.TNone)),
- _PForce,
- ]
REQ_BGL = False
def CheckArguments(self):
" changes not supported at the same time",
errors.ECODE_INVAL)
- if self.op.disk_template:
- _CheckDiskTemplate(self.op.disk_template)
- if (self.op.disk_template in constants.DTS_NET_MIRROR and
- self.op.remote_node is None):
- raise errors.OpPrereqError("Changing the disk template to a mirrored"
- " one requires specifying a secondary node",
- errors.ECODE_INVAL)
+ if (self.op.disk_template and
+ self.op.disk_template in constants.DTS_NET_MIRROR and
+ self.op.remote_node is None):
+ raise errors.OpPrereqError("Changing the disk template to a mirrored"
+ " one requires specifying a secondary node",
+ errors.ECODE_INVAL)
# NIC validation
nic_addremove = 0
_CheckInstanceDown(self, instance, "cannot remove disks")
if (disk_op == constants.DDM_ADD and
- len(instance.nics) >= constants.MAX_DISKS):
+ len(instance.disks) >= constants.MAX_DISKS):
raise errors.OpPrereqError("Instance has too many disks (%d), cannot"
" add more" % constants.MAX_DISKS,
errors.ECODE_STATE)
if self.op.disk_template:
r_shut = _ShutdownInstanceDisks(self, instance)
if not r_shut:
- raise errors.OpExecError("Cannot shutdow instance disks, unable to"
+ raise errors.OpExecError("Cannot shutdown instance disks, unable to"
" proceed with disk template conversion")
mode = (instance.disk_template, self.op.disk_template)
try:
}
-class LUQueryExports(NoHooksLU):
+class LUBackupQuery(NoHooksLU):
"""Query the exports list
"""
- _OP_PARAMS = [
- ("nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
- ("use_locking", False, ht.TBool),
- ]
REQ_BGL = False
def ExpandNames(self):
return result
-class LUPrepareExport(NoHooksLU):
+class LUBackupPrepare(NoHooksLU):
"""Prepares an instance for an export and returns useful information.
"""
- _OP_PARAMS = [
- _PInstanceName,
- ("mode", ht.NoDefault, ht.TElemOf(constants.EXPORT_MODES)),
- ]
REQ_BGL = False
def ExpandNames(self):
return None
-class LUExportInstance(LogicalUnit):
+class LUBackupExport(LogicalUnit):
"""Export an instance to an image in the cluster.
"""
HPATH = "instance-export"
HTYPE = constants.HTYPE_INSTANCE
- _OP_PARAMS = [
- _PInstanceName,
- ("target_node", ht.NoDefault, ht.TOr(ht.TNonEmptyString, ht.TList)),
- ("shutdown", True, ht.TBool),
- _PShutdownTimeout,
- ("remove_instance", False, ht.TBool),
- ("ignore_remove_failures", False, ht.TBool),
- ("mode", constants.EXPORT_MODE_LOCAL, ht.TElemOf(constants.EXPORT_MODES)),
- ("x509_key_name", None, ht.TOr(ht.TList, ht.TNone)),
- ("destination_x509_ca", None, ht.TMaybeString),
- ]
REQ_BGL = False
def CheckArguments(self):
nodelist.remove(self.dst_node.name)
# on one-node clusters nodelist will be empty after the removal
- # if we proceed the backup would be removed because OpQueryExports
+ # if we proceed the backup would be removed because OpBackupQuery
# substitutes an empty list with the full cluster node list.
iname = self.instance.name
if nodelist:
return fin_resu, dresults
-class LURemoveExport(NoHooksLU):
+class LUBackupRemove(NoHooksLU):
"""Remove exports related to the named instance.
"""
- _OP_PARAMS = [
- _PInstanceName,
- ]
REQ_BGL = False
def ExpandNames(self):
" Domain Name.")
-class LUAddGroup(LogicalUnit):
+class LUGroupAdd(LogicalUnit):
"""Logical unit for creating node groups.
"""
HPATH = "group-add"
HTYPE = constants.HTYPE_GROUP
-
- _OP_PARAMS = [
- _PGroupName,
- ]
-
REQ_BGL = False
def ExpandNames(self):
(self.op.group_name, existing_uuid),
errors.ECODE_EXISTS)
+ if self.op.ndparams:
+ utils.ForceDictType(self.op.ndparams, constants.NDS_PARAMETER_TYPES)
+
def BuildHooksEnv(self):
"""Build hooks env.
"""
group_obj = objects.NodeGroup(name=self.op.group_name, members=[],
- uuid=self.group_uuid)
+ uuid=self.group_uuid,
+ alloc_policy=self.op.alloc_policy,
+ ndparams=self.op.ndparams)
self.cfg.AddNodeGroup(group_obj, self.proc.GetECId(), check_uuid=False)
del self.remove_locks[locking.LEVEL_NODEGROUP]
-class LUQueryGroups(NoHooksLU):
- """Logical unit for querying node groups.
+class LUGroupAssignNodes(NoHooksLU):
+ """Logical unit for assigning nodes to groups.
"""
- # pylint: disable-msg=W0142
- _OP_PARAMS = [
- _POutputFields,
- ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
- ]
-
REQ_BGL = False
- _FIELDS_DYNAMIC = utils.FieldSet()
+ def ExpandNames(self):
+ # These raise errors.OpPrereqError on their own:
+ self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
+ self.op.nodes = _GetWantedNodes(self, self.op.nodes)
- _SIMPLE_FIELDS = ["name", "uuid", "ctime", "mtime", "serial_no"]
+ # We want to lock all the affected nodes and groups. We have readily
+ # available the list of nodes, and the *destination* group. To gather the
+ # list of "source" groups, we need to fetch node information.
+ self.node_data = self.cfg.GetAllNodesInfo()
+ affected_groups = set(self.node_data[node].group for node in self.op.nodes)
+ affected_groups.add(self.group_uuid)
- _FIELDS_STATIC = utils.FieldSet(
- "node_cnt", "node_list", "pinst_cnt", "pinst_list", *_SIMPLE_FIELDS)
+ self.needed_locks = {
+ locking.LEVEL_NODEGROUP: list(affected_groups),
+ locking.LEVEL_NODE: self.op.nodes,
+ }
- def CheckArguments(self):
- _CheckOutputFields(static=self._FIELDS_STATIC,
- dynamic=self._FIELDS_DYNAMIC,
- selected=self.op.output_fields)
+ def CheckPrereq(self):
+ """Check prerequisites.
- def ExpandNames(self):
- self.needed_locks = {}
+ """
+ self.group = self.cfg.GetNodeGroup(self.group_uuid)
+ instance_data = self.cfg.GetAllInstancesInfo()
+
+ if self.group is None:
+ raise errors.OpExecError("Could not retrieve group '%s' (UUID: %s)" %
+ (self.op.group_name, self.group_uuid))
+
+ (new_splits, previous_splits) = \
+ self.CheckAssignmentForSplitInstances([(node, self.group_uuid)
+ for node in self.op.nodes],
+ self.node_data, instance_data)
+
+ if new_splits:
+ fmt_new_splits = utils.CommaJoin(utils.NiceSort(new_splits))
+
+ if not self.op.force:
+ raise errors.OpExecError("The following instances get split by this"
+ " change and --force was not given: %s" %
+ fmt_new_splits)
+ else:
+ self.LogWarning("This operation will split the following instances: %s",
+ fmt_new_splits)
+
+ if previous_splits:
+ self.LogWarning("In addition, these already-split instances continue"
+ " to be spit across groups: %s",
+ utils.CommaJoin(utils.NiceSort(previous_splits)))
def Exec(self, feedback_fn):
- """Computes the list of groups and their attributes.
+ """Assign nodes to a new group.
"""
- all_groups = self.cfg.GetAllNodeGroupsInfo()
+ for node in self.op.nodes:
+ self.node_data[node].group = self.group_uuid
+
+ self.cfg.Update(self.group, feedback_fn) # Saves all modified nodes.
+
+ @staticmethod
+ def CheckAssignmentForSplitInstances(changes, node_data, instance_data):
+ """Check for split instances after a node assignment.
+
+ This method considers a series of node assignments as an atomic operation,
+ and returns information about split instances after applying the set of
+ changes.
+
+ In particular, it returns information about newly split instances, and
+ instances that were already split, and remain so after the change.
+
+ Only instances whose disk template is listed in constants.DTS_NET_MIRROR are
+ considered.
+
+ @type changes: list of (node_name, new_group_uuid) pairs.
+ @param changes: list of node assignments to consider.
+ @param node_data: a dict with data for all nodes
+ @param instance_data: a dict with all instances to consider
+ @rtype: a two-tuple
+ @return: a list of instances that were previously okay and result split as a
+ consequence of this change, and a list of instances that were previously
+ split and this change does not fix.
+
+ """
+ changed_nodes = dict((node, group) for node, group in changes
+ if node_data[node].group != group)
+
+ all_split_instances = set()
+ previously_split_instances = set()
+
+ def InstanceNodes(instance):
+ return [instance.primary_node] + list(instance.secondary_nodes)
- if not self.op.names:
- my_groups = utils.NiceSort(all_groups.keys())
+ for inst in instance_data.values():
+ if inst.disk_template not in constants.DTS_NET_MIRROR:
+ continue
+
+ instance_nodes = InstanceNodes(inst)
+
+ if len(set(node_data[node].group for node in instance_nodes)) > 1:
+ previously_split_instances.add(inst.name)
+
+ if len(set(changed_nodes.get(node, node_data[node].group)
+ for node in instance_nodes)) > 1:
+ all_split_instances.add(inst.name)
+
+ return (list(all_split_instances - previously_split_instances),
+ list(previously_split_instances & all_split_instances))
+
+
+class _GroupQuery(_QueryBase):
+
+ FIELDS = query.GROUP_FIELDS
+
+ def ExpandNames(self, lu):
+ lu.needed_locks = {}
+
+ self._all_groups = lu.cfg.GetAllNodeGroupsInfo()
+ name_to_uuid = dict((g.name, g.uuid) for g in self._all_groups.values())
+
+ if not self.names:
+ self.wanted = [name_to_uuid[name]
+ for name in utils.NiceSort(name_to_uuid.keys())]
else:
# Accept names to be either names or UUIDs.
- all_uuid = frozenset(all_groups.keys())
- name_to_uuid = dict((g.name, g.uuid) for g in all_groups.values())
- my_groups = []
missing = []
+ self.wanted = []
+ all_uuid = frozenset(self._all_groups.keys())
- for name in self.op.names:
+ for name in self.names:
if name in all_uuid:
- my_groups.append(name)
+ self.wanted.append(name)
elif name in name_to_uuid:
- my_groups.append(name_to_uuid[name])
+ self.wanted.append(name_to_uuid[name])
else:
missing.append(name)
raise errors.OpPrereqError("Some groups do not exist: %s" % missing,
errors.ECODE_NOENT)
- do_nodes = bool(frozenset(["node_cnt", "node_list"]).
- intersection(self.op.output_fields))
+ def DeclareLocks(self, lu, level):
+ pass
+
+ def _GetQueryData(self, lu):
+ """Computes the list of node groups and their attributes.
- do_instances = bool(frozenset(["pinst_cnt", "pinst_list"]).
- intersection(self.op.output_fields))
+ """
+ do_nodes = query.GQ_NODE in self.requested_data
+ do_instances = query.GQ_INST in self.requested_data
- # We need to map group->[nodes], and group->[instances]. The former is
- # directly attainable, but the latter we have to do through instance->node,
- # hence we need to process nodes even if we only need instance information.
+ group_to_nodes = None
+ group_to_instances = None
+
+ # For GQ_NODE, we need to map group->[nodes], and group->[instances] for
+ # GQ_INST. The former is attainable with just GetAllNodesInfo(), but for the
+ # latter GetAllInstancesInfo() is not enough, for we have to go through
+ # instance->node. Hence, we will need to process nodes even if we only need
+ # instance information.
if do_nodes or do_instances:
- all_nodes = self.cfg.GetAllNodesInfo()
- group_to_nodes = dict((all_groups[name].uuid, []) for name in my_groups)
+ all_nodes = lu.cfg.GetAllNodesInfo()
+ group_to_nodes = dict((uuid, []) for uuid in self.wanted)
node_to_group = {}
for node in all_nodes.values():
node_to_group[node.name] = node.group
if do_instances:
- all_instances = self.cfg.GetAllInstancesInfo()
- group_to_instances = dict((all_groups[name].uuid, [])
- for name in my_groups)
+ all_instances = lu.cfg.GetAllInstancesInfo()
+ group_to_instances = dict((uuid, []) for uuid in self.wanted)
+
for instance in all_instances.values():
node = instance.primary_node
if node in node_to_group:
group_to_instances[node_to_group[node]].append(instance.name)
- output = []
+ if not do_nodes:
+ # Do not pass on node information if it was not requested.
+ group_to_nodes = None
- for name in my_groups:
- group = all_groups[name]
- group_output = []
+ return query.GroupQueryData([self._all_groups[uuid]
+ for uuid in self.wanted],
+ group_to_nodes, group_to_instances)
- for field in self.op.output_fields:
- if field in self._SIMPLE_FIELDS:
- val = getattr(group, field)
- elif field == "node_list":
- val = utils.NiceSort(group_to_nodes[group.uuid])
- elif field == "node_cnt":
- val = len(group_to_nodes[group.uuid])
- elif field == "pinst_list":
- val = utils.NiceSort(group_to_instances[group.uuid])
- elif field == "pinst_cnt":
- val = len(group_to_instances[group.uuid])
- else:
- raise errors.ParameterError(field)
- group_output.append(val)
- output.append(group_output)
- return output
+class LUGroupQuery(NoHooksLU):
+ """Logical unit for querying node groups.
+ """
+ REQ_BGL = False
-class LURemoveGroup(LogicalUnit):
- HPATH = "group-remove"
+ def CheckArguments(self):
+ self.gq = _GroupQuery(self.op.names, self.op.output_fields, False)
+
+ def ExpandNames(self):
+ self.gq.ExpandNames(self)
+
+ def Exec(self, feedback_fn):
+ return self.gq.OldStyleQuery(self)
+
+
+class LUGroupSetParams(LogicalUnit):
+ """Modifies the parameters of a node group.
+
+ """
+ HPATH = "group-modify"
HTYPE = constants.HTYPE_GROUP
+ REQ_BGL = False
- _OP_PARAMS = [
- _PGroupName,
- ]
+ def CheckArguments(self):
+ all_changes = [
+ self.op.ndparams,
+ self.op.alloc_policy,
+ ]
+
+ if all_changes.count(None) == len(all_changes):
+ raise errors.OpPrereqError("Please pass at least one modification",
+ errors.ECODE_INVAL)
+ def ExpandNames(self):
+ # This raises errors.OpPrereqError on its own:
+ self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
+
+ self.needed_locks = {
+ locking.LEVEL_NODEGROUP: [self.group_uuid],
+ }
+
+ def CheckPrereq(self):
+ """Check prerequisites.
+
+ """
+ self.group = self.cfg.GetNodeGroup(self.group_uuid)
+
+ if self.group is None:
+ raise errors.OpExecError("Could not retrieve group '%s' (UUID: %s)" %
+ (self.op.group_name, self.group_uuid))
+
+ if self.op.ndparams:
+ new_ndparams = _GetUpdatedParams(self.group.ndparams, self.op.ndparams)
+ utils.ForceDictType(self.op.ndparams, constants.NDS_PARAMETER_TYPES)
+ self.new_ndparams = new_ndparams
+
+ def BuildHooksEnv(self):
+ """Build hooks env.
+
+ """
+ env = {
+ "GROUP_NAME": self.op.group_name,
+ "NEW_ALLOC_POLICY": self.op.alloc_policy,
+ }
+ mn = self.cfg.GetMasterNode()
+ return env, [mn], [mn]
+
+ def Exec(self, feedback_fn):
+ """Modifies the node group.
+
+ """
+ result = []
+
+ if self.op.ndparams:
+ self.group.ndparams = self.new_ndparams
+ result.append(("ndparams", str(self.group.ndparams)))
+
+ if self.op.alloc_policy:
+ self.group.alloc_policy = self.op.alloc_policy
+
+ self.cfg.Update(self.group, feedback_fn)
+ return result
+
+
+
+class LUGroupRemove(LogicalUnit):
+ HPATH = "group-remove"
+ HTYPE = constants.HTYPE_GROUP
REQ_BGL = False
def ExpandNames(self):
# Verify the cluster would not be left group-less.
if len(self.cfg.GetNodeGroupList()) == 1:
- raise errors.OpPrereqError("Group '%s' is the last group in the cluster,"
- " which cannot be left without at least one"
- " group" % self.op.group_name,
+ raise errors.OpPrereqError("Group '%s' is the only group,"
+ " cannot be removed" %
+ self.op.group_name,
errors.ECODE_STATE)
def BuildHooksEnv(self):
self.remove_locks[locking.LEVEL_NODEGROUP] = self.group_uuid
-class LURenameGroup(LogicalUnit):
+class LUGroupRename(LogicalUnit):
HPATH = "group-rename"
HTYPE = constants.HTYPE_GROUP
-
- _OP_PARAMS = [
- ("old_name", ht.NoDefault, ht.TNonEmptyString),
- ("new_name", ht.NoDefault, ht.TNonEmptyString),
- ]
-
REQ_BGL = False
def ExpandNames(self):
str(self.op.kind), errors.ECODE_INVAL)
-class LUGetTags(TagsLU):
+class LUTagsGet(TagsLU):
"""Returns the tags of a given object.
"""
- _OP_PARAMS = [
- ("kind", ht.NoDefault, ht.TElemOf(constants.VALID_TAG_TYPES)),
- # Name is only meaningful for nodes and instances
- ("name", ht.NoDefault, ht.TMaybeString),
- ]
REQ_BGL = False
def ExpandNames(self):
return list(self.target.GetTags())
-class LUSearchTags(NoHooksLU):
+class LUTagsSearch(NoHooksLU):
"""Searches the tags for a given pattern.
"""
- _OP_PARAMS = [
- ("pattern", ht.NoDefault, ht.TNonEmptyString),
- ]
REQ_BGL = False
def ExpandNames(self):
return results
-class LUAddTags(TagsLU):
+class LUTagsSet(TagsLU):
"""Sets a tag on a given object.
"""
- _OP_PARAMS = [
- ("kind", ht.NoDefault, ht.TElemOf(constants.VALID_TAG_TYPES)),
- # Name is only meaningful for nodes and instances
- ("name", ht.NoDefault, ht.TMaybeString),
- ("tags", ht.NoDefault, ht.TListOf(ht.TNonEmptyString)),
- ]
REQ_BGL = False
def CheckPrereq(self):
self.cfg.Update(self.target, feedback_fn)
-class LUDelTags(TagsLU):
+class LUTagsDel(TagsLU):
"""Delete a list of tags from a given object.
"""
- _OP_PARAMS = [
- ("kind", ht.NoDefault, ht.TElemOf(constants.VALID_TAG_TYPES)),
- # Name is only meaningful for nodes and instances
- ("name", ht.NoDefault, ht.TMaybeString),
- ("tags", ht.NoDefault, ht.TListOf(ht.TNonEmptyString)),
- ]
REQ_BGL = False
def CheckPrereq(self):
time.
"""
- _OP_PARAMS = [
- ("duration", ht.NoDefault, ht.TFloat),
- ("on_master", True, ht.TBool),
- ("on_nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
- ("repeat", 0, ht.TPositiveInt)
- ]
REQ_BGL = False
def ExpandNames(self):
self._TestDelay()
-class LUTestJobqueue(NoHooksLU):
+class LUTestJqueue(NoHooksLU):
"""Utility LU to test some aspects of the job queue.
"""
- _OP_PARAMS = [
- ("notify_waitlock", False, ht.TBool),
- ("notify_exec", False, ht.TBool),
- ("log_messages", ht.EmptyList, ht.TListOf(ht.TString)),
- ("fail", False, ht.TBool),
- ]
REQ_BGL = False
# Must be lower than default timeout for WaitForJobChange to see whether it
"enabled_hypervisors": list(cluster_info.enabled_hypervisors),
# we don't have job IDs
}
+ ninfo = cfg.GetAllNodesInfo()
iinfo = cfg.GetAllInstancesInfo().values()
i_list = [(inst, cluster_info.FillBE(inst)) for inst in iinfo]
# node data
- node_list = cfg.GetNodeList()
+ node_list = [n.name for n in ninfo.values() if n.vm_capable]
if self.mode == constants.IALLOCATOR_MODE_ALLOC:
hypervisor_name = self.hypervisor
data["nodegroups"] = self._ComputeNodeGroupData(cfg)
- data["nodes"] = self._ComputeNodeData(cfg, node_data, node_iinfo, i_list)
+ config_ndata = self._ComputeBasicNodeData(ninfo)
+ data["nodes"] = self._ComputeDynamicNodeData(ninfo, node_data, node_iinfo,
+ i_list, config_ndata)
+ assert len(data["nodes"]) == len(ninfo), \
+ "Incomplete node data computed"
data["instances"] = self._ComputeInstanceData(cluster_info, i_list)
"""
ng = {}
for guuid, gdata in cfg.GetAllNodeGroupsInfo().items():
- ng[guuid] = { "name": gdata.name }
+ ng[guuid] = {
+ "name": gdata.name,
+ "alloc_policy": gdata.alloc_policy,
+ }
return ng
@staticmethod
- def _ComputeNodeData(cfg, node_data, node_iinfo, i_list):
+ def _ComputeBasicNodeData(node_cfg):
"""Compute global node data.
+ @rtype: dict
+ @returns: a dict of name: (node dict, node config)
+
"""
node_results = {}
- for nname, nresult in node_data.items():
- # first fill in static (config-based) values
- ninfo = cfg.GetNodeInfo(nname)
+ for ninfo in node_cfg.values():
+ # fill in static (config-based) values
pnr = {
"tags": list(ninfo.GetTags()),
"primary_ip": ninfo.primary_ip,
"vm_capable": ninfo.vm_capable,
}
+ node_results[ninfo.name] = pnr
+
+ return node_results
+
+ @staticmethod
+ def _ComputeDynamicNodeData(node_cfg, node_data, node_iinfo, i_list,
+ node_results):
+ """Compute global node data.
+
+ @param node_results: the basic node structures as filled from the config
+
+ """
+ # make a copy of the current dict
+ node_results = dict(node_results)
+ for nname, nresult in node_data.items():
+ assert nname in node_results, "Missing basic data for node %s" % nname
+ ninfo = node_cfg[nname]
+
if not (ninfo.offline or ninfo.drained):
nresult.Raise("Can't get data for node %s" % nname)
node_iinfo[nname].Raise("Can't get node instance info from node %s" %
"i_pri_memory": i_p_mem,
"i_pri_up_memory": i_p_up_mem,
}
- pnr.update(pnr_dyn)
+ pnr_dyn.update(node_results[nname])
- node_results[nname] = pnr
+ node_results[nname] = pnr_dyn
return node_results
This LU runs the allocator tests
"""
- _OP_PARAMS = [
- ("direction", ht.NoDefault,
- ht.TElemOf(constants.VALID_IALLOCATOR_DIRECTIONS)),
- ("mode", ht.NoDefault, ht.TElemOf(constants.VALID_IALLOCATOR_MODES)),
- ("name", ht.NoDefault, ht.TNonEmptyString),
- ("nics", ht.NoDefault, ht.TOr(ht.TNone, ht.TListOf(
- ht.TDictOf(ht.TElemOf(["mac", "ip", "bridge"]),
- ht.TOr(ht.TNone, ht.TNonEmptyString))))),
- ("disks", ht.NoDefault, ht.TOr(ht.TNone, ht.TList)),
- ("hypervisor", None, ht.TMaybeString),
- ("allocator", None, ht.TMaybeString),
- ("tags", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
- ("mem_size", None, ht.TOr(ht.TNone, ht.TPositiveInt)),
- ("vcpus", None, ht.TOr(ht.TNone, ht.TPositiveInt)),
- ("os", None, ht.TMaybeString),
- ("disk_template", None, ht.TMaybeString),
- ("evac_nodes", None, ht.TOr(ht.TNone, ht.TListOf(ht.TNonEmptyString))),
- ]
-
def CheckPrereq(self):
"""Check prerequisites.
ial.Run(self.op.allocator, validate=False)
result = ial.out_text
return result
+
+
+#: Query type implementations
+_QUERY_IMPL = {
+ constants.QR_INSTANCE: _InstanceQuery,
+ constants.QR_NODE: _NodeQuery,
+ constants.QR_GROUP: _GroupQuery,
+ }
+
+
+def _GetQueryImplementation(name):
+ """Returns the implemtnation for a query type.
+
+ @param name: Query type, must be one of L{constants.QR_OP_QUERY}
+
+ """
+ try:
+ return _QUERY_IMPL[name]
+ except KeyError:
+ raise errors.OpPrereqError("Unknown query resource '%s'" % name,
+ errors.ECODE_INVAL)