Merge branch 'stable-2.14' into stable-2.15
[ganeti-github.git] / lib / backend.py
index 0397ad5..4b523b6 100644 (file)
@@ -56,12 +56,12 @@ import random
 import re
 import shutil
 import signal
-import socket
 import stat
 import tempfile
 import time
 import zlib
-import copy
+import contextlib
+import collections
 
 from ganeti import errors
 from ganeti import http
@@ -86,8 +86,7 @@ from ganeti import ht
 from ganeti.storage.base import BlockDev
 from ganeti.storage.drbd import DRBD8
 from ganeti import hooksmaster
-from ganeti.rpc import transport
-from ganeti.rpc.errors import NoMasterError, TimeoutError
+import ganeti.metad as metad
 
 
 _BOOT_ID_PATH = "/proc/sys/kernel/random/boot_id"
@@ -567,6 +566,8 @@ def LeaveCluster(modify_ssh_setup):
       utils.RemoveFile(pub_key)
     except errors.OpExecError:
       logging.exception("Error while processing ssh files")
+    except IOError:
+      logging.exception("At least one SSH file was not accessible.")
 
   try:
     utils.RemoveFile(pathutils.CONFD_HMAC_KEY)
@@ -1454,37 +1455,93 @@ def AddNodeSshKey(node_uuid, node_name,
     to its {ganeti_pub_keys} file
 
   """
-  # assure that at least one of those flags is true, as the function would
-  # not do anything otherwise
-  assert (to_authorized_keys or to_public_keys or get_public_keys)
+  node_list = [SshAddNodeInfo(name=node_name, uuid=node_uuid,
+                              to_authorized_keys=to_authorized_keys,
+                              to_public_keys=to_public_keys,
+                              get_public_keys=get_public_keys)]
+  return AddNodeSshKeyBulk(node_list,
+                           potential_master_candidates,
+                           pub_key_file=pub_key_file,
+                           ssconf_store=ssconf_store,
+                           noded_cert_file=noded_cert_file,
+                           run_cmd_fn=run_cmd_fn)
+
+
+# Node info named tuple specifically for the use with AddNodeSshKeyBulk
+SshAddNodeInfo = collections.namedtuple(
+  "SshAddNodeInfo",
+  ["uuid",
+   "name",
+   "to_authorized_keys",
+   "to_public_keys",
+   "get_public_keys"])
+
+
+def AddNodeSshKeyBulk(node_list,
+                      potential_master_candidates,
+                      pub_key_file=pathutils.SSH_PUB_KEYS,
+                      ssconf_store=None,
+                      noded_cert_file=pathutils.NODED_CERT_FILE,
+                      run_cmd_fn=ssh.RunSshCmdWithStdin):
+  """Distributes a node's public SSH key across the cluster.
+
+  Note that this function should only be executed on the master node, which
+  then will copy the new node's key to all nodes in the cluster via SSH.
+
+  Also note: at least one of the flags C{to_authorized_keys},
+  C{to_public_keys}, and C{get_public_keys} has to be set to C{True} for
+  the function to actually perform any actions.
+
+  @type node_list: list of SshAddNodeInfo tuples
+  @param node_list: list of tuples containing the necessary node information for
+    adding their keys
+  @type potential_master_candidates: list of str
+  @param potential_master_candidates: list of node names of potential master
+    candidates; this should match the list of uuids in the public key file
+
+  """
+  # whether there are any keys to be added or retrieved at all
+  to_authorized_keys = any([node_info.to_authorized_keys for node_info in
+                            node_list])
+  to_public_keys = any([node_info.to_public_keys for node_info in
+                        node_list])
 
   if not ssconf_store:
     ssconf_store = ssconf.SimpleStore()
 
-  # Check and fix sanity of key file
-  keys_by_name = ssh.QueryPubKeyFile([node_name], key_file=pub_key_file)
-  keys_by_uuid = ssh.QueryPubKeyFile([node_uuid], key_file=pub_key_file)
+  for node_info in node_list:
+    # replacement not necessary for keys that are not supposed to be in the
+    # list of public keys
+    if not node_info.to_public_keys:
+      continue
+    # Check and fix sanity of key file
+    keys_by_name = ssh.QueryPubKeyFile([node_info.name], key_file=pub_key_file)
+    keys_by_uuid = ssh.QueryPubKeyFile([node_info.uuid], key_file=pub_key_file)
+
+    if (not keys_by_name or node_info.name not in keys_by_name) \
+        and (not keys_by_uuid or node_info.uuid not in keys_by_uuid):
+      raise errors.SshUpdateError(
+        "No keys found for the new node '%s' (UUID %s) in the list of public"
+        " SSH keys, neither for the name or the UUID" %
+        (node_info.name, node_info.uuid))
+    else:
+      if node_info.name in keys_by_name:
+        # Replace the name by UUID in the file as the name should only be used
+        # temporarily
+        ssh.ReplaceNameByUuid(node_info.uuid, node_info.name,
+                              error_fn=errors.SshUpdateError,
+                              key_file=pub_key_file)
 
-  if (not keys_by_name or node_name not in keys_by_name) \
-      and (not keys_by_uuid or node_uuid not in keys_by_uuid):
-    raise errors.SshUpdateError(
-      "No keys found for the new node '%s' (UUID %s) in the list of public"
-      " SSH keys, neither for the name or the UUID" % (node_name, node_uuid))
-  else:
-    if node_name in keys_by_name:
-      keys_by_uuid = {}
-      # Replace the name by UUID in the file as the name should only be used
-      # temporarily
-      ssh.ReplaceNameByUuid(node_uuid, node_name,
-                            error_fn=errors.SshUpdateError,
-                            key_file=pub_key_file)
-      keys_by_uuid[node_uuid] = keys_by_name[node_name]
+  # Retrieve updated map of UUIDs to keys
+  keys_by_uuid = ssh.QueryPubKeyFile(
+      [node_info.uuid for node_info in node_list], key_file=pub_key_file)
 
   # Update the master node's key files
-  if to_authorized_keys:
-    (auth_key_file, _) = \
-      ssh.GetAllUserFiles(constants.SSH_LOGIN_USER, mkdir=False, dircheck=False)
-    ssh.AddAuthorizedKeys(auth_key_file, keys_by_uuid[node_uuid])
+  (auth_key_file, _) = \
+    ssh.GetAllUserFiles(constants.SSH_LOGIN_USER, mkdir=False, dircheck=False)
+  for node_info in node_list:
+    if node_info.to_authorized_keys:
+      ssh.AddAuthorizedKeys(auth_key_file, keys_by_uuid[node_info.uuid])
 
   base_data = {}
   _InitSshUpdateData(base_data, noded_cert_file, ssconf_store)
@@ -1492,38 +1549,47 @@ def AddNodeSshKey(node_uuid, node_name,
 
   ssh_port_map = ssconf_store.GetSshPortMap()
 
-  # Update the target node itself
-  logging.debug("Updating SSH key files of target node '%s'.", node_name)
-  if get_public_keys:
-    node_data = {}
-    _InitSshUpdateData(node_data, noded_cert_file, ssconf_store)
-    all_keys = ssh.QueryPubKeyFile(None, key_file=pub_key_file)
-    node_data[constants.SSHS_SSH_PUBLIC_KEYS] = \
-      (constants.SSHS_OVERRIDE, all_keys)
+  # Update the target nodes themselves
+  for node_info in node_list:
+    logging.debug("Updating SSH key files of target node '%s'.", node_info.name)
+    if node_info.get_public_keys:
+      node_data = {}
+      _InitSshUpdateData(node_data, noded_cert_file, ssconf_store)
+      all_keys = ssh.QueryPubKeyFile(None, key_file=pub_key_file)
+      node_data[constants.SSHS_SSH_PUBLIC_KEYS] = \
+        (constants.SSHS_OVERRIDE, all_keys)
 
-    try:
-      utils.RetryByNumberOfTimes(
-          constants.SSHS_MAX_RETRIES,
-          errors.SshUpdateError,
-          run_cmd_fn, cluster_name, node_name, pathutils.SSH_UPDATE,
-          ssh_port_map.get(node_name), node_data,
-          debug=False, verbose=False, use_cluster_key=False,
-          ask_key=False, strict_host_check=False)
-    except errors.SshUpdateError as e:
-      # Clean up the master's public key file if adding key fails
-      if to_public_keys:
-        ssh.RemovePublicKey(node_uuid)
-      raise e
-
-  # Update all nodes except master and the target node
+      try:
+        utils.RetryByNumberOfTimes(
+            constants.SSHS_MAX_RETRIES,
+            errors.SshUpdateError,
+            run_cmd_fn, cluster_name, node_info.name, pathutils.SSH_UPDATE,
+            ssh_port_map.get(node_info.name), node_data,
+            debug=False, verbose=False, use_cluster_key=False,
+            ask_key=False, strict_host_check=False)
+      except errors.SshUpdateError as e:
+        # Clean up the master's public key file if adding key fails
+        if node_info.to_public_keys:
+          ssh.RemovePublicKey(node_info.uuid)
+        raise e
+
+  # Update all nodes except master and the target nodes
+  keys_by_uuid_auth = ssh.QueryPubKeyFile(
+      [node_info.uuid for node_info in node_list
+       if node_info.to_authorized_keys],
+      key_file=pub_key_file)
   if to_authorized_keys:
     base_data[constants.SSHS_SSH_AUTHORIZED_KEYS] = \
-      (constants.SSHS_ADD, keys_by_uuid)
+      (constants.SSHS_ADD, keys_by_uuid_auth)
 
-  pot_mc_data = copy.deepcopy(base_data)
+  pot_mc_data = base_data.copy()
+  keys_by_uuid_pub = ssh.QueryPubKeyFile(
+      [node_info.uuid for node_info in node_list
+       if node_info.to_public_keys],
+      key_file=pub_key_file)
   if to_public_keys:
     pot_mc_data[constants.SSHS_SSH_PUBLIC_KEYS] = \
-      (constants.SSHS_REPLACE_OR_ADD, keys_by_uuid)
+      (constants.SSHS_REPLACE_OR_ADD, keys_by_uuid_pub)
 
   all_nodes = ssconf_store.GetNodeList()
   master_node = ssconf_store.GetMasterNode()
@@ -1551,7 +1617,7 @@ def AddNodeSshKey(node_uuid, node_name,
         error_msg = ("When adding the key of node '%s', updating SSH key"
                      " files of node '%s' failed after %s retries."
                      " Not trying again. Last error was: %s." %
-                     (node, node_name, constants.SSHS_MAX_RETRIES,
+                     (node, node_info.name, constants.SSHS_MAX_RETRIES,
                       last_exception))
         node_errors.append((node, error_msg))
         # We only log the error and don't throw an exception, because
@@ -1620,9 +1686,83 @@ def RemoveNodeSshKey(node_uuid, node_name,
   @returns: list of feedback messages
 
   """
+  node_list = [SshRemoveNodeInfo(uuid=node_uuid,
+                                 name=node_name,
+                                 from_authorized_keys=from_authorized_keys,
+                                 from_public_keys=from_public_keys,
+                                 clear_authorized_keys=clear_authorized_keys,
+                                 clear_public_keys=clear_public_keys)]
+  return RemoveNodeSshKeyBulk(node_list,
+                              master_candidate_uuids,
+                              potential_master_candidates,
+                              master_uuid=master_uuid,
+                              keys_to_remove=keys_to_remove,
+                              pub_key_file=pub_key_file,
+                              ssconf_store=ssconf_store,
+                              noded_cert_file=noded_cert_file,
+                              readd=readd,
+                              run_cmd_fn=run_cmd_fn)
+
+
+# Node info named tuple specifically for the use with RemoveNodeSshKeyBulk
+SshRemoveNodeInfo = collections.namedtuple(
+  "SshRemoveNodeInfo",
+  ["uuid",
+   "name",
+   "from_authorized_keys",
+   "from_public_keys",
+   "clear_authorized_keys",
+   "clear_public_keys"])
+
+
+def RemoveNodeSshKeyBulk(node_list,
+                         master_candidate_uuids,
+                         potential_master_candidates,
+                         master_uuid=None,
+                         keys_to_remove=None,
+                         pub_key_file=pathutils.SSH_PUB_KEYS,
+                         ssconf_store=None,
+                         noded_cert_file=pathutils.NODED_CERT_FILE,
+                         readd=False,
+                         run_cmd_fn=ssh.RunSshCmdWithStdin):
+  """Removes the node's SSH keys from the key files and distributes those.
+
+  Note that at least one of the flags C{from_authorized_keys},
+  C{from_public_keys}, C{clear_authorized_keys}, and C{clear_public_keys}
+  of at least one node has to be set to C{True} for the function to perform any
+  action at all. Not doing so will trigger an assertion in the function.
+
+  @type node_list: list of C{SshRemoveNodeInfo}.
+  @param node_list: list of information about nodes whose keys are being removed
+  @type master_candidate_uuids: list of str
+  @param master_candidate_uuids: list of UUIDs of the current master candidates
+  @type potential_master_candidates: list of str
+  @param potential_master_candidates: list of names of potential master
+    candidates
+  @type keys_to_remove: dict of str to list of str
+  @param keys_to_remove: a dictionary mapping node UUIDS to lists of SSH keys
+    to be removed. This list is supposed to be used only if the keys are not
+    in the public keys file. This is for example the case when removing a
+    master node's key.
+  @type readd: boolean
+  @param readd: whether this is called during a readd operation.
+  @rtype: list of string
+  @returns: list of feedback messages
+
+  """
   # Non-disruptive error messages, list of (node, msg) pairs
   result_msgs = []
 
+  # whether there are any keys to be added or retrieved at all
+  from_authorized_keys = any([node_info.from_authorized_keys for node_info in
+                              node_list])
+  from_public_keys = any([node_info.from_public_keys for node_info in
+                          node_list])
+  clear_authorized_keys = any([node_info.clear_authorized_keys for node_info in
+                               node_list])
+  clear_public_keys = any([node_info.clear_public_keys for node_info in
+                           node_list])
+
   # Make sure at least one of these flags is true.
   if not (from_authorized_keys or from_public_keys or clear_authorized_keys
           or clear_public_keys):
@@ -1634,53 +1774,77 @@ def RemoveNodeSshKey(node_uuid, node_name,
   master_node = ssconf_store.GetMasterNode()
   ssh_port_map = ssconf_store.GetSshPortMap()
 
+  all_keys_to_remove = {}
   if from_authorized_keys or from_public_keys:
-    if keys_to_remove:
-      keys = keys_to_remove
-    else:
-      keys = ssh.QueryPubKeyFile([node_uuid], key_file=pub_key_file)
-      if (not keys or node_uuid not in keys) and not readd:
-        raise errors.SshUpdateError("Node '%s' not found in the list of public"
-                                    " SSH keys. It seems someone tries to"
-                                    " remove a key from outside the cluster!"
-                                    % node_uuid)
-      # During an upgrade all nodes have the master key. In this case we
-      # should not remove it to avoid accidentally shutting down cluster
-      # SSH communication
-      master_keys = None
-      if master_uuid:
-        master_keys = ssh.QueryPubKeyFile([master_uuid], key_file=pub_key_file)
-        for master_key in master_keys:
-          if master_key in keys[node_uuid]:
-            keys[node_uuid].remove(master_key)
-
-    if node_name == master_node and not keys_to_remove:
-      raise errors.SshUpdateError("Cannot remove the master node's keys.")
-
-    if node_uuid in keys:
+    for node_info in node_list:
+      # Skip nodes that don't actually need any keys to be removed.
+      if not (node_info.from_authorized_keys or node_info.from_public_keys):
+        continue
+      if node_info.name == master_node and not keys_to_remove:
+        raise errors.SshUpdateError("Cannot remove the master node's keys.")
+      if keys_to_remove:
+        keys = keys_to_remove
+      else:
+        keys = ssh.QueryPubKeyFile([node_info.uuid], key_file=pub_key_file)
+        if (not keys or node_info.uuid not in keys) and not readd:
+          raise errors.SshUpdateError("Node '%s' not found in the list of"
+                                      " public SSH keys. It seems someone"
+                                      " tries to remove a key from outside"
+                                      " the cluster!" % node_info.uuid)
+        # During an upgrade all nodes have the master key. In this case we
+        # should not remove it to avoid accidentally shutting down cluster
+        # SSH communication
+        master_keys = None
+        if master_uuid:
+          master_keys = ssh.QueryPubKeyFile([master_uuid],
+                                            key_file=pub_key_file)
+          for master_key in master_keys:
+            if master_key in keys[node_info.uuid]:
+              keys[node_info.uuid].remove(master_key)
+
+      all_keys_to_remove.update(keys)
+
+    if all_keys_to_remove:
       base_data = {}
       _InitSshUpdateData(base_data, noded_cert_file, ssconf_store)
       cluster_name = base_data[constants.SSHS_CLUSTER_NAME]
 
       if from_authorized_keys:
+        # UUIDs of nodes that are supposed to be removed from the
+        # authorized_keys files.
+        nodes_remove_from_authorized_keys = [
+            node_info.uuid for node_info in node_list
+            if node_info.from_authorized_keys]
+        keys_to_remove_from_authorized_keys = dict([
+            (uuid, keys) for (uuid, keys) in all_keys_to_remove.items()
+            if uuid in nodes_remove_from_authorized_keys])
         base_data[constants.SSHS_SSH_AUTHORIZED_KEYS] = \
-          (constants.SSHS_REMOVE, keys)
+          (constants.SSHS_REMOVE, keys_to_remove_from_authorized_keys)
         (auth_key_file, _) = \
           ssh.GetAllUserFiles(constants.SSH_LOGIN_USER, mkdir=False,
                               dircheck=False)
-        ssh.RemoveAuthorizedKeys(auth_key_file, keys[node_uuid])
 
-      pot_mc_data = copy.deepcopy(base_data)
+        for uuid in nodes_remove_from_authorized_keys:
+          ssh.RemoveAuthorizedKeys(auth_key_file,
+                                   keys_to_remove_from_authorized_keys[uuid])
+
+      pot_mc_data = base_data.copy()
 
       if from_public_keys:
+        nodes_remove_from_public_keys = [
+            node_info.uuid for node_info in node_list
+            if node_info.from_public_keys]
+        keys_to_remove_from_public_keys = dict([
+            (uuid, keys) for (uuid, keys) in all_keys_to_remove.items()
+            if uuid in nodes_remove_from_public_keys])
         pot_mc_data[constants.SSHS_SSH_PUBLIC_KEYS] = \
-          (constants.SSHS_REMOVE, keys)
-        ssh.RemovePublicKey(node_uuid, key_file=pub_key_file)
+          (constants.SSHS_REMOVE, keys_to_remove_from_public_keys)
 
       all_nodes = ssconf_store.GetNodeList()
       online_nodes = ssconf_store.GetOnlineNodeList()
-      logging.debug("Removing key of node '%s' from all nodes but itself and"
-                    " master.", node_name)
+      all_nodes_to_remove = [node_info.name for node_info in node_list]
+      logging.debug("Removing keys of nodes '%s' from all nodes but itself and"
+                    " master.", ", ".join(all_nodes_to_remove))
       for node in all_nodes:
         if node == master_node:
           logging.debug("Skipping master node '%s'.", master_node)
@@ -1688,6 +1852,9 @@ def RemoveNodeSshKey(node_uuid, node_name,
         if node not in online_nodes:
           logging.debug("Skipping offline node '%s'.", node)
           continue
+        if node in all_nodes_to_remove:
+          logging.debug("Skipping node whose key is removed itself '%s'.", node)
+          continue
         ssh_port = ssh_port_map.get(node)
         if not ssh_port:
           raise errors.OpExecError("No SSH port information available for"
@@ -1709,7 +1876,7 @@ def RemoveNodeSshKey(node_uuid, node_name,
                 ask_key=False, strict_host_check=False)
           except errors.SshUpdateError as last_exception:
             error_msg = error_msg_final % (
-                node_name, node, last_exception)
+                node_info.name, node, last_exception)
             result_msgs.append((node, error_msg))
             logging.error(error_msg)
 
@@ -1726,61 +1893,68 @@ def RemoveNodeSshKey(node_uuid, node_name,
                   ask_key=False, strict_host_check=False)
             except errors.SshUpdateError as last_exception:
               error_msg = error_msg_final % (
-                  node_name, node, last_exception)
+                  node_info.name, node, last_exception)
               result_msgs.append((node, error_msg))
               logging.error(error_msg)
 
-  if clear_authorized_keys or from_public_keys or clear_public_keys:
-    data = {}
-    _InitSshUpdateData(data, noded_cert_file, ssconf_store)
-    cluster_name = data[constants.SSHS_CLUSTER_NAME]
-    ssh_port = ssh_port_map.get(node_name)
-    if not ssh_port:
-      raise errors.OpExecError("No SSH port information available for"
-                               " node '%s', which is leaving the cluster.")
-
-    if clear_authorized_keys:
-      # The 'authorized_keys' file is not solely managed by Ganeti. Therefore,
-      # we have to specify exactly which keys to clear to leave keys untouched
-      # that were not added by Ganeti.
-      other_master_candidate_uuids = [uuid for uuid in master_candidate_uuids
-                                      if uuid != node_uuid]
-      candidate_keys = ssh.QueryPubKeyFile(other_master_candidate_uuids,
-                                           key_file=pub_key_file)
-      data[constants.SSHS_SSH_AUTHORIZED_KEYS] = \
-        (constants.SSHS_REMOVE, candidate_keys)
-
-    if clear_public_keys:
-      data[constants.SSHS_SSH_PUBLIC_KEYS] = \
-        (constants.SSHS_CLEAR, {})
-    elif from_public_keys:
-      # Since clearing the public keys subsumes removing just a single key,
-      # we only do it of clear_public_keys is 'False'.
-
-      if keys[node_uuid]:
+  for node_info in node_list:
+    if node_info.clear_authorized_keys or node_info.from_public_keys or \
+        node_info.clear_public_keys:
+      data = {}
+      _InitSshUpdateData(data, noded_cert_file, ssconf_store)
+      cluster_name = data[constants.SSHS_CLUSTER_NAME]
+      ssh_port = ssh_port_map.get(node_info.name)
+      if not ssh_port:
+        raise errors.OpExecError("No SSH port information available for"
+                                 " node '%s', which is leaving the cluster.")
+
+      if node_info.clear_authorized_keys:
+        # The 'authorized_keys' file is not solely managed by Ganeti. Therefore,
+        # we have to specify exactly which keys to clear to leave keys untouched
+        # that were not added by Ganeti.
+        other_master_candidate_uuids = [uuid for uuid in master_candidate_uuids
+                                        if uuid != node_info.uuid]
+        candidate_keys = ssh.QueryPubKeyFile(other_master_candidate_uuids,
+                                             key_file=pub_key_file)
+        data[constants.SSHS_SSH_AUTHORIZED_KEYS] = \
+          (constants.SSHS_REMOVE, candidate_keys)
+
+      if node_info.clear_public_keys:
         data[constants.SSHS_SSH_PUBLIC_KEYS] = \
-          (constants.SSHS_REMOVE, keys)
-
-    # If we have no changes to any keyfile, just return
-    if not (constants.SSHS_SSH_PUBLIC_KEYS in data or
-            constants.SSHS_SSH_AUTHORIZED_KEYS in data):
-      return
+          (constants.SSHS_CLEAR, {})
+      elif node_info.from_public_keys:
+        # Since clearing the public keys subsumes removing just a single key,
+        # we only do it if clear_public_keys is 'False'.
+
+        if all_keys_to_remove:
+          data[constants.SSHS_SSH_PUBLIC_KEYS] = \
+            (constants.SSHS_REMOVE, all_keys_to_remove)
+
+      # If we have no changes to any keyfile, just return
+      if not (constants.SSHS_SSH_PUBLIC_KEYS in data or
+              constants.SSHS_SSH_AUTHORIZED_KEYS in data):
+        return
 
-    logging.debug("Updating SSH key setup of target node '%s'.", node_name)
-    try:
-      utils.RetryByNumberOfTimes(
-          constants.SSHS_MAX_RETRIES,
-          errors.SshUpdateError,
-          run_cmd_fn, cluster_name, node_name, pathutils.SSH_UPDATE,
-          ssh_port, data,
-          debug=False, verbose=False, use_cluster_key=False,
-          ask_key=False, strict_host_check=False)
-    except errors.SshUpdateError as last_exception:
-      result_msgs.append(
-          (node_name,
-           ("Removing SSH keys from node '%s' failed."
-            " This can happen when the node is already unreachable."
-            " Error: %s" % (node_name, last_exception))))
+      logging.debug("Updating SSH key setup of target node '%s'.",
+                    node_info.name)
+      try:
+        utils.RetryByNumberOfTimes(
+            constants.SSHS_MAX_RETRIES,
+            errors.SshUpdateError,
+            run_cmd_fn, cluster_name, node_info.name, pathutils.SSH_UPDATE,
+            ssh_port, data,
+            debug=False, verbose=False, use_cluster_key=False,
+            ask_key=False, strict_host_check=False)
+      except errors.SshUpdateError as last_exception:
+        result_msgs.append(
+            (node_info.name,
+             ("Removing SSH keys from node '%s' failed."
+              " This can happen when the node is already unreachable."
+              " Error: %s" % (node_info.name, last_exception))))
+
+  if all_keys_to_remove and from_public_keys:
+    for node_uuid in nodes_remove_from_public_keys:
+      ssh.RemovePublicKey(node_uuid, key_file=pub_key_file)
 
   return result_msgs
 
@@ -1940,11 +2114,23 @@ def RenewSshKeys(node_uuids, node_names, master_candidate_uuids,
   all_node_errors = []
 
   # process non-master nodes
+
+  # keys to add in bulk at the end
+  node_keys_to_add = []
+
+  # list of all nodes
+  node_list = []
+
+  # list of keys to be removed before generating new keys
+  node_info_to_remove = []
+
   for node_uuid, node_name in node_uuid_name_map:
     if node_name == master_node_name:
       continue
     master_candidate = node_uuid in master_candidate_uuids
     potential_master_candidate = node_name in potential_master_candidates
+    node_list.append((node_uuid, node_name, master_candidate,
+                      potential_master_candidate))
 
     keys_by_uuid = ssh.QueryPubKeyFile([node_uuid], key_file=pub_key_file)
     if not keys_by_uuid:
@@ -1965,19 +2151,30 @@ def RenewSshKeys(node_uuids, node_names, master_candidate_uuids,
         # remove that node's key, because it is also the master node's key
         # and that would terminate all communication from the master to the
         # node.
-        logging.debug("Removing SSH key of node '%s'.", node_name)
-        node_errors = RemoveNodeSshKey(
-           node_uuid, node_name, master_candidate_uuids,
-           potential_master_candidates,
-           master_uuid=master_node_uuid, from_authorized_keys=master_candidate,
-           from_public_keys=False, clear_authorized_keys=False,
-           clear_public_keys=False)
-        if node_errors:
-          all_node_errors = all_node_errors + node_errors
+        node_info_to_remove.append(SshRemoveNodeInfo(
+            uuid=node_uuid,
+            name=node_name,
+            from_authorized_keys=master_candidate,
+            from_public_keys=False,
+            clear_authorized_keys=False,
+            clear_public_keys=False))
       else:
         logging.debug("Old key of node '%s' is the same as the current master"
                       " key. Not deleting that key on the node.", node_name)
 
+  logging.debug("Removing old SSH keys of all master candidates.")
+  if node_info_to_remove:
+    node_errors = RemoveNodeSshKeyBulk(
+        node_info_to_remove,
+        master_candidate_uuids,
+        potential_master_candidates,
+        master_uuid=master_node_uuid)
+    if node_errors:
+      all_node_errors = all_node_errors + node_errors
+
+  for (node_uuid, node_name, master_candidate, potential_master_candidate) \
+      in node_list:
+
     logging.debug("Generating new SSH key for node '%s'.", node_name)
     _GenerateNodeSshKey(node_uuid, node_name, ssh_port_map,
                         pub_key_file=pub_key_file,
@@ -2001,16 +2198,20 @@ def RenewSshKeys(node_uuids, node_names, master_candidate_uuids,
       ssh.AddPublicKey(node_uuid, pub_key, key_file=pub_key_file)
 
     logging.debug("Add ssh key of node '%s'.", node_name)
-    node_errors = AddNodeSshKey(
-        node_uuid, node_name, potential_master_candidates,
-        to_authorized_keys=master_candidate,
-        to_public_keys=potential_master_candidate,
-        get_public_keys=True,
-        pub_key_file=pub_key_file, ssconf_store=ssconf_store,
-        noded_cert_file=noded_cert_file,
-        run_cmd_fn=run_cmd_fn)
-    if node_errors:
-      all_node_errors = all_node_errors + node_errors
+    node_info = SshAddNodeInfo(name=node_name,
+                               uuid=node_uuid,
+                               to_authorized_keys=master_candidate,
+                               to_public_keys=potential_master_candidate,
+                               get_public_keys=True)
+    node_keys_to_add.append(node_info)
+
+  node_errors = AddNodeSshKeyBulk(
+      node_keys_to_add, potential_master_candidates,
+      pub_key_file=pub_key_file, ssconf_store=ssconf_store,
+      noded_cert_file=noded_cert_file,
+      run_cmd_fn=run_cmd_fn)
+  if node_errors:
+    all_node_errors = all_node_errors + node_errors
 
   # Renewing the master node's key
 
@@ -3002,31 +3203,8 @@ def ModifyInstanceMetadata(metadata):
     if result.failed:
       raise errors.HypervisorError("Failed to start metadata daemon")
 
-  def _Connect():
-    return transport.Transport(pathutils.SOCKET_DIR + "/ganeti-metad",
-                               allow_non_master=True)
-
-  retries = 5
-
-  while True:
-    try:
-      trans = utils.Retry(_Connect, 1.0, constants.LUXI_DEF_CTMO)
-      break
-    except utils.RetryTimeout:
-      raise TimeoutError("Connection to metadata daemon timed out")
-    except (socket.error, NoMasterError), err:
-      if retries == 0:
-        logging.error("Failed to connect to the metadata daemon",
-                      exc_info=True)
-        raise TimeoutError("Failed to connect to metadata daemon: %s" % err)
-      else:
-        retries -= 1
-
-  data = serializer.DumpJson(metadata,
-                             private_encoder=serializer.EncodeWithPrivateFields)
-
-  trans.Send(data)
-  trans.Close()
+  with contextlib.closing(metad.Client()) as client:
+    client.UpdateConfig(metadata)
 
 
 def BlockdevCreate(disk, size, owner, on_primary, info, excl_stor):