Commit c8a96ae7 authored by Iustin Pop's avatar Iustin Pop
Browse files

Recreate instance disks: allow changing nodes

This patch introduces the option of changing an instance's nodes when
doing the disk recreation. The rationale is that currently if an
instance lives on a node that has gone down and is marked offline,
it's not possible to re-create the disks and reinstall the instance on
a different node without hacking the config file.

Additionally, the LU now locks the instance's nodes (which was not
done before), as we most likely allocate new resources on them.
Signed-off-by: default avatarIustin Pop <>
Reviewed-by: default avatarRené Nussbaumer <>
parent 931413f6
......@@ -638,8 +638,17 @@ def RecreateDisks(opts, args):
opts.disks = []
if opts.node:
pnode, snode = SplitNodeOption(opts.node)
nodes = [pnode]
if snode is not None:
nodes = []
op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name,
SubmitOrSend(op, opts)
return 0
......@@ -1472,7 +1481,7 @@ commands = {
"[-f] <instance>", "Deactivate an instance's disks"),
'recreate-disks': (
"<instance>", "Recreate an instance's disks"),
'grow-disk': (
......@@ -5440,8 +5440,25 @@ class LUInstanceRecreateDisks(LogicalUnit):
REQ_BGL = False
def CheckArguments(self):
# normalise the disk list
self.op.disks = sorted(frozenset(self.op.disks))
def ExpandNames(self):
self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
if self.op.nodes:
self.op.nodes = [_ExpandNodeName(self.cfg, n) for n in self.op.nodes]
self.needed_locks[locking.LEVEL_NODE] = list(self.op.nodes)
self.needed_locks[locking.LEVEL_NODE] = []
def DeclareLocks(self, level):
if level == locking.LEVEL_NODE:
# if we replace the nodes, we only need to lock the old primary,
# otherwise we need to lock all nodes for disk re-creation
primary_only = bool(self.op.nodes)
def BuildHooksEnv(self):
"""Build hooks env.
......@@ -5462,12 +5479,31 @@ class LUInstanceRecreateDisks(LogicalUnit):
instance = self.cfg.GetInstanceInfo(self.op.instance_name)
assert instance is not None, \
"Cannot retrieve locked instance %s" % self.op.instance_name
_CheckNodeOnline(self, instance.primary_node)
if self.op.nodes:
if len(self.op.nodes) != len(instance.all_nodes):
raise errors.OpPrereqError("Instance %s currently has %d nodes, but"
" %d replacement nodes were specified" %
(, len(instance.all_nodes),
assert instance.disk_template != constants.DT_DRBD8 or \
len(self.op.nodes) == 2
assert instance.disk_template != constants.DT_PLAIN or \
len(self.op.nodes) == 1
primary_node = self.op.nodes[0]
primary_node = instance.primary_node
_CheckNodeOnline(self, primary_node)
if instance.disk_template == constants.DT_DISKLESS:
raise errors.OpPrereqError("Instance '%s' has no disks" %
self.op.instance_name, errors.ECODE_INVAL)
_CheckInstanceDown(self, instance, "cannot recreate disks")
# if we replace nodes *and* the old primary is offline, we don't
# check
assert instance.primary_node in self.needed_locks[locking.LEVEL_NODE]
old_pnode = self.cfg.GetNodeInfo(instance.primary_node)
if not (self.op.nodes and old_pnode.offline):
_CheckInstanceDown(self, instance, "cannot recreate disks")
if not self.op.disks:
self.op.disks = range(len(instance.disks))
......@@ -5476,18 +5512,39 @@ class LUInstanceRecreateDisks(LogicalUnit):
if idx >= len(instance.disks):
raise errors.OpPrereqError("Invalid disk index passed '%s'" % idx,
if self.op.disks != range(len(instance.disks)) and self.op.nodes:
raise errors.OpPrereqError("Can't recreate disks partially and"
" change the nodes at the same time",
self.instance = instance
def Exec(self, feedback_fn):
"""Recreate the disks.
# change primary node, if needed
if self.op.nodes:
self.instance.primary_node = self.op.nodes[0]
self.LogWarning("Changing the instance's nodes, you will have to"
" remove any disks left on the older nodes manually")
to_skip = []
for idx, _ in enumerate(self.instance.disks):
for idx, disk in enumerate(self.instance.disks):
if idx not in self.op.disks: # disk idx has not been passed in
# update secondaries for disks, if needed
if self.op.nodes:
if disk.dev_type == constants.LD_DRBD8:
# need to update the nodes
assert len(self.op.nodes) == 2
logical_id = list(disk.logical_id)
logical_id[0] = self.op.nodes[0]
logical_id[1] = self.op.nodes[1]
disk.logical_id = tuple(logical_id)
if self.op.nodes:
self.cfg.Update(self.instance, feedback_fn)
_CreateDisks(self, self.instance, to_skip=to_skip)
......@@ -952,6 +952,7 @@ class OpInstanceRecreateDisks(OpCode):
("disks", ht.EmptyList, ht.TListOf(ht.TPositiveInt)),
("nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString)),
......@@ -1329,7 +1329,8 @@ instance.
**recreate-disks** [--submit] [--disks=``indices``] {*instance*}
**recreate-disks** [--submit] [--disks=``indices``] [-n node1:[node2]]
Recreates the disks of the given instance, or only a subset of the
disks (if the option ``disks`` is passed, which must be a
......@@ -1340,6 +1341,15 @@ any of the given disks already exists, the operation will fail. While
this is suboptimal, recreate-disks should hopefully not be needed in
normal operation and as such the impact of this is low.
Optionally the instance's disks can be recreated on different
nodes. This can be useful if, for example, the original nodes of the
instance have gone down (and are marked offline), so we can't recreate
on the same nodes. To do this, pass the new node(s) via ``-n`` option,
with a syntax similar to the **add** command. The number of nodes
passed must equal the number of nodes that the instance currently
has. Note that changing nodes is only allowed for 'all disk'
replacement (when ``--disks`` is not passed).
The ``--submit`` option is used to send the job to the master daemon
but not wait for its completion. The job ID will be shown so that it
can be examined via **gnt-job info**.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment