From b989b9d927a07f32a16d310d6a730b1969e2b50e Mon Sep 17 00:00:00 2001
From: Ken Wehr <ksw@google.com>
Date: Wed, 21 Oct 2009 12:15:47 -0400
Subject: [PATCH] Adding '--no-ssh-init' option to 'gnt-cluster init'.

Allows the initialization of a cluster without the creation or distribution
of SSH key pairs. Includes changes for LeaveCluster and RPC.

Signed-off-by: Ken Wehr <ksw@google.com>
Signed-off-by: Guido Trotter <ultrotter@google.com>
Reviewed-by: Guido Trotter <ultrotter@google.com>
---
 NEWS                 |  1 +
 daemons/ganeti-noded |  2 +-
 lib/backend.py       | 19 +++++++++++--------
 lib/bootstrap.py     | 10 +++++++---
 lib/cli.py           |  5 +++++
 lib/cmdlib.py        | 43 +++++++++++++++++++++++++------------------
 lib/objects.py       |  4 ++++
 lib/rpc.py           |  5 +++--
 man/gnt-cluster.sgml | 18 ++++++++++++++++--
 scripts/gnt-cluster  |  4 +++-
 10 files changed, 76 insertions(+), 35 deletions(-)

diff --git a/NEWS b/NEWS
index 8d7e2701e..1d7fdb8cb 100644
--- a/NEWS
+++ b/NEWS
@@ -85,6 +85,7 @@ Details
 - Reimplemented bash completion script to be more complete
 - Improved burnin
 - Added option to specify maximum timeout on instance shutdown
+- Added ``--no-ssh-init`` option to ``gnt-cluster init``
 
 
 Version 2.0.4
diff --git a/daemons/ganeti-noded b/daemons/ganeti-noded
index 3eaf7efc7..0486dd1f5 100755
--- a/daemons/ganeti-noded
+++ b/daemons/ganeti-noded
@@ -593,7 +593,7 @@ class NodeHttpServer(http.server.HttpServer):
     """Cleanup after leaving a cluster.
 
     """
-    return backend.LeaveCluster()
+    return backend.LeaveCluster(params[0])
 
   @staticmethod
   def perspective_node_volumes(params):
diff --git a/lib/backend.py b/lib/backend.py
index 8c906c9e6..e6eefec1c 100644
--- a/lib/backend.py
+++ b/lib/backend.py
@@ -351,7 +351,7 @@ def AddNode(dsa, dsapub, rsa, rsapub, sshkey, sshpub):
   utils.RunCmd([constants.SSH_INITD_SCRIPT, "restart"])
 
 
-def LeaveCluster():
+def LeaveCluster(modify_ssh_setup):
   """Cleans up and remove the current node.
 
   This function cleans up and prepares the current node to be removed
@@ -361,19 +361,22 @@ def LeaveCluster():
   L{errors.QuitGanetiException} which is used as a special case to
   shutdown the node daemon.
 
+  @param modify_ssh_setup: boolean
+
   """
   _CleanDirectory(constants.DATA_DIR)
   JobQueuePurge()
 
-  try:
-    priv_key, pub_key, auth_keys = ssh.GetUserFiles(constants.GANETI_RUNAS)
+  if modify_ssh_setup:
+    try:
+      priv_key, pub_key, auth_keys = ssh.GetUserFiles(constants.GANETI_RUNAS)
 
-    utils.RemoveAuthorizedKey(auth_keys, utils.ReadFile(pub_key))
+      utils.RemoveAuthorizedKey(auth_keys, utils.ReadFile(pub_key))
 
-    utils.RemoveFile(priv_key)
-    utils.RemoveFile(pub_key)
-  except errors.OpExecError:
-    logging.exception("Error while processing ssh files")
+      utils.RemoveFile(priv_key)
+      utils.RemoveFile(pub_key)
+    except errors.OpExecError:
+      logging.exception("Error while processing ssh files")
 
   try:
     utils.RemoveFile(constants.HMAC_CLUSTER_KEY)
