---
doc/hooks.rst | 10 ++++
lib/cmdlib.py | 131 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
lib/mcpu.py | 1 +
lib/opcodes.py | 10 ++++
4 files changed, 152 insertions(+), 0 deletions(-)
diff --git a/doc/hooks.rst b/doc/hooks.rst
index b2f05ce..ecd6c9e 100644
--- a/doc/hooks.rst
+++ b/doc/hooks.rst
@@ -145,6 +145,16 @@ Changes a node's parameters.
:pre-execution: master node, the target node
:post-execution: master node, the target node
+OP_NODE_EVACUATE
+++++++++++++++++
+
+Relocate secondary instances from a node.
+
+:directory: node-evacuate
+:env. vars: NEW_SECONDARY, NODE_NAME
+:pre-execution: master node, target node
+:post-execution: master node, target node
+
Instance operations
~~~~~~~~~~~~~~~~~~~
diff --git a/lib/cmdlib.py b/lib/cmdlib.py
index 3ffecb8..e03a6b3 100644
--- a/lib/cmdlib.py
+++ b/lib/cmdlib.py
@@ -5199,6 +5199,137 @@ class LUReplaceDisks(LogicalUnit):
self.replacer.Exec()
+class LUEvacuateNode(LogicalUnit):
+ """Relocate the secondary instances from a node.
+
+ """
+ HPATH = "node-evacuate"
+ HTYPE = constants.HTYPE_NODE
+ _OP_REQP = ["node_name"]
+ REQ_BGL = False
+
+ def CheckArguments(self):
+ if not hasattr(self.op, "remote_node"):
+ self.op.remote_node = None
+ if not hasattr(self.op, "iallocator"):
+ self.op.iallocator = None
+
+ _DiskReplacer.CheckArguments(constants.REPLACE_DISK_CHG,
+ self.op.remote_node,
+ self.op.iallocator)
+
+ def ExpandNames(self):
+ self.op.node_name = self.cfg.ExpandNodeName(self.op.node_name)
+ if self.op.node_name is None:
+ raise errors.OpPrereqError("Node '%s' not known" % self.op.node_name)
+
+ self.needed_locks = {}
+
+ # Declare node locks
+ if self.op.iallocator is not None:
+ self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
+
+ elif self.op.remote_node is not None:
+ remote_node = self.cfg.ExpandNodeName(self.op.remote_node)
+ if remote_node is None:
+ raise errors.OpPrereqError("Node '%s' not known" %
+ self.op.remote_node)
+
+ self.op.remote_node = remote_node
+
+ # Warning: do not remove the locking of the new secondary here
+ # unless DRBD8.AddChildren is changed to work in parallel;
+ # currently it doesn't since parallel invocations of
+ # FindUnusedMinor will conflict
+ self.needed_locks[locking.LEVEL_NODE] = [remote_node]
+ self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
+
+ else:
+ raise errors.OpPrereqError("Invalid parameters")
+
+ # Create replacer objects for all secondary instances on this node
+ names = []
+ replacers = []
+
+ for inst in self._GetNodeSecondaryInstances(self.op.node_name):
+ logging.debug("Replacing disks for instance %s", inst.name)
+ names.append(inst.name)
+
+ replacer = _DiskReplacer(self, inst.name, constants.REPLACE_DISK_CHG,
+ self.op.iallocator, self.op.remote_node, [])
+ replacers.append(replacer)
+
+ self.instance_names = names
+ self.replacers = replacers
+
+ # Declare instance locks
+ self.needed_locks[locking.LEVEL_INSTANCE] = self.instance_names
+
+ def DeclareLocks(self, level):
+ # If we're not already locking all nodes in the set we have to declare the
+ # instance's primary/secondary nodes.
+ if (level == locking.LEVEL_NODE and
+ self.needed_locks[locking.LEVEL_NODE] is not locking.ALL_SET):
+ self._LockInstancesNodes()
+
+ def _GetNodeSecondaryInstances(self, node_name):
+ """Returns secondary instances on a node.
+
+ """
+ instances = []
+
+ for (_, inst) in self.cfg.GetAllInstancesInfo().iteritems():
+ if node_name in inst.secondary_nodes:
+ instances.append(inst)
+
+ return instances
+
+ def BuildHooksEnv(self):
+ """Build hooks env.
+
+ This runs on the master, the primary and all the secondaries.
+
+ """
+ env = {
+ "NODE_NAME": self.op.node_name,
+ }
+
+ nl = [self.cfg.GetMasterNode()]
+
+ if self.op.remote_node is not None:
+ env["NEW_SECONDARY"] = self.op.remote_node
+ nl.append(self.op.remote_node)
+
+ return (env, nl, nl)
+
+ def CheckPrereq(self):
+ """Check prerequisites.
+
+ """
+ for repl in self.replacers:
+ repl.CheckPrereq()
+
+ def Exec(self, feedback_fn):
+ """Execute disk replacement.
+
+ """
+ feedback_fn("Replacing secondary disks on %s for instance(s) %s" %
+ (self.op.node_name, ", ".join(self.instance_names)))
+
+ for idx, repl in enumerate(self.replacers):
+ feedback_fn("Replacing disks for %s (%s/%s)" %
+ (repl.instance.name, idx + 1, len(self.replacers)))
+ repl.Exec()
+
+ cur_inst_names = [inst.name for inst in
+ self._GetNodeSecondaryInstances(self.op.node_name)]
+ diff = set(cur_inst_names) - set(self.instance_names)
+ if diff:
+ feedback_fn(("The following instances' secondary node was changed "
+ " to %s during this operation: %s") %
+ (self.op.node_name, ", ".join(diff)))
+
+
class _DiskReplacer:
"""Replaces disks for an instance.
diff --git a/lib/mcpu.py b/lib/mcpu.py
index 78faa37..27e5ca3 100644
--- a/lib/mcpu.py
+++ b/lib/mcpu.py
@@ -57,6 +57,7 @@ class Processor(object):
opcodes.OpRemoveNode: cmdlib.LURemoveNode,
opcodes.OpSetNodeParams: cmdlib.LUSetNodeParams,
opcodes.OpPowercycleNode: cmdlib.LUPowercycleNode,
+ opcodes.OpEvacuateNode: cmdlib.LUEvacuateNode,
# instance lu
opcodes.OpCreateInstance: cmdlib.LUCreateInstance,
opcodes.OpReinstallInstance: cmdlib.LUReinstallInstance,
diff --git a/lib/opcodes.py b/lib/opcodes.py
index e7d34a8..e49d0be 100644
--- a/lib/opcodes.py
+++ b/lib/opcodes.py
@@ -350,6 +350,16 @@ class OpPowercycleNode(OpCode):
"force",
]
+
+class OpEvacuateNode(OpCode):
+ """Relocate secondary instances from a node."""
+ OP_ID = "OP_NODE_EVACUATE"
+ OP_DSC_FIELD = "node_name"
+ __slots__ = OpCode.__slots__ + [
+ "node_name", "remote_node", "iallocator",
+ ]
+
+
# instance opcodes
class OpCreateInstance(OpCode):
--
1.6.3.3