X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/363c0106cb4616658f313fecc65ca47132cb3882..cf572b1378a61db132d84a5e03b0e5a157848caa:/lib/cmdlib.py diff --git a/lib/cmdlib.py b/lib/cmdlib.py index b66bfba..f867a4d 100644 --- a/lib/cmdlib.py +++ b/lib/cmdlib.py @@ -1,7 +1,7 @@ # # -# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Google Inc. +# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 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 @@ -721,6 +721,44 @@ def _GetUpdatedParams(old_params, update_dict, return params_copy +def _GetUpdatedIPolicy(old_ipolicy, new_ipolicy, group_policy=False): + """Return the new version of a instance policy. + + @param group_policy: whether this policy applies to a group and thus + we should support removal of policy entries + + """ + use_none = use_default = group_policy + ipolicy = copy.deepcopy(old_ipolicy) + for key, value in new_ipolicy.items(): + if key not in constants.IPOLICY_ALL_KEYS: + raise errors.OpPrereqError("Invalid key in new ipolicy: %s" % key, + errors.ECODE_INVAL) + if key in constants.IPOLICY_PARAMETERS: + utils.ForceDictType(value, constants.ISPECS_PARAMETER_TYPES) + ipolicy[key] = _GetUpdatedParams(old_ipolicy.get(key, {}), value, + use_none=use_none, + use_default=use_default) + else: + # FIXME: we assume all others are lists; this should be redone + # in a nicer way + if not value or value == [constants.VALUE_DEFAULT]: + if group_policy: + del ipolicy[key] + else: + raise errors.OpPrereqError("Can't unset ipolicy attribute '%s'" + " on the cluster'" % key, + errors.ECODE_INVAL) + else: + ipolicy[key] = list(value) + try: + objects.InstancePolicy.CheckParameterSyntax(ipolicy) + except errors.ConfigurationError, err: + raise errors.OpPrereqError("Invalid instance policy: %s" % err, + errors.ECODE_INVAL) + return ipolicy + + def _UpdateAndVerifySubDict(base, updates, type_check): """Updates and verifies a dict with sub dicts of the same type. @@ -1001,8 +1039,8 @@ def _CheckInstanceState(lu, instance, req_states, msg=None): if msg is None: msg = "can't use instance from outside %s states" % ", ".join(req_states) if instance.admin_state not in req_states: - raise errors.OpPrereqError("Instance %s is marked to be %s, %s" % - (instance, instance.admin_state, msg), + raise errors.OpPrereqError("Instance '%s' is marked to be %s, %s" % + (instance.name, instance.admin_state, msg), errors.ECODE_STATE) if constants.ADMINST_UP not in req_states: @@ -1158,6 +1196,19 @@ def _CheckTargetNodeIPolicy(lu, ipolicy, instance, node, ignore=False, raise errors.OpPrereqError(msg, errors.ECODE_INVAL) +def _ComputeNewInstanceViolations(old_ipolicy, new_ipolicy, instances): + """Computes a set of any instances that would violate the new ipolicy. + + @param old_ipolicy: The current (still in-place) ipolicy + @param new_ipolicy: The new (to become) ipolicy + @param instances: List of instances to verify + @return: A list of instances which violates the new ipolicy but did not before + + """ + return (_ComputeViolatingInstances(old_ipolicy, instances) - + _ComputeViolatingInstances(new_ipolicy, instances)) + + def _ExpandItemName(fn, name, kind): """Expand an item name. @@ -1378,6 +1429,19 @@ def _CalculateGroupIPolicy(cluster, group): return cluster.SimpleFillIPolicy(group.ipolicy) +def _ComputeViolatingInstances(ipolicy, instances): + """Computes a set of instances who violates given ipolicy. + + @param ipolicy: The ipolicy to verify + @type instances: object.Instance + @param instances: List of instances to verify + @return: A frozenset of instance names violating the ipolicy + + """ + return frozenset([inst.name for inst in instances + if _ComputeIPolicyInstanceViolation(ipolicy, inst)]) + + def _CheckNicsBridgesExist(lu, target_nics, target_node): """Check that the brigdes needed by a list of nics exist. @@ -3725,8 +3789,14 @@ class LUClusterSetParams(LogicalUnit): # all nodes to be modified. self.needed_locks = { locking.LEVEL_NODE: locking.ALL_SET, + locking.LEVEL_INSTANCE: locking.ALL_SET, + locking.LEVEL_NODEGROUP: locking.ALL_SET, + } + self.share_locks = { + locking.LEVEL_NODE: 1, + locking.LEVEL_INSTANCE: 1, + locking.LEVEL_NODEGROUP: 1, } - self.share_locks[locking.LEVEL_NODE] = 1 def BuildHooksEnv(self): """Build hooks env. @@ -3830,17 +3900,26 @@ class LUClusterSetParams(LogicalUnit): for storage, svalues in new_disk_state.items()) if self.op.ipolicy: - ipolicy = {} - for key, value in self.op.ipolicy.items(): - utils.ForceDictType(value, constants.ISPECS_PARAMETER_TYPES) - ipolicy[key] = _GetUpdatedParams(cluster.ipolicy.get(key, {}), - value) - try: - objects.InstancePolicy.CheckParameterSyntax(ipolicy) - except errors.ConfigurationError, err: - raise errors.OpPrereqError("Invalid instance policy: %s" % err, - errors.ECODE_INVAL) - self.new_ipolicy = ipolicy + self.new_ipolicy = _GetUpdatedIPolicy(cluster.ipolicy, self.op.ipolicy, + group_policy=False) + + all_instances = self.cfg.GetAllInstancesInfo().values() + violations = set() + for group in self.cfg.GetAllNodeGroupsInfo().values(): + instances = frozenset([inst for inst in all_instances + if compat.any(node in group.members + for node in inst.all_nodes)]) + new_ipolicy = objects.FillIPolicy(self.new_ipolicy, group.ipolicy) + new = _ComputeNewInstanceViolations(_CalculateGroupIPolicy(cluster, + group), + new_ipolicy, instances) + if new: + violations.update(new) + + if violations: + self.LogWarning("After the ipolicy change the following instances" + " violate them: %s", + utils.CommaJoin(violations)) if self.op.nicparams: utils.ForceDictType(self.op.nicparams, constants.NICS_PARAMETER_TYPES) @@ -6453,6 +6532,11 @@ class LUInstanceStartup(LogicalUnit): def ExpandNames(self): self._ExpandAndLockInstance() + self.recalculate_locks[locking.LEVEL_NODE_RES] = constants.LOCKS_REPLACE + + def DeclareLocks(self, level): + if level == locking.LEVEL_NODE_RES: + self._LockInstancesNodes(primary_only=True, level=locking.LEVEL_NODE_RES) def BuildHooksEnv(self): """Build hooks env. @@ -6522,7 +6606,7 @@ class LUInstanceStartup(LogicalUnit): if not remote_info.payload: # not running already _CheckNodeFreeMemory(self, instance.primary_node, "starting instance %s" % instance.name, - bep[constants.BE_MAXMEM], instance.hypervisor) + bep[constants.BE_MINMEM], instance.hypervisor) def Exec(self, feedback_fn): """Start the instance. @@ -6829,6 +6913,7 @@ class LUInstanceRecreateDisks(LogicalUnit): self.needed_locks[locking.LEVEL_NODE] = list(self.op.nodes) else: self.needed_locks[locking.LEVEL_NODE] = [] + self.needed_locks[locking.LEVEL_NODE_RES] = [] def DeclareLocks(self, level): if level == locking.LEVEL_NODE: @@ -7747,7 +7832,8 @@ class TLMigrateInstance(Tasklet): i_be = cluster.FillBE(instance) # check memory requirements on the secondary node - if not self.failover or instance.admin_state == constants.ADMINST_UP: + if (not self.cleanup and + (not self.failover or instance.admin_state == constants.ADMINST_UP)): _CheckNodeFreeMemory(self.lu, target_node, "migrating instance %s" % instance.name, i_be[constants.BE_MAXMEM], instance.hypervisor) @@ -8164,6 +8250,21 @@ class TLMigrateInstance(Tasklet): self._GoReconnect(False) self._WaitUntilSync() + # If the instance's disk template is `rbd' and there was a successful + # migration, unmap the device from the source node. + if self.instance.disk_template == constants.DT_RBD: + disks = _ExpandCheckDisks(instance, instance.disks) + self.feedback_fn("* unmapping instance's disks from %s" % source_node) + for disk in disks: + result = self.rpc.call_blockdev_shutdown(source_node, disk) + msg = result.fail_msg + if msg: + logging.error("Migration was successful, but couldn't unmap the" + " block device %s on source node %s: %s", + disk.iv_name, source_node, msg) + logging.error("You need to unmap the device %s manually on %s", + disk.iv_name, source_node) + self.feedback_fn("* done") def _ExecFailover(self): @@ -8431,6 +8532,15 @@ def _ComputeLDParams(disk_template, disk_params): elif disk_template == constants.DT_BLOCK: result.append(constants.DISK_LD_DEFAULTS[constants.LD_BLOCKDEV]) + elif disk_template == constants.DT_RBD: + params = { + constants.LDP_POOL: dt_params[constants.RBD_POOL] + } + params = \ + objects.FillDict(constants.DISK_LD_DEFAULTS[constants.LD_RBD], + params) + result.append(params) + return result @@ -8476,7 +8586,7 @@ def _GenerateDiskTemplate(lu, template_name, if template_name == constants.DT_DISKLESS: pass elif template_name == constants.DT_PLAIN: - if len(secondary_nodes) != 0: + if secondary_nodes: raise errors.ProgrammerError("Wrong template configuration") names = _GenerateUniqueNames(lu, [".disk%d" % (base_index + i) @@ -8520,7 +8630,7 @@ def _GenerateDiskTemplate(lu, template_name, disk_dev.mode = disk[constants.IDISK_MODE] disks.append(disk_dev) elif template_name == constants.DT_FILE: - if len(secondary_nodes) != 0: + if secondary_nodes: raise errors.ProgrammerError("Wrong template configuration") opcodes.RequireFileStorage() @@ -8537,7 +8647,7 @@ def _GenerateDiskTemplate(lu, template_name, params=ld_params[0]) disks.append(disk_dev) elif template_name == constants.DT_SHARED_FILE: - if len(secondary_nodes) != 0: + if secondary_nodes: raise errors.ProgrammerError("Wrong template configuration") opcodes.RequireSharedFileStorage() @@ -8554,7 +8664,7 @@ def _GenerateDiskTemplate(lu, template_name, params=ld_params[0]) disks.append(disk_dev) elif template_name == constants.DT_BLOCK: - if len(secondary_nodes) != 0: + if secondary_nodes: raise errors.ProgrammerError("Wrong template configuration") for idx, disk in enumerate(disk_info): @@ -8567,6 +8677,22 @@ def _GenerateDiskTemplate(lu, template_name, mode=disk[constants.IDISK_MODE], params=ld_params[0]) disks.append(disk_dev) + elif template_name == constants.DT_RBD: + if secondary_nodes: + raise errors.ProgrammerError("Wrong template configuration") + + names = _GenerateUniqueNames(lu, [".rbd.disk%d" % (base_index + i) + for i in range(disk_count)]) + + for idx, disk in enumerate(disk_info): + disk_index = idx + base_index + disk_dev = objects.Disk(dev_type=constants.LD_RBD, + size=disk[constants.IDISK_SIZE], + logical_id=("rbd", names[idx]), + iv_name="disk/%d" % disk_index, + mode=disk[constants.IDISK_MODE], + params=ld_params[0]) + disks.append(disk_dev) else: raise errors.ProgrammerError("Invalid disk template '%s'" % template_name) @@ -8807,6 +8933,7 @@ def _ComputeDiskSize(disk_template, disks): constants.DT_FILE: None, constants.DT_SHARED_FILE: 0, constants.DT_BLOCK: 0, + constants.DT_RBD: 0, } if disk_template not in req_size_dict: @@ -9641,9 +9768,15 @@ class LUInstanceCreate(LogicalUnit): self.diskparams = group_info.diskparams if not self.adopt_disks: - # Check lv size requirements, if not adopting - req_sizes = _ComputeDiskSizePerVG(self.op.disk_template, self.disks) - _CheckNodesFreeDiskPerVG(self, nodenames, req_sizes) + if self.op.disk_template == constants.DT_RBD: + # _CheckRADOSFreeSpace() is just a placeholder. + # Any function that checks prerequisites can be placed here. + # Check if there is enough space on the RADOS cluster. + _CheckRADOSFreeSpace() + else: + # Check lv size requirements, if not adopting + req_sizes = _ComputeDiskSizePerVG(self.op.disk_template, self.disks) + _CheckNodesFreeDiskPerVG(self, nodenames, req_sizes) elif self.op.disk_template == constants.DT_PLAIN: # Check the adoption data all_lvs = set(["%s/%s" % (disk[constants.IDISK_VG], @@ -9956,6 +10089,14 @@ class LUInstanceCreate(LogicalUnit): return list(iobj.all_nodes) +def _CheckRADOSFreeSpace(): + """Compute disk size requirements inside the RADOS cluster. + + """ + # For the RADOS cluster we assume there is always enough space. + pass + + class LUInstanceConsole(NoHooksLU): """Connect to an instance's console. @@ -10071,7 +10212,8 @@ class LUInstanceReplaceDisks(LogicalUnit): self.replacer = TLReplaceDisks(self, self.op.instance_name, self.op.mode, self.op.iallocator, self.op.remote_node, - self.op.disks, False, self.op.early_release) + self.op.disks, False, self.op.early_release, + self.op.ignore_ipolicy) self.tasklets = [self.replacer] @@ -10153,7 +10295,7 @@ class TLReplaceDisks(Tasklet): """ def __init__(self, lu, instance_name, mode, iallocator_name, remote_node, - disks, delay_iallocator, early_release): + disks, delay_iallocator, early_release, ignore_ipolicy): """Initializes this class. """ @@ -10167,6 +10309,7 @@ class TLReplaceDisks(Tasklet): self.disks = disks self.delay_iallocator = delay_iallocator self.early_release = early_release + self.ignore_ipolicy = ignore_ipolicy # Runtime data self.instance = None @@ -10389,6 +10532,16 @@ class TLReplaceDisks(Tasklet): if not self.disks: self.disks = range(len(self.instance.disks)) + # TODO: This is ugly, but right now we can't distinguish between internal + # submitted opcode and external one. We should fix that. + if self.remote_node_info: + # We change the node, lets verify it still meets instance policy + new_group_info = self.cfg.GetNodeGroup(self.remote_node_info.group) + ipolicy = _CalculateGroupIPolicy(self.cfg.GetClusterInfo(), + new_group_info) + _CheckTargetNodeIPolicy(self, ipolicy, instance, self.remote_node_info, + ignore=self.ignore_ipolicy) + # TODO: compute disk parameters primary_node_info = self.cfg.GetNodeInfo(instance.primary_node) secondary_node_info = self.cfg.GetNodeInfo(secondary_node) @@ -11299,7 +11452,8 @@ class LUInstanceGrowDisk(LogicalUnit): self.disk = instance.FindDisk(self.op.disk) if instance.disk_template not in (constants.DT_FILE, - constants.DT_SHARED_FILE): + constants.DT_SHARED_FILE, + constants.DT_RBD): # TODO: check the free disk space for file, when that feature will be # supported _CheckNodesFreeDiskPerVG(self, nodenames, @@ -11546,7 +11700,8 @@ class LUInstanceSetParams(LogicalUnit): def CheckArguments(self): if not (self.op.nics or self.op.disks or self.op.disk_template or self.op.hvparams or self.op.beparams or self.op.os_name or - self.op.online_inst or self.op.offline_inst): + self.op.online_inst or self.op.offline_inst or + self.op.runtime_mem): raise errors.OpPrereqError("No changes submitted", errors.ECODE_INVAL) if self.op.hvparams: @@ -11730,6 +11885,8 @@ class LUInstanceSetParams(LogicalUnit): env = _BuildInstanceHookEnvByObject(self, self.instance, override=args) if self.op.disk_template: env["NEW_DISK_TEMPLATE"] = self.op.disk_template + if self.op.runtime_mem: + env["RUNTIME_MEMORY"] = self.op.runtime_mem return env @@ -11934,6 +12091,33 @@ class LUInstanceSetParams(LogicalUnit): " %s, due to not enough memory" % node, errors.ECODE_STATE) + if self.op.runtime_mem: + remote_info = self.rpc.call_instance_info(instance.primary_node, + instance.name, + instance.hypervisor) + remote_info.Raise("Error checking node %s" % instance.primary_node) + if not remote_info.payload: # not running already + raise errors.OpPrereqError("Instance %s is not running" % instance.name, + errors.ECODE_STATE) + + current_memory = remote_info.payload["memory"] + if (not self.op.force and + (self.op.runtime_mem > self.be_proposed[constants.BE_MAXMEM] or + self.op.runtime_mem < self.be_proposed[constants.BE_MINMEM])): + raise errors.OpPrereqError("Instance %s must have memory between %d" + " and %d MB of memory unless --force is" + " given" % (instance.name, + self.be_proposed[constants.BE_MINMEM], + self.be_proposed[constants.BE_MAXMEM]), + errors.ECODE_INVAL) + + if self.op.runtime_mem > current_memory: + _CheckNodeFreeMemory(self, instance.primary_node, + "ballooning memory for instance %s" % + instance.name, + self.op.memory - current_memory, + instance.hypervisor) + # NIC processing self.nic_pnew = {} self.nic_pinst = {} @@ -12177,6 +12361,15 @@ class LUInstanceSetParams(LogicalUnit): result = [] instance = self.instance + + # runtime memory + if self.op.runtime_mem: + rpcres = self.rpc.call_instance_balloon_memory(instance.primary_node, + instance, + self.op.runtime_mem) + rpcres.Raise("Cannot modify instance runtime memory") + result.append(("runtime_memory", self.op.runtime_mem)) + # disk changes for disk_op, disk_dict in self.op.disks: if disk_op == constants.DDM_REMOVE: @@ -13268,14 +13461,32 @@ class LUGroupSetParams(LogicalUnit): self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name) self.needed_locks = { + locking.LEVEL_INSTANCE: [], locking.LEVEL_NODEGROUP: [self.group_uuid], } + self.share_locks[locking.LEVEL_INSTANCE] = 1 + + def DeclareLocks(self, level): + if level == locking.LEVEL_INSTANCE: + assert not self.needed_locks[locking.LEVEL_INSTANCE] + + # Lock instances optimistically, needs verification once group lock has + # been acquired + self.needed_locks[locking.LEVEL_INSTANCE] = \ + self.cfg.GetNodeGroupInstances(self.group_uuid) + def CheckPrereq(self): """Check prerequisites. """ + owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE)) + + # Check if locked instances are still correct + _CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances) + self.group = self.cfg.GetNodeGroup(self.group_uuid) + cluster = self.cfg.GetClusterInfo() if self.group is None: raise errors.OpExecError("Could not retrieve group '%s' (UUID: %s)" % @@ -13306,18 +13517,22 @@ class LUGroupSetParams(LogicalUnit): self.group.disk_state_static) if self.op.ipolicy: - g_ipolicy = {} - for key, value in self.op.ipolicy.iteritems(): - g_ipolicy[key] = _GetUpdatedParams(self.group.ipolicy.get(key, {}), - value, - use_none=True) - utils.ForceDictType(g_ipolicy[key], constants.ISPECS_PARAMETER_TYPES) - self.new_ipolicy = g_ipolicy - try: - objects.InstancePolicy.CheckParameterSyntax(self.new_ipolicy) - except errors.ConfigurationError, err: - raise errors.OpPrereqError("Invalid instance policy: %s" % err, - errors.ECODE_INVAL) + self.new_ipolicy = _GetUpdatedIPolicy(self.group.ipolicy, + self.op.ipolicy, + group_policy=True) + + new_ipolicy = cluster.SimpleFillIPolicy(self.new_ipolicy) + inst_filter = lambda inst: inst.name in owned_instances + instances = self.cfg.GetInstancesInfoByFilter(inst_filter).values() + violations = \ + _ComputeNewInstanceViolations(_CalculateGroupIPolicy(cluster, + self.group), + new_ipolicy, instances) + + if violations: + self.LogWarning("After the ipolicy change the following instances" + " violate them: %s", + utils.CommaJoin(violations)) def BuildHooksEnv(self): """Build hooks env. @@ -14110,9 +14325,11 @@ class IAllocator(object): """Compute node groups data. """ + cluster = cfg.GetClusterInfo() ng = dict((guuid, { "name": gdata.name, "alloc_policy": gdata.alloc_policy, + "ipolicy": _CalculateGroupIPolicy(cluster, gdata), }) for guuid, gdata in cfg.GetAllNodeGroupsInfo().items())