diff --git a/lib/bootstrap.py b/lib/bootstrap.py
index 190cc261d..8e6d19b23 100644
--- a/lib/bootstrap.py
+++ b/lib/bootstrap.py
@@ -138,7 +138,7 @@ def InitCluster(cluster_name, mac_prefix,
                 master_netdev, file_storage_dir, candidate_pool_size,
                 secondary_ip=None, vg_name=None, beparams=None,
                 nicparams=None, hvparams=None, enabled_hypervisors=None,
-                modify_etc_hosts=True):
+                modify_etc_hosts=True, modify_ssh_setup=True):
   """Initialise the cluster.
 
   @type candidate_pool_size: int
@@ -250,7 +250,8 @@ def InitCluster(cluster_name, mac_prefix,
   if modify_etc_hosts:
     utils.AddHostToEtcHosts(hostname.name)
 
-  _InitSSHSetup()
+  if modify_ssh_setup:
+    _InitSSHSetup()
 
   now = time.time()
 
@@ -273,6 +274,7 @@ def InitCluster(cluster_name, mac_prefix,
     hvparams=hvparams,
     candidate_pool_size=candidate_pool_size,
     modify_etc_hosts=modify_etc_hosts,
+    modify_ssh_setup=modify_ssh_setup,
     ctime=now,
     mtime=now,
     uuid=utils.NewUUID(),
@@ -335,11 +337,13 @@ def FinalizeClusterDestroy(master):
   begun in cmdlib.LUDestroyOpcode.
 
   """
+  cfg = config.ConfigWriter()
+  modify_ssh_setup = cfg.GetClusterInfo().modify_ssh_setup
   result = rpc.RpcRunner.call_node_stop_master(master, True)
   msg = result.fail_msg
   if msg:
     logging.warning("Could not disable the master role: %s" % msg)
-  result = rpc.RpcRunner.call_node_leave_cluster(master)
+  result = rpc.RpcRunner.call_node_leave_cluster(master, modify_ssh_setup)
   msg = result.fail_msg
   if msg:
     logging.warning("Could not shutdown the node daemon and cleanup"
diff --git a/lib/cli.py b/lib/cli.py
index eff591ef4..e79f6c633 100644
--- a/lib/cli.py
+++ b/lib/cli.py
@@ -84,6 +84,7 @@ __all__ = [
   "NOIPCHECK_OPT",
   "NOLVM_STORAGE_OPT",
   "NOMODIFY_ETCHOSTS_OPT",
+  "NOMODIFY_SSH_SETUP_OPT",
   "NONICS_OPT",
   "NONLIVE_OPT",
   "NONPLUS1_OPT",
@@ -794,6 +795,10 @@ NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
                                    help="Don't modify /etc/hosts",
                                    action="store_false", default=True)
 
+NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
+                                    help="Don't initialize SSH keys",
+                                    action="store_false", default=True)
+
 ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
                              help="Enable parseable error messages",
                              action="store_true", default=False)
