---
 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

Reply via email to