X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/4a19bdc01429dbf79c4f0ee4b1ad194bce0d4c41..d817d49f030cca5095d7624b5490c40bd3b75e50:/lib/cmdlib.py diff --git a/lib/cmdlib.py b/lib/cmdlib.py index a30ec49..1b0e036 100644 --- a/lib/cmdlib.py +++ b/lib/cmdlib.py @@ -1,7 +1,7 @@ # # -# 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 @@ -39,7 +39,7 @@ import OpenSSL import socket import tempfile import shutil -import operator +import itertools from ganeti import ssh from ganeti import utils @@ -54,43 +54,24 @@ from ganeti import uidpool 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 @@ -110,13 +91,10 @@ class LogicalUnit(object): @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): @@ -155,32 +133,8 @@ class LogicalUnit(object): # 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() @@ -545,15 +499,7 @@ class _QueryBase: @return: List of L{objects.QueryFieldDefinition} """ - if fields is None: - # Client requests all fields, sort by name - fdefs = sorted(query.GetAllFields(cls.FIELDS.values()), - key=operator.attrgetter("name")) - else: - # Keep order as requested by client - 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. @@ -583,10 +529,7 @@ class _QueryBase: """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. @@ -786,42 +729,6 @@ def _CheckNodeHasSecondaryIP(lu, node, secondary_ip, prereq): 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. @@ -965,7 +872,7 @@ def _NICListToTuple(lu, nics): """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 @@ -1182,7 +1089,7 @@ def _CheckIAllocatorOrNode(lu, iallocator_slot, node_slot): " iallocator.") -class LUPostInitCluster(LogicalUnit): +class LUClusterPostInit(LogicalUnit): """Logical unit for running hooks after cluster initialization. """ @@ -1204,7 +1111,7 @@ class LUPostInitCluster(LogicalUnit): return True -class LUDestroyCluster(LogicalUnit): +class LUClusterDestroy(LogicalUnit): """Logical unit for destroying the cluster. """ @@ -1260,7 +1167,7 @@ class LUDestroyCluster(LogicalUnit): def _VerifyCertificate(filename): - """Verifies a certificate for LUVerifyCluster. + """Verifies a certificate for LUClusterVerify. @type filename: string @param filename: Path to PEM file @@ -1270,7 +1177,7 @@ def _VerifyCertificate(filename): 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) = \ @@ -1285,26 +1192,19 @@ def _VerifyCertificate(filename): 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" @@ -1319,6 +1219,7 @@ class LUVerifyCluster(LogicalUnit): EINSTANCEMISSINGDISK = (TINSTANCE, "EINSTANCEMISSINGDISK") EINSTANCEFAULTYDISK = (TINSTANCE, "EINSTANCEFAULTYDISK") EINSTANCEWRONGNODE = (TINSTANCE, "EINSTANCEWRONGNODE") + EINSTANCESPLITGROUPS = (TINSTANCE, "EINSTANCESPLITGROUPS") ENODEDRBD = (TNODE, "ENODEDRBD") ENODEDRBDHELPER = (TNODE, "ENODEDRBDHELPER") ENODEFILECHECK = (TNODE, "ENODEFILECHECK") @@ -1335,6 +1236,7 @@ class LUVerifyCluster(LogicalUnit): ENODEVERSION = (TNODE, "ENODEVERSION") ENODESETUP = (TNODE, "ENODESETUP") ENODETIME = (TNODE, "ENODETIME") + ENODEOOBPATH = (TNODE, "ENODEOOBPATH") ETYPE_FIELD = "code" ETYPE_ERROR = "ERROR" @@ -1721,8 +1623,8 @@ class LUVerifyCluster(LogicalUnit): 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): @@ -1925,6 +1827,22 @@ class LUVerifyCluster(LogicalUnit): "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. @@ -2023,18 +1941,26 @@ class LUVerifyCluster(LogicalUnit): @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: @@ -2063,28 +1989,43 @@ class LUVerifyCluster(LogicalUnit): 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 @@ -2108,6 +2049,7 @@ class LUVerifyCluster(LogicalUnit): """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 @@ -2127,9 +2069,11 @@ class LUVerifyCluster(LogicalUnit): 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 @@ -2184,6 +2128,16 @@ class LUVerifyCluster(LogicalUnit): 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] @@ -2263,6 +2217,8 @@ class LUVerifyCluster(LogicalUnit): 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, @@ -2302,11 +2258,33 @@ class LUVerifyCluster(LogicalUnit): # 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) @@ -2403,7 +2381,7 @@ class LUVerifyCluster(LogicalUnit): return lu_result -class LUVerifyDisks(NoHooksLU): +class LUClusterVerifyDisks(NoHooksLU): """Verifies the cluster disks status. """ @@ -2478,11 +2456,10 @@ class LUVerifyDisks(NoHooksLU): 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): @@ -2594,13 +2571,12 @@ class LURepairDiskSizes(NoHooksLU): 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. @@ -2673,47 +2649,12 @@ class LURenameCluster(LogicalUnit): 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): @@ -3103,7 +3044,7 @@ def _RedistributeAncillaryFiles(lu, additional_nodes=None, additional_vm=True): _UploadHelper(lu, vm_nodes, fname) -class LURedistributeConfig(NoHooksLU): +class LUClusterRedistConf(NoHooksLU): """Force the redistribution of cluster configuration. This is a very simple LU. @@ -3234,15 +3175,10 @@ def _CheckDiskConsistency(lu, dev, node, on_primary, ldisk=False): 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): @@ -3261,21 +3197,25 @@ class LUOutOfBand(NoHooksLU): 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.oob_program = _SupportsOob(self.cfg, node) if not self.oob_program: raise errors.OpPrereqError("OOB is not supported for node %s" % self.op.node_name) - self.op.node_name = node.name + 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") % self.op.node_name) + self.node = node def ExpandNames(self): """Gather locks we need. """ + node_name = _ExpandNodeName(self.cfg, self.op.node_name) self.needed_locks = { - locking.LEVEL_NODE: [self.op.node_name], + locking.LEVEL_NODE: [node_name], } def Exec(self, feedback_fn): @@ -3359,10 +3299,6 @@ class LUDiagnoseOS(NoHooksLU): """Logical unit for OS diagnose/query. """ - _OP_PARAMS = [ - _POutputFields, - ("names", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)), - ] REQ_BGL = False _HID = "hidden" _BLK = "blacklisted" @@ -3499,9 +3435,6 @@ class LURemoveNode(LogicalUnit): """ HPATH = "node-remove" HTYPE = constants.HTYPE_NODE - _OP_PARAMS = [ - _PNodeName, - ] def BuildHooksEnv(self): """Build hooks env. @@ -3648,6 +3581,12 @@ class _NodeQuery(_QueryBase): 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: @@ -3655,7 +3594,8 @@ class _NodeQuery(_QueryBase): 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): @@ -3663,11 +3603,6 @@ class LUQueryNodes(NoHooksLU): """ # 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): @@ -3685,10 +3620,6 @@ class LUQueryNodeVolumes(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") @@ -3768,12 +3699,6 @@ class LUQueryNodeStorage(NoHooksLU): """ _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): @@ -3922,37 +3847,11 @@ class _InstanceQuery(_QueryBase): 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) - - class LUQuery(NoHooksLU): """Query for resources/items of a certain kind. """ # 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): @@ -3976,10 +3875,6 @@ class LUQueryFields(NoHooksLU): """ # 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): @@ -3996,12 +3891,6 @@ class LUModifyNodeStorage(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): @@ -4040,22 +3929,12 @@ class LUModifyNodeStorage(NoHooksLU): (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): @@ -4249,6 +4128,8 @@ class LUAddNode(LogicalUnit): if self.op.ndparams: new_node.ndparams = self.op.ndparams + else: + new_node.ndparams = {} # check connectivity result = self.rpc.call_version([node])[node] @@ -4321,18 +4202,6 @@ class LUSetNodeParams(LogicalUnit): """ 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 = { @@ -4473,6 +4342,18 @@ class LUSetNodeParams(LogicalUnit): # 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)): @@ -4562,6 +4443,9 @@ class LUSetNodeParams(LogicalUnit): 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: @@ -4604,10 +4488,6 @@ class LUPowercycleNode(NoHooksLU): """Powercycles a node. """ - _OP_PARAMS = [ - _PNodeName, - _PForce, - ] REQ_BGL = False def CheckArguments(self): @@ -4636,7 +4516,7 @@ class LUPowercycleNode(NoHooksLU): return result.payload -class LUQueryClusterInfo(NoHooksLU): +class LUClusterQuery(NoHooksLU): """Query cluster configuration. """ @@ -4681,6 +4561,7 @@ class LUQueryClusterInfo(NoHooksLU): "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, @@ -4701,11 +4582,10 @@ class LUQueryClusterInfo(NoHooksLU): 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", @@ -4741,14 +4621,10 @@ class LUQueryConfigValues(NoHooksLU): 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): @@ -4887,13 +4763,10 @@ def _StartInstanceDisks(lu, instance, force): 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): @@ -4972,7 +4845,8 @@ def _ShutdownInstanceDisks(lu, instance, disks=None, ignore_primary=False): 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 @@ -5033,9 +4907,8 @@ def _CheckNodesFreeDiskPerVG(lu, nodenames, req_sizes): 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): @@ -5075,19 +4948,12 @@ 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): @@ -5183,18 +5049,12 @@ class LUStartupInstance(LogicalUnit): 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): @@ -5264,17 +5124,12 @@ class LURebootInstance(LogicalUnit): 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): @@ -5331,18 +5186,12 @@ class LUShutdownInstance(LogicalUnit): _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): @@ -5423,16 +5272,12 @@ class LUReinstallInstance(LogicalUnit): _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): @@ -5487,18 +5332,12 @@ class LURecreateInstanceDisks(LogicalUnit): _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. @@ -5537,6 +5376,8 @@ class LURenameInstance(LogicalUnit): 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)): @@ -5550,7 +5391,7 @@ class LURenameInstance(LogicalUnit): new_name, errors.ECODE_EXISTS) def Exec(self, feedback_fn): - """Reinstall the instance. + """Rename the instance. """ inst = self.instance @@ -5596,17 +5437,12 @@ class LURenameInstance(LogicalUnit): 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): @@ -5684,16 +5520,11 @@ def _RemoveInstance(lu, feedback_fn, instance, ignore_failures): 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): @@ -5710,17 +5541,12 @@ class LUQueryInstances(NoHooksLU): 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): @@ -5862,7 +5688,7 @@ class LUFailoverInstance(LogicalUnit): (instance.name, target_node, msg)) -class LUMigrateInstance(LogicalUnit): +class LUInstanceMigrate(LogicalUnit): """Migrate an instance. This is migration without shutting down, compared to the failover, @@ -5871,13 +5697,6 @@ class LUMigrateInstance(LogicalUnit): """ HPATH = "instance-migrate" HTYPE = constants.HTYPE_INSTANCE - _OP_PARAMS = [ - _PInstanceName, - _PMigrationMode, - _PMigrationLive, - ("cleanup", False, ht.TBool), - ] - REQ_BGL = False def ExpandNames(self): @@ -5918,17 +5737,12 @@ class LUMigrateInstance(LogicalUnit): 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): @@ -6104,11 +5918,6 @@ class LUMigrateNode(LogicalUnit): """ HPATH = "node-migrate" HTYPE = constants.HTYPE_NODE - _OP_PARAMS = [ - _PNodeName, - _PMigrationMode, - _PMigrationLive, - ] REQ_BGL = False def ExpandNames(self): @@ -6671,7 +6480,7 @@ def _GenerateDiskTemplate(lu, template_name, 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 @@ -6718,32 +6527,52 @@ def _WipeDisks(lu, instance): """ 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): @@ -6779,7 +6608,7 @@ 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 @@ -6856,11 +6685,11 @@ def _ComputeDiskSizePerVG(disk_template, disks): # 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: @@ -6946,43 +6775,12 @@ def _CheckOSParams(lu, required, nodenames, osname, osparams): 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): @@ -7406,8 +7204,6 @@ class LUCreateInstance(LogicalUnit): 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" @@ -7890,7 +7686,7 @@ class LUCreateInstance(LogicalUnit): 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 @@ -7898,9 +7694,6 @@ class LUConnectConsole(NoHooksLU): console. """ - _OP_PARAMS = [ - _PInstanceName - ] REQ_BGL = False def ExpandNames(self): @@ -7944,26 +7737,20 @@ class LUConnectConsole(NoHooksLU): # 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) + 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): @@ -8700,12 +8487,6 @@ class LURepairNodeStorage(NoHooksLU): """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): @@ -8768,11 +8549,6 @@ class LUNodeEvacuationStrategy(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): @@ -8813,18 +8589,12 @@ class LUNodeEvacuationStrategy(NoHooksLU): 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): @@ -8875,7 +8645,7 @@ class LUGrowDisk(LogicalUnit): # 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. @@ -8915,14 +8685,10 @@ class LUGrowDisk(LogicalUnit): " 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): @@ -9073,25 +8839,12 @@ class LUQueryInstanceData(NoHooksLU): 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): @@ -9148,13 +8901,12 @@ class LUSetInstanceParams(LogicalUnit): " 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 @@ -9672,7 +9424,7 @@ class LUSetInstanceParams(LogicalUnit): 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: @@ -9742,14 +9494,10 @@ class LUSetInstanceParams(LogicalUnit): } -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): @@ -9782,14 +9530,10 @@ class LUQueryExports(NoHooksLU): 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): @@ -9837,23 +9581,12 @@ class LUPrepareExport(NoHooksLU): 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): @@ -10020,7 +9753,7 @@ class LUExportInstance(LogicalUnit): 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: @@ -10136,13 +9869,10 @@ class LUExportInstance(LogicalUnit): 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): @@ -10186,20 +9916,12 @@ class LURemoveExport(NoHooksLU): " Domain Name.") -class LUAddGroup(LogicalUnit): +class LUGroupAdd(LogicalUnit): """Logical unit for creating node groups. """ HPATH = "group-add" HTYPE = constants.HTYPE_GROUP - - _OP_PARAMS = [ - _PGroupName, - ("ndparams", None, ht.TOr(ht.TDict, ht.TNone)), - ("alloc_policy", None, ht.TOr(ht.TNone, - ht.TElemOf(constants.VALID_ALLOC_POLICIES))), - ] - REQ_BGL = False def ExpandNames(self): @@ -10253,55 +9975,144 @@ class LUAddGroup(LogicalUnit): 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", "alloc_policy", - "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() - name_to_uuid = dict((g.name, g.uuid) for g in all_groups.values()) + 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) + + 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) - if not self.op.names: - sorted_names = utils.NiceSort(name_to_uuid.keys()) - my_groups = [name_to_uuid[n] for n in sorted_names] + 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()) - 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) @@ -10309,18 +10120,27 @@ class LUQueryGroups(NoHooksLU): 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 + + group_to_nodes = None + group_to_instances = None - # 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. + # 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(): @@ -10329,53 +10149,45 @@ class LUQueryGroups(NoHooksLU): 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 uuid in my_groups: - group = all_groups[uuid] - 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 + + def CheckArguments(self): + self.gq = _GroupQuery(self.op.names, self.op.output_fields, False) -class LUSetGroupParams(LogicalUnit): + 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 - - _OP_PARAMS = [ - _PGroupName, - ("ndparams", None, ht.TOr(ht.TDict, ht.TNone)), - ("alloc_policy", None, ht.TOr(ht.TNone, - ht.TElemOf(constants.VALID_ALLOC_POLICIES))), - ] - REQ_BGL = False def CheckArguments(self): @@ -10407,8 +10219,9 @@ class LUSetGroupParams(LogicalUnit): (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 = self.group.SimpleFillND(self.op.ndparams) + self.new_ndparams = new_ndparams def BuildHooksEnv(self): """Build hooks env. @@ -10439,14 +10252,9 @@ class LUSetGroupParams(LogicalUnit): -class LURemoveGroup(LogicalUnit): +class LUGroupRemove(LogicalUnit): HPATH = "group-remove" HTYPE = constants.HTYPE_GROUP - - _OP_PARAMS = [ - _PGroupName, - ] - REQ_BGL = False def ExpandNames(self): @@ -10506,15 +10314,9 @@ class LURemoveGroup(LogicalUnit): 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): @@ -10616,11 +10418,6 @@ class LUGetTags(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): @@ -10640,9 +10437,6 @@ class LUSearchTags(NoHooksLU): """Searches the tags for a given pattern. """ - _OP_PARAMS = [ - ("pattern", ht.NoDefault, ht.TNonEmptyString), - ] REQ_BGL = False def ExpandNames(self): @@ -10682,12 +10476,6 @@ class LUAddTags(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): @@ -10716,12 +10504,6 @@ class LUDelTags(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): @@ -10759,12 +10541,6 @@ class LUTestDelay(NoHooksLU): 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): @@ -10810,12 +10586,6 @@ class LUTestJobqueue(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 @@ -11031,11 +10801,12 @@ class IAllocator(object): "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 @@ -11052,7 +10823,11 @@ class IAllocator(object): 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) @@ -11065,18 +10840,23 @@ class IAllocator(object): """ 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, @@ -11089,6 +10869,24 @@ class IAllocator(object): "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" % @@ -11130,9 +10928,9 @@ class IAllocator(object): "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 @@ -11310,25 +11108,6 @@ class LUTestAllocator(NoHooksLU): 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. @@ -11418,3 +11197,24 @@ class LUTestAllocator(NoHooksLU): 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)