diff --git a/lib/cmdlib.py b/lib/cmdlib.py
index 3d0d8aaa0..63378717f 100644
--- a/lib/cmdlib.py
+++ b/lib/cmdlib.py
@@ -867,6 +867,7 @@ class LUDestroyCluster(LogicalUnit):
 
     """
     master = self.cfg.GetMasterNode()
+    modify_ssh_setup = self.cfg.GetClusterInfo().modify_ssh_setup
 
     # Run post hooks on master node before it's removed
     hm = self.proc.hmclass(self.rpc.call_hooks_runner, self)
@@ -877,9 +878,12 @@ class LUDestroyCluster(LogicalUnit):
 
     result = self.rpc.call_node_stop_master(master, False)
     result.Raise("Could not disable the master role")
-    priv_key, pub_key, _ = ssh.GetUserFiles(constants.GANETI_RUNAS)
-    utils.CreateBackup(priv_key)
-    utils.CreateBackup(pub_key)
+
+    if modify_ssh_setup:
+      priv_key, pub_key, _ = ssh.GetUserFiles(constants.GANETI_RUNAS)
+      utils.CreateBackup(priv_key)
+      utils.CreateBackup(pub_key)
+
     return master
 
 
@@ -2340,6 +2344,8 @@ class LURemoveNode(LogicalUnit):
     logging.info("Stopping the node daemon and removing configs from node %s",
                  node.name)
 
+    modify_ssh_setup = self.cfg.GetClusterInfo().modify_ssh_setup
+
     # Promote nodes to master candidate as needed
     _AdjustCandidatePool(self, exceptions=[node.name])
     self.context.RemoveNode(node.name)
@@ -2351,7 +2357,7 @@ class LURemoveNode(LogicalUnit):
     except:
       self.LogWarning("Errors occurred running hooks on %s" % node.name)
 
-    result = self.rpc.call_node_leave_cluster(node.name)
+    result = self.rpc.call_node_leave_cluster(node.name, modify_ssh_setup)
     msg = result.fail_msg
     if msg:
       self.LogWarning("Errors encountered on the remote node while leaving"
@@ -2900,20 +2906,21 @@ class LUAddNode(LogicalUnit):
                                (constants.PROTOCOL_VERSION, result.payload))
 
     # setup ssh on node
-    logging.info("Copy ssh key to node %s", node)
-    priv_key, pub_key, _ = ssh.GetUserFiles(constants.GANETI_RUNAS)
-    keyarray = []
-    keyfiles = [constants.SSH_HOST_DSA_PRIV, constants.SSH_HOST_DSA_PUB,
-                constants.SSH_HOST_RSA_PRIV, constants.SSH_HOST_RSA_PUB,
-                priv_key, pub_key]
-
-    for i in keyfiles:
-      keyarray.append(utils.ReadFile(i))
-
-    result = self.rpc.call_node_add(node, keyarray[0], keyarray[1],
-                                    keyarray[2],
-                                    keyarray[3], keyarray[4], keyarray[5])
-    result.Raise("Cannot transfer ssh keys to the new node")
+    if self.cfg.GetClusterInfo().modify_ssh_setup:
+      logging.info("Copy ssh key to node %s", node)
+      priv_key, pub_key, _ = ssh.GetUserFiles(constants.GANETI_RUNAS)
+      keyarray = []
+      keyfiles = [constants.SSH_HOST_DSA_PRIV, constants.SSH_HOST_DSA_PUB,
+                  constants.SSH_HOST_RSA_PRIV, constants.SSH_HOST_RSA_PUB,
+                  priv_key, pub_key]
+
+      for i in keyfiles:
+        keyarray.append(utils.ReadFile(i))
+
+      result = self.rpc.call_node_add(node, keyarray[0], keyarray[1],
+                                      keyarray[2], keyarray[3], keyarray[4],
+                                      keyarray[5])
+      result.Raise("Cannot transfer ssh keys to the new node")
 
     # Add node to our /etc/hosts, and add key to known_hosts
     if self.cfg.GetClusterInfo().modify_etc_hosts:
diff --git a/lib/objects.py b/lib/objects.py
index 510518ab1..969901c51 100644
--- a/lib/objects.py
+++ b/lib/objects.py
@@ -825,6 +825,7 @@ class Cluster(TaggableObject):
     "nicparams",
     "candidate_pool_size",
     "modify_etc_hosts",
+    "modify_ssh_setup",
     ] + _TIMESTAMPS + _UUID
 
   def UpgradeConfig(self):
@@ -850,6 +851,9 @@ class Cluster(TaggableObject):
     if self.modify_etc_hosts is None:
       self.modify_etc_hosts = True
 
+    if self.modify_ssh_setup is None:
+      self.modify_ssh_setup = True
+
     # default_bridge is no longer used it 2.1. The slot is left there to
     # support auto-upgrading, but will be removed in 2.2
     if self.default_bridge is not None:
diff --git a/lib/rpc.py b/lib/rpc.py
index 8701945ab..780f601fc 100644
--- a/lib/rpc.py
+++ b/lib/rpc.py
@@ -1044,7 +1044,7 @@ class RpcRunner(object):
     return self._SingleNodeCall(node, "export_remove", [export])
 
   @classmethod
-  def call_node_leave_cluster(cls, node):
+  def call_node_leave_cluster(cls, node, modify_ssh_setup):
     """Requests a node to clean the cluster information it has.
 
     This will remove the configuration information from the ganeti data
