On Mon, May 30, 2011 at 5:14 PM, Michael Hanselmann <[email protected]> wrote: > Signed-off-by: Michael Hanselmann <[email protected]> > --- > lib/cmdlib.py | 171 > ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ > lib/opcodes.py | 19 ++++++- > 2 files changed, 188 insertions(+), 2 deletions(-) > > diff --git a/lib/cmdlib.py b/lib/cmdlib.py > index 49f7eb5..fbec6a4 100644 > --- a/lib/cmdlib.py > +++ b/lib/cmdlib.py > @@ -9760,6 +9760,177 @@ class LUNodeEvacStrategy(NoHooksLU): > return result > > > +class LUNodeEvacuate(NoHooksLU): > + """Evacuates instances off a list of nodes. > + > + """ > + REQ_BGL = False > + > + def CheckArguments(self): > + _CheckIAllocatorOrNode(self, "iallocator", "remote_node") > + > + def ExpandNames(self): > + self.op.nodes = _GetWantedNodes(self, self.op.nodes) > + > + if self.op.remote_node is not None: > + self.op.remote_node = _ExpandNodeName(self.cfg, self.op.remote_node) > + > + if self.op.remote_node is not None: > + if self.op.remote_node in self.op.nodes: > + raise errors.OpPrereqError("Can not use evacuated node as a new" > + " secondary node", errors.ECODE_INVAL) > + > + if self.op.primary: > + raise errors.OpPrereqError("Without the use of an iallocator only" > + " secondary instances can be evacuated", > + errors.ECODE_INVAL) > + > + # Declare locks > + self.share_locks = dict.fromkeys(locking.LEVELS, 1) > + self.needed_locks = { > + locking.LEVEL_INSTANCE: [], > + locking.LEVEL_NODEGROUP: [], > + locking.LEVEL_NODE: [], > + } > + > + # Determine nodes to be locked > + self.lock_nodes = set(self.op.nodes) > + > + if self.op.remote_node is None: > + # Iallocator will choose any node(s) in the same groups > + > self.lock_nodes.update(self.cfg.GetNodeGroupMembersByNodes(self.op.nodes)) > + else: > + self.lock_nodes.add(self.op.remote_node) > + > + def _DetermineInstances(self): > + """Builds list of instances to operate on. > + > + """ > + if not self.op.primary: > + # Secondary instances only > + inst_fn = _GetNodeSecondaryInstances > + elif not self.op.secondary: > + # Primary instances only > + inst_fn = _GetNodePrimaryInstances > + assert self.op.remote_node is None, \ > + "Evacuating primary instances requires iallocator" > + else: > + # All instances > + inst_fn = _GetNodeInstances > + > + return itertools.chain(*(inst_fn(self.cfg, node) > + for node in self.op.nodes)) > + > + 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 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.glm.list_owned(locking.LEVEL_INSTANCE) > + owned_nodes = self.glm.list_owned(locking.LEVEL_NODE) > + owned_groups = self.glm.list_owned(locking.LEVEL_NODEGROUP) > + > + assert owned_nodes == self.lock_nodes > + > + wanted_groups = self.cfg.GetNodeGroupsFromNodes(owned_nodes) > + if owned_groups != wanted_groups: > + raise errors.OpExecError("Nodes changed groups since locks were > acquired," > + " current groups are '%s', used to be '%s'" % > + (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 nodes '%s' changed since locks" > + " were acquired, current instances are '%s'," > + " used to be '%s'" % > + (utils.CommaJoin(self.op.nodes), > + utils.CommaJoin(self.instance_names), > + utils.CommaJoin(owned_instances))) > + > + if self.instance_names: > + self.LogInfo("Evacuating instances from node(s) %s: %s", > + utils.CommaJoin(utils.NiceSort(self.op.nodes)), > + utils.CommaJoin(utils.NiceSort(self.instance_names))) > + else: > + self.LogInfo("No instances to evacuate from node(s) %s", > + utils.CommaJoin(self.op.nodes)) > + > + 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 > + ial = IAllocator(self.cfg, self.rpc, constants.IALLOCATOR_MODE_MRELOC, > + reloc_mode=constants.IALLOCATOR_MRELOC_KEEP, > + instances=list(self.instance_names), > + target_groups=[]) > + > + 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 = [[opcodes.OpCode.LoadOpCode(state) for state in jobset] > + for jobset in ial.result] > + > + # Set "early_release" flag on opcodes where available > + early_release = self.op.early_release > + for op in itertools.chain(*jobs): > + try: > + op.early_release = early_release > + except AttributeError: > + assert not isinstance(op, opcodes.OpInstanceReplaceDisks) > + > + elif self.op.remote_node is not None: > + assert not self.op.primary > + 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) > + > + > class LUInstanceGrowDisk(LogicalUnit): > """Grow a disk of an instance. > > diff --git a/lib/opcodes.py b/lib/opcodes.py > index 6a5f3be..8839eec 100644 > --- a/lib/opcodes.py > +++ b/lib/opcodes.py > @@ -112,6 +112,9 @@ _PGroupNodeParams = ("ndparams", None, ht.TMaybeDict, > _PQueryWhat = ("what", ht.NoDefault, ht.TElemOf(constants.QR_VIA_OP), > "Resource(s) to query for") > > +_PEarlyRelease = ("early_release", False, ht.TBool, > + "Whether to release locks as soon as possible") > + > _PIpCheckDoc = "Whether to ensure instance's IP address is inactive" > > #: Do not remember instance state changes > @@ -894,6 +897,19 @@ class OpNodeEvacStrategy(OpCode): > ] > > > +class OpNodeEvacuate(OpCode): > + """Evacuate instances off a number of nodes.""" > + OP_DSC_FIELD = "nodes" > + OP_PARAMS = [ > + _PEarlyRelease, > + ("nodes", ht.NoDefault, ht.TListOf(ht.TNonEmptyString), "Node names"), > + ("remote_node", None, ht.TMaybeString, "New secondary node"), > + ("iallocator", None, ht.TMaybeString, "Iallocator for computing > solution"), > + ("primary", True, ht.TBool, "Whether to evacuate primary instances"), > + ("secondary", True, ht.TBool, "Whether to evacuate secondary instances"), > + ] > + > + > # instance opcodes > > class OpInstanceCreate(OpCode): > @@ -1044,6 +1060,7 @@ class OpInstanceReplaceDisks(OpCode): > OP_DSC_FIELD = "instance_name" > OP_PARAMS = [ > _PInstanceName, > + _PEarlyRelease, > ("mode", ht.NoDefault, ht.TElemOf(constants.REPLACE_MODES), > "Replacement mode"), > ("disks", ht.EmptyList, ht.TListOf(ht.TPositiveInt), > @@ -1051,8 +1068,6 @@ class OpInstanceReplaceDisks(OpCode): > ("remote_node", None, ht.TMaybeString, "New secondary node"), > ("iallocator", None, ht.TMaybeString, > "Iallocator for deciding new secondary node"), > - ("early_release", False, ht.TBool, > - "Whether to release locks as soon as possible"), > ] > > > -- > 1.7.3.5
LGTM > >
