From e29e955076bc7a5d11fcb0e9f1396f863ac83f73 Mon Sep 17 00:00:00 2001 From: Iustin Pop <iustin@google.com> Date: Sun, 14 Mar 2010 16:38:58 +0100 Subject: [PATCH] Implement conversion from plain to drbd This patch adds a new mode to instance modify, the changing of the disk template. For now only plain to drbd conversion is supported, and the new secondary node must be specified manually (no iallocator support). The procedure for conversion works as follows: - a completely new disk template is created, matching the count, size and mode of the instance's current disks - we create manually (not via _CreateDisks) all the missing volumes - we rename on the primary the LVs to the new name - we create manually the DRBD devices Failures during the creation of volumes will leave orphan volumes. Failure during the rename might leave some disks renamed and some not, leading to an inconsistent instance. Once the disks are renamed, we update the instance information and wait for resync. Any failures of the DRBD sync must be manually handled (like a normal failure, e.g. by running replace-disks, etc.). Signed-off-by: Iustin Pop <iustin@google.com> Reviewed-by: Michael Hanselmann <hansmi@google.com> --- lib/cmdlib.py | 109 ++++++++++++++++++++++++++++++++++++++++++- lib/opcodes.py | 3 +- scripts/gnt-instance | 16 +++++-- 3 files changed, 123 insertions(+), 5 deletions(-) diff --git a/lib/cmdlib.py b/lib/cmdlib.py index 910f41d7e..dd06843e8 100644 --- a/lib/cmdlib.py +++ b/lib/cmdlib.py @@ -7733,8 +7733,12 @@ class LUSetInstanceParams(LogicalUnit): self.op.beparams = {} if not hasattr(self.op, 'hvparams'): self.op.hvparams = {} + if not hasattr(self.op, "disk_template"): + self.op.disk_template = None + if not hasattr(self.op, "remote_node"): + self.op.remote_node = None self.op.force = getattr(self.op, "force", False) - if not (self.op.nics or self.op.disks or + if not (self.op.nics or self.op.disks or self.op.disk_template or self.op.hvparams or self.op.beparams): raise errors.OpPrereqError("No changes submitted", errors.ECODE_INVAL) @@ -7781,6 +7785,19 @@ class LUSetInstanceParams(LogicalUnit): raise errors.OpPrereqError("Only one disk add or remove operation" " supported at a time", errors.ECODE_INVAL) + if self.op.disks and self.op.disk_template is not None: + raise errors.OpPrereqError("Disk template conversion and other disk" + " changes not supported at the same time", + errors.ECODE_INVAL) + + if self.op.disk_template: + _CheckDiskTemplate(self.op.disk_template) + if (self.op.disk_template in constants.DTS_NET_MIRROR and + self.op.remote_node is None): + raise errors.OpPrereqError("Changing the disk template to a mirrored" + " one requires specifying a secondary node", + errors.ECODE_INVAL) + # NIC validation nic_addremove = 0 for nic_op, nic_dict in self.op.nics: @@ -7843,6 +7860,9 @@ class LUSetInstanceParams(LogicalUnit): def DeclareLocks(self, level): if level == locking.LEVEL_NODE: self._LockInstancesNodes() + if self.op.disk_template and self.op.remote_node: + self.op.remote_node = _ExpandNodeName(self.cfg, self.op.remote_node) + self.needed_locks[locking.LEVEL_NODE].append(self.op.remote_node) def BuildHooksEnv(self): """Build hooks env. @@ -7892,6 +7912,8 @@ class LUSetInstanceParams(LogicalUnit): del args['nics'][-1] env = _BuildInstanceHookEnvByObject(self, self.instance, override=args) + if self.op.disk_template: + env["NEW_DISK_TEMPLATE"] = self.op.disk_template nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes) return env, nl, nl @@ -7945,6 +7967,25 @@ class LUSetInstanceParams(LogicalUnit): pnode = instance.primary_node nodelist = list(instance.all_nodes) + if self.op.disk_template: + if instance.disk_template == self.op.disk_template: + raise errors.OpPrereqError("Instance already has disk template %s" % + instance.disk_template, errors.ECODE_INVAL) + + if (instance.disk_template, + self.op.disk_template) not in self._DISK_CONVERSIONS: + raise errors.OpPrereqError("Unsupported disk template conversion from" + " %s to %s" % (instance.disk_template, + self.op.disk_template), + errors.ECODE_INVAL) + if self.op.disk_template in constants.DTS_NET_MIRROR: + _CheckNodeOnline(self, self.op.remote_node) + _CheckNodeNotDrained(self, self.op.remote_node) + disks = [{"size": d.size} for d in instance.disks] + required = _ComputeDiskSize(self.op.disk_template, disks) + _CheckNodesFreeDisk(self, [self.op.remote_node], required) + _CheckInstanceDown(self, instance, "cannot change disk template") + # hvparams processing if self.op.hvparams: i_hvdict, hv_new = self._GetUpdatedParams( @@ -8128,6 +8169,55 @@ class LUSetInstanceParams(LogicalUnit): return + def _ConvertPlainToDrbd(self, feedback_fn): + """Converts an instance from plain to drbd. + + """ + feedback_fn("Converting template to drbd") + instance = self.instance + pnode = instance.primary_node + snode = self.op.remote_node + + # create a fake disk info for _GenerateDiskTemplate + disk_info = [{"size": d.size, "mode": d.mode} for d in instance.disks] + new_disks = _GenerateDiskTemplate(self, self.op.disk_template, + instance.name, pnode, [snode], + disk_info, None, None, 0) + info = _GetInstanceInfoText(instance) + feedback_fn("Creating aditional volumes...") + # first, create the missing data and meta devices + for disk in new_disks: + # unfortunately this is... not too nice + _CreateSingleBlockDev(self, pnode, instance, disk.children[1], + info, True) + for child in disk.children: + _CreateSingleBlockDev(self, snode, instance, child, info, True) + # at this stage, all new LVs have been created, we can rename the + # old ones + feedback_fn("Renaming original volumes...") + rename_list = [(o, n.children[0].logical_id) + for (o, n) in zip(instance.disks, new_disks)] + result = self.rpc.call_blockdev_rename(pnode, rename_list) + result.Raise("Failed to rename original LVs") + + feedback_fn("Initializing DRBD devices...") + # all child devices are in place, we can now create the DRBD devices + for disk in new_disks: + for node in [pnode, snode]: + f_create = node == pnode + _CreateSingleBlockDev(self, node, instance, disk, info, f_create) + + # at this point, the instance has been modified + instance.disk_template = constants.DT_DRBD8 + instance.disks = new_disks + self.cfg.Update(instance, feedback_fn) + + # disks are created, waiting for sync + disk_abort = not _WaitForSync(self, instance) + if disk_abort: + raise errors.OpExecError("There are some degraded disks for" + " this instance, please cleanup manually") + def Exec(self, feedback_fn): """Modifies an instance. @@ -8192,6 +8282,20 @@ class LUSetInstanceParams(LogicalUnit): # change a given disk instance.disks[disk_op].mode = disk_dict['mode'] result.append(("disk.mode/%d" % disk_op, disk_dict['mode'])) + + if self.op.disk_template: + r_shut = _ShutdownInstanceDisks(self, instance) + if not r_shut: + raise errors.OpExecError("Cannot shutdow instance disks, unable to" + " proceed with disk template conversion") + mode = (instance.disk_template, self.op.disk_template) + try: + self._DISK_CONVERSIONS[mode](self, feedback_fn) + except: + self.cfg.ReleaseDRBDMinors(instance.name) + raise + result.append(("disk_template", self.op.disk_template)) + # NIC changes for nic_op, nic_dict in self.op.nics: if nic_op == constants.DDM_REMOVE: @@ -8236,6 +8340,9 @@ class LUSetInstanceParams(LogicalUnit): return result + _DISK_CONVERSIONS = { + (constants.DT_PLAIN, constants.DT_DRBD8): _ConvertPlainToDrbd, + } class LUQueryExports(NoHooksLU): """Query the exports list diff --git a/lib/opcodes.py b/lib/opcodes.py index 42ef83964..d7d3c5503 100644 --- a/lib/opcodes.py +++ b/lib/opcodes.py @@ -620,7 +620,8 @@ class OpSetInstanceParams(OpCode): __slots__ = [ "instance_name", "hvparams", "beparams", "force", - "nics", "disks", + "nics", "disks", "disk_template", + "remote_node", ] diff --git a/scripts/gnt-instance b/scripts/gnt-instance index fb9d58fed..45444f11a 100755 --- a/scripts/gnt-instance +++ b/scripts/gnt-instance @@ -1207,7 +1207,7 @@ def SetInstanceParams(opts, args): @return: the desired exit code """ - if not (opts.nics or opts.disks or + if not (opts.nics or opts.disks or opts.disk_template or opts.hvparams or opts.beparams): ToStderr("Please give at least one of the parameters.") return 1 @@ -1247,9 +1247,18 @@ def SetInstanceParams(opts, args): errors.ECODE_INVAL) disk_dict['size'] = utils.ParseUnit(disk_dict['size']) + if (opts.disk_template and + opts.disk_template in constants.DTS_NET_MIRROR and + not opts.node): + ToStderr("Changing the disk template to a mirrored one requires" + " specifying a secondary node") + return 1 + op = opcodes.OpSetInstanceParams(instance_name=args[0], nics=opts.nics, disks=opts.disks, + disk_template=opts.disk_template, + remote_node=opts.node, hvparams=opts.hvparams, beparams=opts.beparams, force=opts.force) @@ -1261,7 +1270,7 @@ def SetInstanceParams(opts, args): ToStdout("Modified instance %s", args[0]) for param, data in result: ToStdout(" - %-5s -> %s", param, data) - ToStdout("Please don't forget that these parameters take effect" + ToStdout("Please don't forget that most parameters take effect" " only at the next start of the instance.") return 0 @@ -1407,7 +1416,8 @@ commands = { "Replaces all disks for the instance"), 'modify': ( SetInstanceParams, ARGS_ONE_INSTANCE, - [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT], + [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT, + DISK_TEMPLATE_OPT, SINGLE_NODE_OPT], "<instance>", "Alters the parameters of an instance"), 'shutdown': ( GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()], -- GitLab