@@ -1053,7 +1053,8 @@ class RpcRunner(object):
     This is a single-node call.
 
     """
-    return cls._StaticSingleNodeCall(node, "node_leave_cluster", [])
+    return cls._StaticSingleNodeCall(node, "node_leave_cluster",
+                                     [modify_ssh_setup])
 
   def call_node_volumes(self, node_list):
     """Gets all volumes on node(s).
diff --git a/man/gnt-cluster.sgml b/man/gnt-cluster.sgml
index 531314704..afd1cb208 100644
--- a/man/gnt-cluster.sgml
+++ b/man/gnt-cluster.sgml
@@ -221,6 +221,10 @@
         <sbr>
         <arg>--no-lvm-storage</arg>
         <sbr>
+        <arg>--no-etc-hosts</arg>
+        <sbr>
+        <arg>--no-ssh-init</arg>
+        <sbr>
         <arg>--file-storage-dir <replaceable>dir</replaceable></arg>
         <sbr>
         <arg>--enabled-hypervisors <replaceable>hypervisors</replaceable></arg>
@@ -296,13 +300,23 @@
       </para>
 
       <para>
-        The <option>--no-lvm-storage</option> allows you to initialize the
-        cluster without lvm support. This means that only instances using
+        The <option>--no-lvm-storage</option> option allows you to initialize
+        the cluster without lvm support. This means that only instances using
         files as storage backend will be possible to create. Once the cluster
         is initialized you can change this setup with the
         <command>modify</command> command.
       </para>
 
+      <para>
+        The <option>--no-etc-hosts</option> option allows you to initialize the
+        cluster without modifying the <filename>/etc/hosts</filename> file.
+      </para>
+
+      <para>
+        The <option>--no-ssh-init</option> option allows you to initialize the
+        cluster without creating or distributing SSH key pairs.
+      </para>
+
       <para>
         The <option>--file-storage-dir</option> option allows you
         set the directory to use for storing the instance disk
diff --git a/scripts/gnt-cluster b/scripts/gnt-cluster
index a57b9a0ea..555e72ed7 100755
--- a/scripts/gnt-cluster
+++ b/scripts/gnt-cluster
@@ -99,6 +99,7 @@ def InitCluster(opts, args):
                         nicparams=nicparams,
                         candidate_pool_size=opts.candidate_pool_size,
                         modify_etc_hosts=opts.modify_etc_hosts,
+                        modify_ssh_setup=opts.modify_ssh_setup,
                         )
   op = opcodes.OpPostInitCluster()
   SubmitOpCode(op)
@@ -600,7 +601,8 @@ commands = {
     InitCluster, [ArgHost(min=1, max=1)],
     [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, GLOBAL_FILEDIR_OPT,
      HVLIST_OPT, MAC_PREFIX_OPT, MASTER_NETDEV_OPT, NIC_PARAMS_OPT,
-     NOLVM_STORAGE_OPT, NOMODIFY_ETCHOSTS_OPT, SECONDARY_IP_OPT, VG_NAME_OPT],
+     NOLVM_STORAGE_OPT, NOMODIFY_ETCHOSTS_OPT, NOMODIFY_SSH_SETUP_OPT,
+     SECONDARY_IP_OPT, VG_NAME_OPT],
     "[opts...] <cluster_name>", "Initialises a new cluster configuration"),
   'destroy': (
     DestroyCluster, ARGS_NONE, [YES_DOIT_OPT],
-- 
GitLab