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