From decd5f45e3132d95b1664b407e3a885016157bb2 Mon Sep 17 00:00:00 2001 From: Iustin Pop <iustin@google.com> Date: Mon, 17 Sep 2007 11:52:38 +0000 Subject: [PATCH] Implement instance rename operation This patch adds support for instance rename operation at all remaining layers: RPC, OpCode/LU and CLI. Reviewed-by: imsnah --- daemons/ganeti-noded | 9 +++++ lib/backend.py | 58 +++++++++++++++++++++++++++++++ lib/cmdlib.py | 81 +++++++++++++++++++++++++++++++++++++++++++ lib/mcpu.py | 1 + lib/opcodes.py | 6 ++++ lib/rpc.py | 13 +++++++ man/gnt-instance.sgml | 21 +++++++++++ scripts/gnt-instance | 28 +++++++++++++-- 8 files changed, 215 insertions(+), 2 deletions(-) diff --git a/daemons/ganeti-noded b/daemons/ganeti-noded index ef6e182d9..e5afb3759 100755 --- a/daemons/ganeti-noded +++ b/daemons/ganeti-noded @@ -287,6 +287,15 @@ class ServerObject(pb.Avatar): inst = objects.ConfigObject.Loads(inst_s) return backend.AddOSToInstance(inst, os_disk, swap_disk) + @staticmethod + def perspective_instance_run_rename(params): + """Runs the OS rename script for an instance. + + """ + inst_s, old_name, os_disk, swap_disk = params + inst = objects.ConfigObject.Loads(inst_s) + return backend.RunRenameInstance(inst, old_name, os_disk, swap_disk) + @staticmethod def perspective_instance_os_import(params): """Run the import function of an OS onto a given instance. diff --git a/lib/backend.py b/lib/backend.py index 65135b71c..af4a4d80f 100644 --- a/lib/backend.py +++ b/lib/backend.py @@ -416,6 +416,64 @@ def AddOSToInstance(instance, os_disk, swap_disk): return True +def RunRenameInstance(instance, old_name, os_disk, swap_disk): + """Run the OS rename script for an instance. + + Args: + instance: the instance object + old_name: the old name of the instance + os_disk: the instance-visible name of the os device + swap_disk: the instance-visible name of the swap device + + """ + inst_os = OSFromDisk(instance.os) + + script = inst_os.rename_script + + os_device = instance.FindDisk(os_disk) + if os_device is None: + logger.Error("Can't find this device-visible name '%s'" % os_disk) + return False + + swap_device = instance.FindDisk(swap_disk) + if swap_device is None: + logger.Error("Can't find this device-visible name '%s'" % swap_disk) + return False + + real_os_dev = _RecursiveFindBD(os_device) + if real_os_dev is None: + raise errors.BlockDeviceError("Block device '%s' is not set up" % + str(os_device)) + real_os_dev.Open() + + real_swap_dev = _RecursiveFindBD(swap_device) + if real_swap_dev is None: + raise errors.BlockDeviceError("Block device '%s' is not set up" % + str(swap_device)) + real_swap_dev.Open() + + logfile = "%s/rename-%s-%s-%s-%d.log" % (constants.LOG_OS_DIR, instance.os, + old_name, + instance.name, int(time.time())) + if not os.path.exists(constants.LOG_OS_DIR): + os.mkdir(constants.LOG_OS_DIR, 0750) + + command = utils.BuildShellCmd("cd %s && %s -o %s -n %s -b %s -s %s &>%s", + inst_os.path, script, old_name, instance.name, + real_os_dev.dev_path, real_swap_dev.dev_path, + logfile) + + result = utils.RunCmd(command) + + if result.failed: + logger.Error("os create command '%s' returned error: %s" + " output: %s" % + (command, result.fail_reason, result.output)) + return False + + return True + + def _GetVGInfo(vg_name): """Get informations about the volume group. diff --git a/lib/cmdlib.py b/lib/cmdlib.py index cedcf9659..d2f159b83 100644 --- a/lib/cmdlib.py +++ b/lib/cmdlib.py @@ -2034,6 +2034,87 @@ class LUReinstallInstance(LogicalUnit): _ShutdownInstanceDisks(inst, self.cfg) +class LURenameInstance(LogicalUnit): + """Rename an instance. + + """ + HPATH = "instance-rename" + HTYPE = constants.HTYPE_INSTANCE + _OP_REQP = ["instance_name", "new_name"] + + def BuildHooksEnv(self): + """Build hooks env. + + This runs on master, primary and secondary nodes of the instance. + + """ + env = _BuildInstanceHookEnvByObject(self.instance) + env["INSTANCE_NEW_NAME"] = self.op.new_name + nl = ([self.sstore.GetMasterNode(), self.instance.primary_node] + + list(self.instance.secondary_nodes)) + return env, nl, nl + + def CheckPrereq(self): + """Check prerequisites. + + This checks that the instance is in the cluster and is not running. + + """ + instance = self.cfg.GetInstanceInfo( + self.cfg.ExpandInstanceName(self.op.instance_name)) + if instance is None: + raise errors.OpPrereqError("Instance '%s' not known" % + self.op.instance_name) + if instance.status != "down": + raise errors.OpPrereqError("Instance '%s' is marked to be up" % + self.op.instance_name) + remote_info = rpc.call_instance_info(instance.primary_node, instance.name) + if remote_info: + raise errors.OpPrereqError("Instance '%s' is running on the node %s" % + (self.op.instance_name, + instance.primary_node)) + self.instance = instance + + # new name verification + hostname1 = utils.LookupHostname(self.op.new_name) + if not hostname1: + raise errors.OpPrereqError("New instance name '%s' not found in dns" % + self.op.new_name) + + self.op.new_name = new_name = hostname1['hostname'] + if not getattr(self.op, "ignore_ip", False): + command = ["fping", "-q", hostname1['ip']] + result = utils.RunCmd(command) + if not result.failed: + raise errors.OpPrereqError("IP %s of instance %s already in use" % + (hostname1['ip'], new_name)) + + + def Exec(self, feedback_fn): + """Reinstall the instance. + + """ + inst = self.instance + old_name = inst.name + + self.cfg.RenameInstance(inst.name, self.op.new_name) + + # re-read the instance from the configuration after rename + inst = self.cfg.GetInstanceInfo(self.op.new_name) + + _StartInstanceDisks(self.cfg, inst, None) + try: + if not rpc.call_instance_run_rename(inst.primary_node, inst, old_name, + "sda", "sdb"): + msg = ("Could run OS rename script for instance %s\n" + "on node %s\n" + "(but the instance has been renamed in Ganeti)" % + (inst.name, inst.primary_node)) + logger.Error(msg) + finally: + _ShutdownInstanceDisks(inst, self.cfg) + + class LURemoveInstance(LogicalUnit): """Remove an instance. diff --git a/lib/mcpu.py b/lib/mcpu.py index 1a3c54694..14be00932 100644 --- a/lib/mcpu.py +++ b/lib/mcpu.py @@ -58,6 +58,7 @@ class Processor(object): opcodes.OpCreateInstance: cmdlib.LUCreateInstance, opcodes.OpReinstallInstance: cmdlib.LUReinstallInstance, opcodes.OpRemoveInstance: cmdlib.LURemoveInstance, + opcodes.OpRenameInstance: cmdlib.LURenameInstance, opcodes.OpActivateInstanceDisks: cmdlib.LUActivateInstanceDisks, opcodes.OpShutdownInstance: cmdlib.LUShutdownInstance, opcodes.OpStartupInstance: cmdlib.LUStartupInstance, diff --git a/lib/opcodes.py b/lib/opcodes.py index c53504ade..615f469be 100644 --- a/lib/opcodes.py +++ b/lib/opcodes.py @@ -145,6 +145,12 @@ class OpRemoveInstance(OpCode): __slots__ = ["instance_name"] +class OpRenameInstance(OpCode): + """Rename an instance.""" + OP_ID = "OP_INSTANCE_RENAME" + __slots__ = ["instance_name", "ignore_ip", "new_name"] + + class OpStartupInstance(OpCode): """Startup an instance.""" OP_ID = "OP_INSTANCE_STARTUP" diff --git a/lib/rpc.py b/lib/rpc.py index dac6f3899..bf6bdd44d 100644 --- a/lib/rpc.py +++ b/lib/rpc.py @@ -324,6 +324,19 @@ def call_instance_os_add(node, inst, osdev, swapdev): return c.getresult().get(node, False) +def call_instance_run_rename(node, inst, old_name, osdev, swapdev): + """Run the OS rename script for an instance. + + This is a single-node call. + + """ + params = [inst.Dumps(), old_name, osdev, swapdev] + c = Client("instance_run_rename", params) + c.connect(node) + c.run() + return c.getresult().get(node, False) + + def call_instance_info(node, instance): """Returns information about a single instance. diff --git a/man/gnt-instance.sgml b/man/gnt-instance.sgml index c2a3f5f17..fd4cf57ce 100644 --- a/man/gnt-instance.sgml +++ b/man/gnt-instance.sgml @@ -413,6 +413,27 @@ </para> </refsect3> + <refsect3> + <title>RENAME</title> + + <cmdsynopsis> + <command>rename</command> + <arg>--no-ip-check</arg> + <arg choice="req"><replaceable>instance</replaceable></arg> + <arg choice="req"><replaceable>new_name</replaceable></arg> + </cmdsynopsis> + + <para> + Renames the given instance. The instance must be stopped + when running this command. The requirements for the new name + are the same as for adding an instance: the new name must be + resolvable and the IP it resolves to must not be reachable + (in order to prevent duplicate IPs the next time the + instance is started). The IP test can be skipped if the + <option>--no-ip-check</option> option is passed. + </para> + </refsect3> + </refsect2> <refsect2> diff --git a/scripts/gnt-instance b/scripts/gnt-instance index 0f12d491c..51d44db9a 100755 --- a/scripts/gnt-instance +++ b/scripts/gnt-instance @@ -239,6 +239,22 @@ def RemoveInstance(opts, args): return 0 +def RenameInstance(opts, args): + """Reinstall an instance. + + Args: + opts - class with options as members + args - list containing two elements, the instance name and the new name + + """ + op = opcodes.OpRenameInstance(instance_name=args[0], + new_name=args[1], + ignore_ip=opts.ignore_ip) + SubmitOpCode(op) + + return 0 + + def ActivateDisks(opts, args): """Activate an instance's disks. @@ -270,7 +286,7 @@ def DeactivateDisks(opts, args): def StartupInstance(opts, args): - """Shutdown an instance. + """Startup an instance. Args: opts - class with options as members @@ -634,6 +650,14 @@ commands = { ], "-b disk -p port <instance>", "Removes a mirror from the instance"), + 'rename': (RenameInstance, ARGS_FIXED(2), + [DEBUG_OPT, + make_option("--no-ip-check", dest="ignore_ip", + help="Do not check that the IP of the new name" + " is alive", + default=False, action="store_true"), + ], + "<instance> <new_name>", "Rename the instance"), 'replace-disks': (ReplaceDisks, ARGS_ONE, [DEBUG_OPT, make_option("-n", "--new-secondary", dest="new_secondary", @@ -655,7 +679,7 @@ commands = { default=None, type="string", metavar="<ADDRESS>"), make_option("-b", "--bridge", dest="bridge", help="Bridge to connect this instance to", - default=None, type="string", metavar="<bridge>") + default=None, type="string", metavar="<bridge>"), ], "<instance>", "Alters the parameters of an instance"), 'shutdown': (ShutdownInstance, ARGS_ANY, -- GitLab