+ def _DetermineNodes(self):
+ """Gets the list of nodes to operate on.
+
+ """
+ if self.op.remote_node is None:
+ # Iallocator will choose any node(s) in the same group
+ group_nodes = self.cfg.GetNodeGroupMembersByNodes([self.op.node_name])
+ else:
+ group_nodes = frozenset([self.op.remote_node])
+
+ # Determine nodes to be locked
+ return set([self.op.node_name]) | group_nodes
+
+ def _DetermineInstances(self):
+ """Builds list of instances to operate on.
+
+ """
+ assert self.op.mode in constants.NODE_EVAC_MODES
+
+ if self.op.mode == constants.NODE_EVAC_PRI:
+ # Primary instances only
+ inst_fn = _GetNodePrimaryInstances
+ assert self.op.remote_node is None, \
+ "Evacuating primary instances requires iallocator"
+ elif self.op.mode == constants.NODE_EVAC_SEC:
+ # Secondary instances only
+ inst_fn = _GetNodeSecondaryInstances
+ else:
+ # All instances
+ assert self.op.mode == constants.NODE_EVAC_ALL
+ inst_fn = _GetNodeInstances
+ # TODO: In 2.6, change the iallocator interface to take an evacuation mode
+ # per instance
+ raise errors.OpPrereqError("Due to an issue with the iallocator"
+ " interface it is not possible to evacuate"
+ " all instances at once; specify explicitly"
+ " whether to evacuate primary or secondary"
+ " instances",
+ errors.ECODE_INVAL)
+
+ return inst_fn(self.cfg, self.op.node_name)
+
+ def DeclareLocks(self, level):
+ if level == locking.LEVEL_INSTANCE:
+ # Lock instances optimistically, needs verification once node and group
+ # locks have been acquired
+ self.needed_locks[locking.LEVEL_INSTANCE] = \
+ set(i.name for i in self._DetermineInstances())
+
+ elif level == locking.LEVEL_NODEGROUP:
+ # Lock node groups for all potential target nodes optimistically, needs
+ # verification once nodes have been acquired
+ self.needed_locks[locking.LEVEL_NODEGROUP] = \
+ self.cfg.GetNodeGroupsFromNodes(self.lock_nodes)
+
+ elif level == locking.LEVEL_NODE:
+ self.needed_locks[locking.LEVEL_NODE] = self.lock_nodes
+
+ def CheckPrereq(self):
+ # Verify locks
+ owned_instances = self.owned_locks(locking.LEVEL_INSTANCE)
+ owned_nodes = self.owned_locks(locking.LEVEL_NODE)
+ owned_groups = self.owned_locks(locking.LEVEL_NODEGROUP)
+
+ need_nodes = self._DetermineNodes()
+
+ if not owned_nodes.issuperset(need_nodes):
+ raise errors.OpPrereqError("Nodes in same group as '%s' changed since"
+ " locks were acquired, current nodes are"
+ " are '%s', used to be '%s'; retry the"
+ " operation" %
+ (self.op.node_name,
+ utils.CommaJoin(need_nodes),
+ utils.CommaJoin(owned_nodes)),
+ errors.ECODE_STATE)
+
+ wanted_groups = self.cfg.GetNodeGroupsFromNodes(owned_nodes)
+ if owned_groups != wanted_groups:
+ raise errors.OpExecError("Node groups changed since locks were acquired,"
+ " current groups are '%s', used to be '%s';"
+ " retry the operation" %
+ (utils.CommaJoin(wanted_groups),
+ utils.CommaJoin(owned_groups)))
+
+ # Determine affected instances
+ self.instances = self._DetermineInstances()
+ self.instance_names = [i.name for i in self.instances]
+
+ if set(self.instance_names) != owned_instances:
+ raise errors.OpExecError("Instances on node '%s' changed since locks"
+ " were acquired, current instances are '%s',"
+ " used to be '%s'; retry the operation" %
+ (self.op.node_name,
+ utils.CommaJoin(self.instance_names),
+ utils.CommaJoin(owned_instances)))
+
+ if self.instance_names:
+ self.LogInfo("Evacuating instances from node '%s': %s",
+ self.op.node_name,
+ utils.CommaJoin(utils.NiceSort(self.instance_names)))
+ else:
+ self.LogInfo("No instances to evacuate from node '%s'",
+ self.op.node_name)
+
+ if self.op.remote_node is not None:
+ for i in self.instances:
+ if i.primary_node == self.op.remote_node:
+ raise errors.OpPrereqError("Node %s is the primary node of"
+ " instance %s, cannot use it as"
+ " secondary" %
+ (self.op.remote_node, i.name),
+ errors.ECODE_INVAL)
+
+ def Exec(self, feedback_fn):
+ assert (self.op.iallocator is not None) ^ (self.op.remote_node is not None)
+
+ if not self.instance_names:
+ # No instances to evacuate
+ jobs = []
+
+ elif self.op.iallocator is not None:
+ # TODO: Implement relocation to other group
+ evac_mode = self._MODE2IALLOCATOR[self.op.mode]
+ req = iallocator.IAReqNodeEvac(evac_mode=evac_mode,
+ instances=list(self.instance_names))
+ ial = iallocator.IAllocator(self.cfg, self.rpc, req)
+
+ ial.Run(self.op.iallocator)
+
+ if not ial.success:
+ raise errors.OpPrereqError("Can't compute node evacuation using"
+ " iallocator '%s': %s" %
+ (self.op.iallocator, ial.info),
+ errors.ECODE_NORES)
+
+ jobs = _LoadNodeEvacResult(self, ial.result, self.op.early_release, True)
+
+ elif self.op.remote_node is not None:
+ assert self.op.mode == constants.NODE_EVAC_SEC
+ jobs = [
+ [opcodes.OpInstanceReplaceDisks(instance_name=instance_name,
+ remote_node=self.op.remote_node,
+ disks=[],
+ mode=constants.REPLACE_DISK_CHG,
+ early_release=self.op.early_release)]
+ for instance_name in self.instance_names]
+
+ else:
+ raise errors.ProgrammerError("No iallocator or remote node")
+
+ return ResultWithJobs(jobs)
+
+
+def _SetOpEarlyRelease(early_release, op):
+ """Sets C{early_release} flag on opcodes if available.
+
+ """
+ try:
+ op.early_release = early_release
+ except AttributeError:
+ assert not isinstance(op, opcodes.OpInstanceReplaceDisks)
+
+ return op
+
+
+def _NodeEvacDest(use_nodes, group, nodes):
+ """Returns group or nodes depending on caller's choice.
+
+ """
+ if use_nodes:
+ return utils.CommaJoin(nodes)
+ else:
+ return group
+
+
+def _LoadNodeEvacResult(lu, alloc_result, early_release, use_nodes):
+ """Unpacks the result of change-group and node-evacuate iallocator requests.
+
+ Iallocator modes L{constants.IALLOCATOR_MODE_NODE_EVAC} and
+ L{constants.IALLOCATOR_MODE_CHG_GROUP}.
+
+ @type lu: L{LogicalUnit}
+ @param lu: Logical unit instance
+ @type alloc_result: tuple/list
+ @param alloc_result: Result from iallocator
+ @type early_release: bool
+ @param early_release: Whether to release locks early if possible
+ @type use_nodes: bool
+ @param use_nodes: Whether to display node names instead of groups
+
+ """
+ (moved, failed, jobs) = alloc_result
+
+ if failed:
+ failreason = utils.CommaJoin("%s (%s)" % (name, reason)
+ for (name, reason) in failed)
+ lu.LogWarning("Unable to evacuate instances %s", failreason)
+ raise errors.OpExecError("Unable to evacuate instances %s" % failreason)
+
+ if moved:
+ lu.LogInfo("Instances to be moved: %s",
+ utils.CommaJoin("%s (to %s)" %
+ (name, _NodeEvacDest(use_nodes, group, nodes))
+ for (name, group, nodes) in moved))
+
+ return [map(compat.partial(_SetOpEarlyRelease, early_release),
+ map(opcodes.OpCode.LoadOpCode, ops))
+ for ops in jobs]
+
+
+def _DiskSizeInBytesToMebibytes(lu, size):
+ """Converts a disk size in bytes to mebibytes.
+
+ Warns and rounds up if the size isn't an even multiple of 1 MiB.
+
+ """
+ (mib, remainder) = divmod(size, 1024 * 1024)
+
+ if remainder != 0:
+ lu.LogWarning("Disk size is not an even multiple of 1 MiB; rounding up"
+ " to not overwrite existing data (%s bytes will not be"
+ " wiped)", (1024 * 1024) - remainder)
+ mib += 1
+
+ return mib
+
+
+class LUInstanceGrowDisk(LogicalUnit):
+ """Grow a disk of an instance.
+
+ """