From 07bd8a51385b2909855148ede04e04275cd4c228 Mon Sep 17 00:00:00 2001
From: Iustin Pop <iustin@google.com>
Date: Tue, 18 Sep 2007 12:42:31 +0000
Subject: [PATCH] Implement cluster rename operation

This patch adds a new OpCode (and corresponding LU) that implements the
cluster rename functionality.

This is done by shutting down the master role, making the needed sstore
modifications and distributing the changed files to all nodes, and then
re-enabling the master role.

The modification to the man page of gnt-cluster also moves the section
on gnt-cluster destroy in order to correct alphabetical ordering.

Reviewed-by: imsnah
---
 lib/cmdlib.py        | 81 ++++++++++++++++++++++++++++++++++++++++++++
 lib/mcpu.py          |  1 +
 lib/opcodes.py       |  8 +++++
 man/gnt-cluster.sgml | 25 ++++++++++++--
 scripts/gnt-cluster  | 25 ++++++++++++++
 5 files changed, 137 insertions(+), 3 deletions(-)

diff --git a/lib/cmdlib.py b/lib/cmdlib.py
index d2f159b83..4da27384c 100644
--- a/lib/cmdlib.py
+++ b/lib/cmdlib.py
@@ -944,6 +944,87 @@ class LUVerifyCluster(NoHooksLU):
     return int(bad)
 
 
+class LURenameCluster(LogicalUnit):
+  """Rename the cluster.
+
+  """
+  HPATH = "cluster-rename"
+  HTYPE = constants.HTYPE_CLUSTER
+  _OP_REQP = ["name"]
+
+  def BuildHooksEnv(self):
+    """Build hooks env.
+
+    """
+    env = {
+      "NEW_NAME": self.op.name,
+      }
+    mn = self.sstore.GetMasterNode()
+    return env, [mn], [mn]
+
+  def CheckPrereq(self):
+    """Verify that the passed name is a valid one.
+
+    """
+    hostname = utils.LookupHostname(self.op.name)
+    if not hostname:
+      raise errors.OpPrereqError("Cannot resolve the new cluster name ('%s')" %
+                                 self.op.name)
+
+    new_name = hostname["hostname"]
+    self.ip = new_ip = hostname["ip"]
+    old_name = self.sstore.GetClusterName()
+    old_ip = self.sstore.GetMasterIP()
+    if new_name == old_name and new_ip == old_ip:
+      raise errors.OpPrereqError("Neither the name nor the IP address of the"
+                                 " cluster has changed")
+    if new_ip != old_ip:
+      result = utils.RunCmd(["fping", "-q", new_ip])
+      if not result.failed:
+        raise errors.OpPrereqError("The given cluster IP address (%s) is"
+                                   " reachable on the network. Aborting." %
+                                   new_ip)
+
+    self.op.name = new_name
+
+  def Exec(self, feedback_fn):
+    """Rename the cluster.
+
+    """
+    clustername = self.op.name
+    ip = self.ip
+    ss = self.sstore
+
+    # shutdown the master IP
+    master = ss.GetMasterNode()
+    if not rpc.call_node_stop_master(master):
+      raise errors.OpExecError("Could not disable the master role")
+
+    try:
+      # modify the sstore
+      ss.SetKey(ss.SS_MASTER_IP, ip)
+      ss.SetKey(ss.SS_CLUSTER_NAME, clustername)
+
+      # Distribute updated ss config to all nodes
+      myself = self.cfg.GetNodeInfo(master)
+      dist_nodes = self.cfg.GetNodeList()
+      if myself.name in dist_nodes:
+        dist_nodes.remove(myself.name)
+
+      logger.Debug("Copying updated ssconf data to all nodes")
+      for keyname in [ss.SS_CLUSTER_NAME, ss.SS_MASTER_IP]:
+        fname = ss.KeyToFilename(keyname)
+        result = rpc.call_upload_file(dist_nodes, fname)
+        for to_node in dist_nodes:
+          if not result[to_node]:
+            logger.Error("copy of file %s to node %s failed" %
+                         (fname, to_node))
+    finally:
+      if not rpc.call_node_start_master(master):
+        logger.Error("Could not re-enable the master role on the master,\n"
+                     "please restart manually.")
+
+
 def _WaitForSync(cfgw, instance, oneshot=False, unlock=False):
   """Sleep and poll for an instance's disk to sync.
 
diff --git a/lib/mcpu.py b/lib/mcpu.py
index 14be00932..2714588f7 100644
--- a/lib/mcpu.py
+++ b/lib/mcpu.py
@@ -49,6 +49,7 @@ class Processor(object):
     opcodes.OpVerifyCluster: cmdlib.LUVerifyCluster,
     opcodes.OpMasterFailover: cmdlib.LUMasterFailover,
     opcodes.OpDumpClusterConfig: cmdlib.LUDumpClusterConfig,
+    opcodes.OpRenameCluster: cmdlib.LURenameCluster,
     # node lu
     opcodes.OpAddNode: cmdlib.LUAddNode,
     opcodes.OpQueryNodes: cmdlib.LUQueryNodes,
diff --git a/lib/opcodes.py b/lib/opcodes.py
index 615f469be..d5b06ada7 100644
--- a/lib/opcodes.py
+++ b/lib/opcodes.py
@@ -98,6 +98,14 @@ class OpDumpClusterConfig(OpCode):
   __slots__ = []
 
 
+class OpRenameCluster(OpCode):
+  """Rename the cluster."""
+  OP_ID = "OP_CLUSTER_RENAME"
+  __slots__ = ["name"]
+
+
+# node opcodes
+
 class OpRemoveNode(OpCode):
   """Remove a node."""
   OP_ID = "OP_NODE_REMOVE"
diff --git a/man/gnt-cluster.sgml b/man/gnt-cluster.sgml
index c8e9ae5cb..f85a8b301 100644
--- a/man/gnt-cluster.sgml
+++ b/man/gnt-cluster.sgml
@@ -110,6 +110,15 @@
       current node to the two named nodes.
     </para>
 
+    <cmdsynopsis>
+      <command>destroy</command>
+    </cmdsynopsis>
+
+    <para>
+      Remove all configuration files related to the cluster, so that a
+      <command>gnt-cluster init</command> can be done again afterwards.
+    </para>
+
     <cmdsynopsis>
       <command>getmaster</command>
     </cmdsynopsis>
@@ -178,12 +187,22 @@
     </para>
 
     <cmdsynopsis>
-      <command>destroy</command>
+      <command>rename</command>
+      <arg>-f</arg>
+      <arg choice="req"><replaceable>name</replaceable></arg>
     </cmdsynopsis>
 
     <para>
-      Remove all configuration files related to the cluster, so that a
-      <command>gnt-cluster init</command> can be done again afterwards.
+      Renames the cluster and in the process updates the master IP
+      address to the one the new name resolves to. At least one of
+      either the name or the IP address must be different, otherwise
+      the operation will be aborted.
+    </para>
+
+    <para>
+      Note that since this command can be dangerous (especially when
+      run over SSH), the command will require confirmation unless run
+      with the <option>-f</option> option.
     </para>
 
     <cmdsynopsis>
diff --git a/scripts/gnt-cluster b/scripts/gnt-cluster
index 171f5f5c8..da6bfc749 100755
--- a/scripts/gnt-cluster
+++ b/scripts/gnt-cluster
@@ -64,6 +64,28 @@ def DestroyCluster(opts, args):
   return 0
 
 
+def RenameCluster(opts, args):
+  """Rename the cluster.
+
+  Args:
+    opts - class with options as members, we use force only
+    args - list of arguments, expected to be [new_name]
+
+  """
+  name = args[0]
+  if not opts.force:
+    usertext = ("This will rename the cluster to '%s'. If you are connected"
+                " over the network to the cluster name, the operation is very"
+                " dangerous as the IP address will be removed from the node"
+                " and the change may not go through. Continue?") % name
+    if not opts._ask_user(usertext):
+      return 1
+
+  op = opcodes.OpRenameCluster(name=name)
+  SubmitOpCode(op)
+  return 0
+
+
 def ShowClusterVersion(opts, args):
   """Write version of ganeti software to the standard output.
 
@@ -222,6 +244,9 @@ commands = {
                            action="store_true"),
               ],
               "", "Destroy cluster"),
+  'rename': (RenameCluster, ARGS_ONE, [DEBUG_OPT, FORCE_OPT],
+               "<new_name>",
+               "Renames the cluster"),
   'verify': (VerifyCluster, ARGS_NONE, [DEBUG_OPT],
              "", "Does a check on the cluster configuration"),
   'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT],
-- 
GitLab