X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/adfa3b261f2ee731cb6905f42a539327e31c4492..bcd87e22bb462c87c50fc07a2cca14a977e79253:/lib/cmdlib.py diff --git a/lib/cmdlib.py b/lib/cmdlib.py index bdcc2fe..9ee361f 100644 --- a/lib/cmdlib.py +++ b/lib/cmdlib.py @@ -861,7 +861,7 @@ def _ExpandInstanceName(cfg, name): def _BuildInstanceHookEnv(name, primary_node, secondary_nodes, os_type, status, memory, vcpus, nics, disk_template, disks, - bep, hvp, hypervisor_name): + bep, hvp, hypervisor_name, tags): """Builds instance related env variables for hooks This builds the hook environment from individual variables. @@ -893,6 +893,8 @@ def _BuildInstanceHookEnv(name, primary_node, secondary_nodes, os_type, status, @param hvp: the hypervisor parameters for the instance @type hypervisor_name: string @param hypervisor_name: the hypervisor for the instance + @type tags: list + @param tags: list of instance tags as strings @rtype: dict @return: the hook environment for this instance @@ -940,6 +942,11 @@ def _BuildInstanceHookEnv(name, primary_node, secondary_nodes, os_type, status, env["INSTANCE_DISK_COUNT"] = disk_count + if not tags: + tags = [] + + env["INSTANCE_TAGS"] = " ".join(tags) + for source, kind in [(bep, "BE"), (hvp, "HV")]: for key, value in source.items(): env["INSTANCE_%s_%s" % (kind, key)] = value @@ -1003,6 +1010,7 @@ def _BuildInstanceHookEnvByObject(lu, instance, override=None): 'bep': bep, 'hvp': hvp, 'hypervisor_name': instance.hypervisor, + 'tags': instance.tags, } if override: args.update(override) @@ -1254,7 +1262,7 @@ class LUClusterDestroy(LogicalUnit): def _VerifyCertificate(filename): - """Verifies a certificate for LUClusterVerifyConfig. + """Verifies a certificate for L{LUClusterVerifyConfig}. @type filename: string @param filename: Path to PEM file @@ -1406,8 +1414,7 @@ class LUClusterVerifyConfig(NoHooksLU, _VerifyErrors): """Verifies the cluster config. """ - - REQ_BGL = False + REQ_BGL = True def _VerifyHVP(self, hvp_data): """Verifies locally the syntax of the hypervisor parameters. @@ -1424,6 +1431,8 @@ class LUClusterVerifyConfig(NoHooksLU, _VerifyErrors): self._ErrorIf(True, self.ECLUSTERCFG, None, msg % str(err)) def ExpandNames(self): + # Information can be safely retrieved as the BGL is acquired in exclusive + # mode self.all_group_info = self.cfg.GetAllNodeGroupsInfo() self.all_node_info = self.cfg.GetAllNodesInfo() self.all_inst_info = self.cfg.GetAllInstancesInfo() @@ -1455,7 +1464,7 @@ class LUClusterVerifyConfig(NoHooksLU, _VerifyErrors): feedback_fn("* Verifying all nodes belong to an existing group") # We do this verification here because, should this bogus circumstance - # occur, it would never be catched by VerifyGroup, which only acts on + # occur, it would never be caught by VerifyGroup, which only acts on # nodes/instances reachable from existing node groups. dangling_nodes = set(node.name for node in self.all_node_info.values() @@ -1492,7 +1501,6 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors): """Verifies the status of a node group. """ - HPATH = "cluster-verify" HTYPE = constants.HTYPE_CLUSTER REQ_BGL = False @@ -1553,45 +1561,39 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors): # This raises errors.OpPrereqError on its own: self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name) - all_node_info = self.cfg.GetAllNodesInfo() - all_inst_info = self.cfg.GetAllInstancesInfo() - - node_names = set(node.name - for node in all_node_info.values() - if node.group == self.group_uuid) - - inst_names = [inst.name - for inst in all_inst_info.values() - if inst.primary_node in node_names] - - # In Exec(), we warn about mirrored instances that have primary and - # secondary living in separate node groups. To fully verify that - # volumes for these instances are healthy, we will need to do an - # extra call to their secondaries. We ensure here those nodes will - # be locked. - for inst in inst_names: - if all_inst_info[inst].disk_template in constants.DTS_INT_MIRROR: - node_names.update(all_inst_info[inst].secondary_nodes) + # Get instances in node group; this is unsafe and needs verification later + inst_names = self.cfg.GetNodeGroupInstances(self.group_uuid) self.needed_locks = { - locking.LEVEL_NODEGROUP: [self.group_uuid], - locking.LEVEL_NODE: list(node_names), locking.LEVEL_INSTANCE: inst_names, - } + locking.LEVEL_NODEGROUP: [self.group_uuid], + locking.LEVEL_NODE: [], + } self.share_locks = dict.fromkeys(locking.LEVELS, 1) - def CheckPrereq(self): - self.all_node_info = self.cfg.GetAllNodesInfo() - self.all_inst_info = self.cfg.GetAllInstancesInfo() + def DeclareLocks(self, level): + if level == locking.LEVEL_NODE: + # Get members of node group; this is unsafe and needs verification later + nodes = set(self.cfg.GetNodeGroup(self.group_uuid).members) - group_nodes = set(node.name - for node in self.all_node_info.values() - if node.group == self.group_uuid) + all_inst_info = self.cfg.GetAllInstancesInfo() - group_instances = set(inst.name - for inst in self.all_inst_info.values() - if inst.primary_node in group_nodes) + # In Exec(), we warn about mirrored instances that have primary and + # secondary living in separate node groups. To fully verify that + # volumes for these instances are healthy, we will need to do an + # extra call to their secondaries. We ensure here those nodes will + # be locked. + for inst in self.glm.list_owned(locking.LEVEL_INSTANCE): + # Important: access only the instances whose lock is owned + if all_inst_info[inst].disk_template in constants.DTS_INT_MIRROR: + nodes.update(all_inst_info[inst].secondary_nodes) + + self.needed_locks[locking.LEVEL_NODE] = nodes + + def CheckPrereq(self): + group_nodes = set(self.cfg.GetNodeGroup(self.group_uuid).members) + group_instances = self.cfg.GetNodeGroupInstances(self.group_uuid) unlocked_nodes = \ group_nodes.difference(self.glm.list_owned(locking.LEVEL_NODE)) @@ -1600,13 +1602,16 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors): group_instances.difference(self.glm.list_owned(locking.LEVEL_INSTANCE)) if unlocked_nodes: - raise errors.OpPrereqError("missing lock for nodes: %s" % + raise errors.OpPrereqError("Missing lock for nodes: %s" % utils.CommaJoin(unlocked_nodes)) if unlocked_instances: - raise errors.OpPrereqError("missing lock for instances: %s" % + raise errors.OpPrereqError("Missing lock for instances: %s" % utils.CommaJoin(unlocked_instances)) + self.all_node_info = self.cfg.GetAllNodesInfo() + self.all_inst_info = self.cfg.GetAllInstancesInfo() + self.my_node_names = utils.NiceSort(group_nodes) self.my_inst_names = utils.NiceSort(group_instances) @@ -2559,6 +2564,8 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors): all_nvinfo = self.rpc.call_node_verify(self.my_node_names, node_verify_param, self.cfg.GetClusterName()) + nvinfo_endtime = time.time() + if self.extra_lv_nodes and vg_name is not None: extra_lv_nvinfo = \ self.rpc.call_node_verify(self.extra_lv_nodes, @@ -2566,7 +2573,6 @@ class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors): self.cfg.GetClusterName()) else: extra_lv_nvinfo = {} - nvinfo_endtime = time.time() all_drbd_map = self.cfg.ComputeDRBDMap() @@ -6650,45 +6656,15 @@ class LUNodeMigrate(LogicalUnit): REQ_BGL = False def CheckArguments(self): - _CheckIAllocatorOrNode(self, "iallocator", "remote_node") + pass def ExpandNames(self): self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name) - self.needed_locks = {} - - # Create tasklets for migrating instances for all instances on this node - names = [] - tasklets = [] - - self.lock_all_nodes = False - - for inst in _GetNodePrimaryInstances(self.cfg, self.op.node_name): - logging.debug("Migrating instance %s", inst.name) - names.append(inst.name) - - tasklets.append(TLMigrateInstance(self, inst.name, cleanup=False)) - - if inst.disk_template in constants.DTS_EXT_MIRROR: - # We need to lock all nodes, as the iallocator will choose the - # destination nodes afterwards - self.lock_all_nodes = True - - self.tasklets = tasklets - - # Declare node locks - if self.lock_all_nodes: - self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET - else: - self.needed_locks[locking.LEVEL_NODE] = [self.op.node_name] - self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND - - # Declare instance locks - self.needed_locks[locking.LEVEL_INSTANCE] = names - - def DeclareLocks(self, level): - if level == locking.LEVEL_NODE and not self.lock_all_nodes: - self._LockInstancesNodes() + self.share_locks = dict.fromkeys(locking.LEVELS, 1) + self.needed_locks = { + locking.LEVEL_NODE: [self.op.node_name], + } def BuildHooksEnv(self): """Build hooks env. @@ -6707,6 +6683,30 @@ class LUNodeMigrate(LogicalUnit): nl = [self.cfg.GetMasterNode()] return (nl, nl) + def CheckPrereq(self): + pass + + def Exec(self, feedback_fn): + # Prepare jobs for migration instances + jobs = [ + [opcodes.OpInstanceMigrate(instance_name=inst.name, + mode=self.op.mode, + live=self.op.live, + iallocator=self.op.iallocator, + target_node=self.op.target_node)] + for inst in _GetNodePrimaryInstances(self.cfg, self.op.node_name) + ] + + # TODO: Run iallocator in this opcode and pass correct placement options to + # OpInstanceMigrate. Since other jobs can modify the cluster between + # running the iallocator and the actual migration, a good consistency model + # will have to be found. + + assert (frozenset(self.glm.list_owned(locking.LEVEL_NODE)) == + frozenset([self.op.node_name])) + + return ResultWithJobs(jobs) + class TLMigrateInstance(Tasklet): """Tasklet class for instance migration. @@ -8008,10 +8008,10 @@ class LUInstanceCreate(LogicalUnit): mode=constants.IALLOCATOR_MODE_ALLOC, name=self.op.instance_name, disk_template=self.op.disk_template, - tags=[], + tags=self.op.tags, os=self.op.os_type, vcpus=self.be_full[constants.BE_VCPUS], - mem_size=self.be_full[constants.BE_MEMORY], + memory=self.be_full[constants.BE_MEMORY], disks=self.disks, nics=nics, hypervisor=self.op.hypervisor, @@ -8065,6 +8065,7 @@ class LUInstanceCreate(LogicalUnit): bep=self.be_full, hvp=self.hv_full, hypervisor_name=self.op.hypervisor, + tags=self.op.tags, )) return env @@ -8166,9 +8167,13 @@ class LUInstanceCreate(LogicalUnit): nics.append(ndict) self.op.nics = nics + if not self.op.tags and einfo.has_option(constants.INISECT_INS, "tags"): + self.op.tags = einfo.get(constants.INISECT_INS, "tags").split() + if (self.op.hypervisor is None and einfo.has_option(constants.INISECT_INS, "hypervisor")): self.op.hypervisor = einfo.get(constants.INISECT_INS, "hypervisor") + if einfo.has_section(constants.INISECT_HYP): # use the export parameters but do not override the ones # specified by the user @@ -8244,6 +8249,10 @@ class LUInstanceCreate(LogicalUnit): ",".join(enabled_hvs)), errors.ECODE_STATE) + # Check tag validity + for tag in self.op.tags: + objects.TaggableObject.ValidateTag(tag) + # check hypervisor parameter syntax (locally) utils.ForceDictType(self.op.hvparams, constants.HVS_PARAMETER_TYPES) filled_hvp = cluster.SimpleFillHV(self.op.hypervisor, self.op.os_type, @@ -8591,6 +8600,10 @@ class LUInstanceCreate(LogicalUnit): osparams=self.op.osparams, ) + if self.op.tags: + for tag in self.op.tags: + iobj.AddTag(tag) + if self.adopt_disks: if self.op.disk_template == constants.DT_PLAIN: # rename LVs to the newly-generated names; we need to construct @@ -9740,10 +9753,13 @@ class LUNodeEvacStrategy(NoHooksLU): locks[locking.LEVEL_NODE] = self.op.nodes + [self.op.remote_node] def Exec(self, feedback_fn): + instances = [] + for node in self.op.nodes: + instances.extend(_GetNodeSecondaryInstances(self.cfg, node)) + if not instances: + return [] + if self.op.remote_node is not None: - instances = [] - for node in self.op.nodes: - instances.extend(_GetNodeSecondaryInstances(self.cfg, node)) result = [] for i in instances: if i.primary_node == self.op.remote_node: @@ -12010,26 +12026,28 @@ class IAllocator(object): self.in_text = self.out_text = self.in_data = self.out_data = None # init all input fields so that pylint is happy self.mode = mode - self.mem_size = self.disks = self.disk_template = None + self.memory = self.disks = self.disk_template = None self.os = self.tags = self.nics = self.vcpus = None self.hypervisor = None self.relocate_from = None self.name = None self.evac_nodes = None self.instances = None - self.reloc_mode = None - self.target_groups = None + self.evac_mode = None + self.target_groups = [] # computed fields self.required_nodes = None # init result fields self.success = self.info = self.result = None try: - (fn, keyset, self._result_check) = self._MODE_DATA[self.mode] + (fn, keydata, self._result_check) = self._MODE_DATA[self.mode] except KeyError: raise errors.ProgrammerError("Unknown mode '%s' passed to the" " IAllocator" % self.mode) + keyset = [n for (n, _) in keydata] + for key in kwargs: if key not in keyset: raise errors.ProgrammerError("Invalid input parameter '%s' to" @@ -12040,7 +12058,7 @@ class IAllocator(object): if key not in kwargs: raise errors.ProgrammerError("Missing input parameter '%s' to" " IAllocator" % key) - self._BuildInputData(compat.partial(fn, self)) + self._BuildInputData(compat.partial(fn, self), keydata) def _ComputeClusterData(self): """Compute the generic allocator input data. @@ -12069,8 +12087,7 @@ class IAllocator(object): hypervisor_name = self.hypervisor elif self.mode == constants.IALLOCATOR_MODE_RELOC: hypervisor_name = cfg.GetInstanceInfo(self.name).hypervisor - elif self.mode in (constants.IALLOCATOR_MODE_MEVAC, - constants.IALLOCATOR_MODE_MRELOC): + else: hypervisor_name = cluster_info.enabled_hypervisors[0] node_data = self.rpc.call_node_info(node_list, cfg.GetVGName(), @@ -12250,11 +12267,12 @@ class IAllocator(object): "tags": self.tags, "os": self.os, "vcpus": self.vcpus, - "memory": self.mem_size, + "memory": self.memory, "disks": self.disks, "disk_space_total": disk_space, "nics": self.nics, "required_nodes": self.required_nodes, + "hypervisor": self.hypervisor, } return request @@ -12304,17 +12322,25 @@ class IAllocator(object): } return request - def _AddMultiRelocate(self): - """Get data for multi-relocate requests. + def _AddNodeEvacuate(self): + """Get data for node-evacuate requests. + + """ + return { + "instances": self.instances, + "evac_mode": self.evac_mode, + } + + def _AddChangeGroup(self): + """Get data for node-evacuate requests. """ return { "instances": self.instances, - "reloc_mode": self.reloc_mode, "target_groups": self.target_groups, } - def _BuildInputData(self, fn): + def _BuildInputData(self, fn, keydata): """Build input data structures. """ @@ -12322,30 +12348,58 @@ class IAllocator(object): request = fn() request["type"] = self.mode + for keyname, keytype in keydata: + if keyname not in request: + raise errors.ProgrammerError("Request parameter %s is missing" % + keyname) + val = request[keyname] + if not keytype(val): + raise errors.ProgrammerError("Request parameter %s doesn't pass" + " validation, value %s, expected" + " type %s" % (keyname, val, keytype)) self.in_data["request"] = request self.in_text = serializer.Dump(self.in_data) + _STRING_LIST = ht.TListOf(ht.TString) + _JOBSET_LIST = ht.TListOf(ht.TListOf(ht.TStrictDict(True, False, { + # pylint: disable-msg=E1101 + # Class '...' has no 'OP_ID' member + "OP_ID": ht.TElemOf([opcodes.OpInstanceFailover.OP_ID, + opcodes.OpInstanceMigrate.OP_ID, + opcodes.OpInstanceReplaceDisks.OP_ID]) + }))) _MODE_DATA = { constants.IALLOCATOR_MODE_ALLOC: (_AddNewInstance, - ["name", "mem_size", "disks", "disk_template", "os", "tags", "nics", - "vcpus", "hypervisor"], ht.TList), + [ + ("name", ht.TString), + ("memory", ht.TInt), + ("disks", ht.TListOf(ht.TDict)), + ("disk_template", ht.TString), + ("os", ht.TString), + ("tags", _STRING_LIST), + ("nics", ht.TListOf(ht.TDict)), + ("vcpus", ht.TInt), + ("hypervisor", ht.TString), + ], ht.TList), constants.IALLOCATOR_MODE_RELOC: - (_AddRelocateInstance, ["name", "relocate_from"], ht.TList), + (_AddRelocateInstance, + [("name", ht.TString), ("relocate_from", _STRING_LIST)], + ht.TList), constants.IALLOCATOR_MODE_MEVAC: - (_AddEvacuateNodes, ["evac_nodes"], - ht.TListOf(ht.TAnd(ht.TIsLength(2), - ht.TListOf(ht.TString)))), - constants.IALLOCATOR_MODE_MRELOC: - (_AddMultiRelocate, ["instances", "reloc_mode", "target_groups"], - ht.TListOf(ht.TListOf(ht.TStrictDict(True, False, { - # pylint: disable-msg=E1101 - # Class '...' has no 'OP_ID' member - "OP_ID": ht.TElemOf([opcodes.OpInstanceFailover.OP_ID, - opcodes.OpInstanceMigrate.OP_ID, - opcodes.OpInstanceReplaceDisks.OP_ID]) - })))), + (_AddEvacuateNodes, [("evac_nodes", _STRING_LIST)], + ht.TListOf(ht.TAnd(ht.TIsLength(2), _STRING_LIST))), + constants.IALLOCATOR_MODE_NODE_EVAC: + (_AddNodeEvacuate, [ + ("instances", _STRING_LIST), + ("evac_mode", ht.TElemOf(constants.IALLOCATOR_NEVAC_MODES)), + ], _JOBSET_LIST), + constants.IALLOCATOR_MODE_CHG_GROUP: + (_AddChangeGroup, [ + ("instances", _STRING_LIST), + ("target_groups", _STRING_LIST), + ], _JOBSET_LIST), } def Run(self, name, validate=True, call_fn=None): @@ -12477,7 +12531,7 @@ class LUTestAllocator(NoHooksLU): """ if self.op.mode == constants.IALLOCATOR_MODE_ALLOC: - for attr in ["mem_size", "disks", "disk_template", + for attr in ["memory", "disks", "disk_template", "os", "tags", "nics", "vcpus"]: if not hasattr(self.op, attr): raise errors.OpPrereqError("Missing attribute '%s' on opcode input" % @@ -12494,10 +12548,10 @@ class LUTestAllocator(NoHooksLU): errors.ECODE_INVAL) for row in self.op.disks: if (not isinstance(row, dict) or - "size" not in row or - not isinstance(row["size"], int) or - "mode" not in row or - row["mode"] not in ['r', 'w']): + constants.IDISK_SIZE not in row or + not isinstance(row[constants.IDISK_SIZE], int) or + constants.IDISK_MODE not in row or + row[constants.IDISK_MODE] not in constants.DISK_ACCESS_SET): raise errors.OpPrereqError("Invalid contents of the 'disks'" " parameter", errors.ECODE_INVAL) if self.op.hypervisor is None: @@ -12510,12 +12564,11 @@ class LUTestAllocator(NoHooksLU): if not hasattr(self.op, "evac_nodes"): raise errors.OpPrereqError("Missing attribute 'evac_nodes' on" " opcode input", errors.ECODE_INVAL) - elif self.op.mode == constants.IALLOCATOR_MODE_MRELOC: - if self.op.instances: - self.op.instances = _GetWantedInstances(self, self.op.instances) - else: - raise errors.OpPrereqError("Missing instances to relocate", - errors.ECODE_INVAL) + elif self.op.mode in (constants.IALLOCATOR_MODE_CHG_GROUP, + constants.IALLOCATOR_MODE_NODE_EVAC): + if not self.op.instances: + raise errors.OpPrereqError("Missing instances", errors.ECODE_INVAL) + self.op.instances = _GetWantedInstances(self, self.op.instances) else: raise errors.OpPrereqError("Invalid test allocator mode '%s'" % self.op.mode, errors.ECODE_INVAL) @@ -12536,7 +12589,7 @@ class LUTestAllocator(NoHooksLU): ial = IAllocator(self.cfg, self.rpc, mode=self.op.mode, name=self.op.name, - mem_size=self.op.mem_size, + memory=self.op.memory, disks=self.op.disks, disk_template=self.op.disk_template, os=self.op.os, @@ -12555,12 +12608,16 @@ class LUTestAllocator(NoHooksLU): ial = IAllocator(self.cfg, self.rpc, mode=self.op.mode, evac_nodes=self.op.evac_nodes) - elif self.op.mode == constants.IALLOCATOR_MODE_MRELOC: + elif self.op.mode == constants.IALLOCATOR_MODE_CHG_GROUP: ial = IAllocator(self.cfg, self.rpc, mode=self.op.mode, instances=self.op.instances, - reloc_mode=self.op.reloc_mode, target_groups=self.op.target_groups) + elif self.op.mode == constants.IALLOCATOR_MODE_NODE_EVAC: + ial = IAllocator(self.cfg, self.rpc, + mode=self.op.mode, + instances=self.op.instances, + evac_mode=self.op.evac_mode) else: raise errors.ProgrammerError("Uncatched mode %s in" " LUTestAllocator.Exec", self.op.mode)