On Fri, May 08, 2015 at 04:09:57PM +0200, 'Hrvoje Ribicic' via ganeti-devel 
wrote:
commit fea9a8347f53e0439da6209173774ab5b9d19478
Merge: 1d0abda ebeed78
Author: Hrvoje Ribicic <[email protected]>
Date:   Fri May 8 14:05:17 2015 +0000

   Merge branch 'stable-2.12' into stable-2.13

   * stable-2.12
     Expand orphan volume test
     Restrict Ganeti's orphan volume checks to the single VG
     Modify UDS server startup to set permissions for sockets
     Add wheezy chroot files to gitignore file
     Makefile.am: Don't use -dynamic-too for .hpc_o files
     Makefile.am: Don't use dots in -osuf
     Fix compiler invocation for GHC >= 7.8
     Makefile.am: Fix wrong -dep-suffix for GHC 7.8
     Only upgrade configs not upgraded
     Only unlock config if we did lock it
     Mention preferred DRBD module settings when using Xen
     Avoid assertIn
     Test presence of public and private parameters
     Put private parameters into the environment
     Always close pipe on job forking
     Clean up pipes early on failed forks

   Conflicts:
   test/py/ganeti.backend_unittest.py

   Resolution:
           Merge all test changes.

diff --cc src/Ganeti/Luxi.hs
index a923014,03e71cc..16570d1
--- a/src/Ganeti/Luxi.hs
+++ b/src/Ganeti/Luxi.hs
@@@ -77,11 -76,11 +77,12 @@@ import Ganeti.Object
 import Ganeti.OpParams (pTagsObject)
 import Ganeti.OpCodes
 import qualified Ganeti.Query.Language as Qlang
- import Ganeti.Runtime (GanetiDaemon(..))
+ import Ganeti.Runtime (GanetiDaemon(..), GanetiGroup(..), MiscGroup(..))
 import Ganeti.THH
 import Ganeti.THH.Field
+import Ganeti.THH.Types (getOneTuple)
 import Ganeti.Types
+ import Ganeti.Utils


 -- | Currently supported Luxi operations and JSON serialization.
diff --cc test/py/ganeti.backend_unittest.py
index 08f156a,2e33993..01cc335
--- a/test/py/ganeti.backend_unittest.py
+++ b/test/py/ganeti.backend_unittest.py
@@@ -46,8 -44,9 +46,10 @@@ from ganeti import hyperviso
 from ganeti import netutils
 from ganeti import objects
 from ganeti import pathutils
+ from ganeti import serializer
+from ganeti import ssh
 from ganeti import utils
+ from cmdlib.testsupport.config_mock import ConfigMock


 class TestX509Certificates(unittest.TestCase):
@@@ -951,810 -950,38 +953,843 @@@ class TestSpaceReportingConstants(unitt
       self.assertEqual(None, backend._STORAGE_TYPE_INFO_FN[storage_type])


+class TestAddRemoveGenerateNodeSshKey(testutils.GanetiTestCase):
+
+  _CLUSTER_NAME = "mycluster"
+  _SSH_PORT = 22
+
+  def setUp(self):
+    self._ssh_file_manager = testutils_ssh.FakeSshFileManager()
+    testutils.GanetiTestCase.setUp(self)
+    self._ssh_add_authorized_patcher = testutils \
+      .patch_object(ssh, "AddAuthorizedKeys")
+    self._ssh_remove_authorized_patcher = testutils \
+      .patch_object(ssh, "RemoveAuthorizedKeys")
+    self._ssh_add_authorized_mock =
self._ssh_add_authorized_patcher.start()
+    self._ssh_add_authorized_mock.side_effect = \
+        self._ssh_file_manager.AddAuthorizedKeys
+
+    self._ssconf_mock = mock.Mock()
+    self._ssconf_mock.GetNodeList = mock.Mock()
+    self._ssconf_mock.GetMasterNode = mock.Mock()
+    self._ssconf_mock.GetClusterName = mock.Mock()
+    self._ssconf_mock.GetOnlineNodeList = mock.Mock()
+
+    self._run_cmd_mock = mock.Mock()
+    self._run_cmd_mock.side_effect = self._ssh_file_manager.RunCommand
+
+    self._ssh_remove_authorized_mock = \
+      self._ssh_remove_authorized_patcher.start()
+    self._ssh_remove_authorized_mock.side_effect = \
+        self._ssh_file_manager.RemoveAuthorizedKeys
+
+    self._ssh_add_public_key_patcher = testutils \
+      .patch_object(ssh, "AddPublicKey")
+    self._ssh_add_public_key_mock = \
+      self._ssh_add_public_key_patcher.start()
+    self._ssh_add_public_key_mock.side_effect = \
+      self._ssh_file_manager.AddPublicKey
+
+    self._ssh_remove_public_key_patcher = testutils \
+      .patch_object(ssh, "RemovePublicKey")
+    self._ssh_remove_public_key_mock = \
+      self._ssh_remove_public_key_patcher.start()
+    self._ssh_remove_public_key_mock.side_effect = \
+      self._ssh_file_manager.RemovePublicKey
+
+    self._ssh_query_pub_key_file_patcher = testutils \
+      .patch_object(ssh, "QueryPubKeyFile")
+    self._ssh_query_pub_key_file_mock = \
+      self._ssh_query_pub_key_file_patcher.start()
+    self._ssh_query_pub_key_file_mock.side_effect = \
+      self._ssh_file_manager.QueryPubKeyFile
+
+    self._ssh_replace_name_by_uuid_patcher = testutils \
+      .patch_object(ssh, "ReplaceNameByUuid")
+    self._ssh_replace_name_by_uuid_mock = \
+      self._ssh_replace_name_by_uuid_patcher.start()
+    self._ssh_replace_name_by_uuid_mock.side_effect = \
+      self._ssh_file_manager.ReplaceNameByUuid
+
+    self.noded_cert_file = testutils.TestDataFilename("cert1.pem")
+
+    self._SetupTestData()
+
+  def tearDown(self):
+    super(testutils.GanetiTestCase, self).tearDown()
+    self._ssh_add_authorized_patcher.stop()
+    self._ssh_remove_authorized_patcher.stop()
+    self._ssh_add_public_key_patcher.stop()
+    self._ssh_remove_public_key_patcher.stop()
+    self._ssh_query_pub_key_file_patcher.stop()
+    self._ssh_replace_name_by_uuid_patcher.stop()
+    self._TearDownTestData()
+
+  def _SetupTestData(self, number_of_nodes=15, number_of_pot_mcs=5,
+                     number_of_mcs=5):
+    """Sets up consistent test data for a cluster with a couple of nodes.
+
+    """
+    self._pub_key_file = self._CreateTempFile()
+    self._all_nodes = []
+    self._potential_master_candidates = []
+    self._master_candidate_uuids = []
+    self._ssh_port_map = {}
+
+    self._ssconf_mock.reset_mock()
+    self._ssconf_mock.GetNodeList.reset_mock()
+    self._ssconf_mock.GetMasterNode.reset_mock()
+    self._ssconf_mock.GetClusterName.reset_mock()
+    self._ssconf_mock.GetOnlineNodeList.reset_mock()
+    self._run_cmd_mock.reset_mock()
+
+    self._ssh_file_manager.InitAllNodes(15, 10, 5)
+    self._master_node = self._ssh_file_manager.GetMasterNodeName()
+    self._ssh_port_map =
self._ssh_file_manager.GetSshPortMap(self._SSH_PORT)
+    self._potential_master_candidates = \
+        self._ssh_file_manager.GetAllPotentialMasterCandidateNodeNames()
+    self._master_candidate_uuids = \
+        self._ssh_file_manager.GetAllMasterCandidateUuids()
+    self._all_nodes = self._ssh_file_manager.GetAllNodeNames()
+
+    self._ssconf_mock.GetNodeList.return_value = self._all_nodes
+    self._ssconf_mock.GetOnlineNodeList.return_value = self._all_nodes
+
+  def _TearDownTestData(self):
+    os.remove(self._pub_key_file)
+
+  def _GetCallsPerNode(self):
+    calls_per_node = {}
+    for (pos, keyword) in self._run_cmd_mock.call_args_list:
+      (cluster_name, node, _, _, data) = pos
+      if not node in calls_per_node:
+        calls_per_node[node] = []
+      calls_per_node[node].append(data)
+    return calls_per_node
+
+  def testGenerateKey(self):
+    test_node_name = "node_name_7"
+    test_node_uuid = "node_uuid_7"
+
+    self._SetupTestData()
+    ssh.AddPublicKey(test_node_uuid, "some_old_key",
+                     key_file=self._pub_key_file)
+
+    backend._GenerateNodeSshKey(test_node_uuid, test_node_name,
+                                self._ssh_port_map,
+                                pub_key_file=self._pub_key_file,
+                                ssconf_store=self._ssconf_mock,
+                                noded_cert_file=self.noded_cert_file,
+                                run_cmd_fn=self._run_cmd_mock)
+
+    calls_per_node = self._GetCallsPerNode()
+    for node, calls in calls_per_node.items():
+      self.assertEquals(node, test_node_name)
+      for call in calls:
+        self.assertTrue(constants.SSHS_GENERATE in call)
+
+  def _AddNewNodeToTestData(self, name, uuid, key, pot_mc, mc, master):
+    self._ssh_file_manager.SetOrAddNode(name, uuid, key, pot_mc, mc,
master)
+
+    if pot_mc:
+      ssh.AddPublicKey(name, key, key_file=self._pub_key_file)
+      self._potential_master_candidates.append(name)
+
+    self._ssh_port_map[name] = self._SSH_PORT
+
+  def _GetNewMasterCandidate(self):
+    """Returns the properties of a new master candidate node."""
+    return ("new_node_name", "new_node_uuid", "new_node_key", True, True,
False)
+
+  def testAddMasterCandidate(self):
+    (new_node_name, new_node_uuid, new_node_key, is_master_candidate,
+     is_potential_master_candidate, is_master) =
self._GetNewMasterCandidate()
+
+    self._AddNewNodeToTestData(
+        new_node_name, new_node_uuid, new_node_key,
+        is_potential_master_candidate, is_master_candidate,
+        is_master)
+
+    backend.AddNodeSshKey(new_node_uuid, new_node_name,
+                          self._potential_master_candidates,
+                          self._ssh_port_map,
+                          to_authorized_keys=is_master_candidate,
+                          to_public_keys=is_potential_master_candidate,
+                          get_public_keys=is_potential_master_candidate,
+                          pub_key_file=self._pub_key_file,
+                          ssconf_store=self._ssconf_mock,
+                          noded_cert_file=self.noded_cert_file,
+                          run_cmd_fn=self._run_cmd_mock)
+
+
self._ssh_file_manager.AssertPotentialMasterCandidatesOnlyHavePublicKey(
+        new_node_name)
+    self._ssh_file_manager.AssertAllNodesHaveAuthorizedKey(new_node_key)
+
+  def testAddPotentialMasterCandidate(self):
+    new_node_name = "new_node_name"
+    new_node_uuid = "new_node_uuid"
+    new_node_key = "new_node_key"
+    is_master_candidate = False
+    is_potential_master_candidate = True
+    is_master = False
+
+    self._AddNewNodeToTestData(
+        new_node_name, new_node_uuid, new_node_key,
+        is_potential_master_candidate, is_master_candidate,
+        is_master)
+
+    backend.AddNodeSshKey(new_node_uuid, new_node_name,
+                          self._potential_master_candidates,
+                          self._ssh_port_map,
+                          to_authorized_keys=is_master_candidate,
+                          to_public_keys=is_potential_master_candidate,
+                          get_public_keys=is_potential_master_candidate,
+                          pub_key_file=self._pub_key_file,
+                          ssconf_store=self._ssconf_mock,
+                          noded_cert_file=self.noded_cert_file,
+                          run_cmd_fn=self._run_cmd_mock)
+
+
self._ssh_file_manager.AssertPotentialMasterCandidatesOnlyHavePublicKey(
+        new_node_name)
+    self._ssh_file_manager.AssertNoNodeHasAuthorizedKey(new_node_key)
+
+  def testAddNormalNode(self):
+    new_node_name = "new_node_name"
+    new_node_uuid = "new_node_uuid"
+    new_node_key = "new_node_key"
+    is_master_candidate = False
+    is_potential_master_candidate = False
+    is_master = False
+
+    self._AddNewNodeToTestData(
+        new_node_name, new_node_uuid, new_node_key,
+        is_potential_master_candidate, is_master_candidate,
+        is_master)
+
+    self.assertRaises(
+        AssertionError, backend.AddNodeSshKey, new_node_uuid,
new_node_name,
+        self._potential_master_candidates, self._ssh_port_map,
+        to_authorized_keys=is_master_candidate,
+        to_public_keys=is_potential_master_candidate,
+        get_public_keys=is_potential_master_candidate,
+        pub_key_file=self._pub_key_file,
+        ssconf_store=self._ssconf_mock,
+        noded_cert_file=self.noded_cert_file,
+        run_cmd_fn=self._run_cmd_mock)
+
+    self._ssh_file_manager.AssertNoNodeHasPublicKey(new_node_uuid,
new_node_key)
+    self._ssh_file_manager.AssertNoNodeHasAuthorizedKey(new_node_key)
+
+  def testPromoteToMasterCandidate(self):
+    # Get one of the potential master candidates
+    node_name, node_info = \
+      self._ssh_file_manager.GetAllPurePotentialMasterCandidates()[0]
+    # Update it's role to master candidate in the test data
+    self._ssh_file_manager.SetOrAddNode(
+        node_name, node_info.uuid, node_info.key,
+        node_info.is_potential_master_candidate, True,
node_info.is_master)
+
+    backend.AddNodeSshKey(node_info.uuid, node_name,
+                          self._potential_master_candidates,
+                          self._ssh_port_map,
+                          to_authorized_keys=True,
+                          to_public_keys=False,
+                          get_public_keys=False,
+                          pub_key_file=self._pub_key_file,
+                          ssconf_store=self._ssconf_mock,
+                          noded_cert_file=self.noded_cert_file,
+                          run_cmd_fn=self._run_cmd_mock)
+
+
self._ssh_file_manager.AssertPotentialMasterCandidatesOnlyHavePublicKey(
+        node_name)
+    self._ssh_file_manager.AssertAllNodesHaveAuthorizedKey(node_info.key)
+
+  def testRemoveMasterCandidate(self):
+    node_name, (node_uuid, node_key, is_potential_master_candidate,
+     is_master_candidate, is_master) = \
+        self._ssh_file_manager.GetAllMasterCandidates()[0]
+
+    backend.RemoveNodeSshKey(node_uuid, node_name,
+                             self._master_candidate_uuids,
+                             self._potential_master_candidates,
+                             self._ssh_port_map,
+                             from_authorized_keys=True,
+                             from_public_keys=True,
+                             clear_authorized_keys=True,
+                             clear_public_keys=True,
+                             pub_key_file=self._pub_key_file,
+                             ssconf_store=self._ssconf_mock,
+                             noded_cert_file=self.noded_cert_file,
+                             run_cmd_fn=self._run_cmd_mock)
+
+    self._ssh_file_manager.AssertNoNodeHasPublicKey(node_uuid, node_key)
+    self._ssh_file_manager.AssertNoNodeHasAuthorizedKey(node_key)
+    self.assertEqual(0,
+        len(self._ssh_file_manager.GetPublicKeysOfNode(node_name)))
+    self.assertEqual(0,
+        len(self._ssh_file_manager.GetAuthorizedKeysOfNode(node_name)))
+
+  def testRemovePotentialMasterCandidate(self):
+    (node_name, node_info) = \
+        self._ssh_file_manager.GetAllPurePotentialMasterCandidates()[0]
+
+    backend.RemoveNodeSshKey(node_info.uuid, node_name,
+                             self._master_candidate_uuids,
+                             self._potential_master_candidates,
+                             self._ssh_port_map,
+                             from_authorized_keys=False,
+                             from_public_keys=True,
+                             clear_authorized_keys=True,
+                             clear_public_keys=True,
+                             pub_key_file=self._pub_key_file,
+                             ssconf_store=self._ssconf_mock,
+                             noded_cert_file=self.noded_cert_file,
+                             run_cmd_fn=self._run_cmd_mock)
+
+    self._ssh_file_manager.AssertNoNodeHasPublicKey(
+        node_info.uuid, node_info.key)
+    self._ssh_file_manager.AssertNoNodeHasAuthorizedKey(node_info.key)
+    self.assertEqual(0,
+        len(self._ssh_file_manager.GetPublicKeysOfNode(node_name)))
+    self.assertEqual(0,
+        len(self._ssh_file_manager.GetAuthorizedKeysOfNode(node_name)))
+
+  def testRemoveNormalNode(self):
+    node_name, node_info = self._ssh_file_manager.GetAllNormalNodes()[0]
+
+    backend.RemoveNodeSshKey(node_info.uuid, node_name,
+                             self._master_candidate_uuids,
+                             self._potential_master_candidates,
+                             self._ssh_port_map,
+                             from_authorized_keys=False,
+                             from_public_keys=False,
+                             clear_authorized_keys=True,
+                             clear_public_keys=True,
+                             pub_key_file=self._pub_key_file,
+                             ssconf_store=self._ssconf_mock,
+                             noded_cert_file=self.noded_cert_file,
+                             run_cmd_fn=self._run_cmd_mock)
+
+    self._ssh_file_manager.AssertNoNodeHasPublicKey(
+        node_info.uuid, node_info.key)
+    self._ssh_file_manager.AssertNoNodeHasAuthorizedKey(node_info.key)
+    self.assertEqual(0,
+        len(self._ssh_file_manager.GetPublicKeysOfNode(node_name)))
+    self.assertEqual(0,
+        len(self._ssh_file_manager.GetAuthorizedKeysOfNode(node_name)))
+
+  def testDemoteMasterCandidateToPotentialMasterCandidate(self):
+    node_name, node_info =
self._ssh_file_manager.GetAllMasterCandidates()[0]
+    self._ssh_file_manager.SetOrAddNode(
+        node_name, node_info.uuid, node_info.key,
+        node_info.is_potential_master_candidate, False,
node_info.is_master)
+
+    backend.RemoveNodeSshKey(node_info.uuid, node_name,
+                             self._master_candidate_uuids,
+                             self._potential_master_candidates,
+                             self._ssh_port_map,
+                             from_authorized_keys=True,
+                             from_public_keys=False,
+                             clear_authorized_keys=False,
+                             clear_public_keys=False,
+                             pub_key_file=self._pub_key_file,
+                             ssconf_store=self._ssconf_mock,
+                             noded_cert_file=self.noded_cert_file,
+                             run_cmd_fn=self._run_cmd_mock)
+
+
self._ssh_file_manager.AssertPotentialMasterCandidatesOnlyHavePublicKey(
+        node_name)
+    self._ssh_file_manager.AssertNoNodeHasAuthorizedKey(node_info.key)
+
+  def testDemotePotentialMasterCandidateToNormalNode(self):
+    (node_name, node_info) = \
+        self._ssh_file_manager.GetAllPurePotentialMasterCandidates()[0]
+    self._ssh_file_manager.SetOrAddNode(
+        node_name, node_info.uuid, node_info.key, False,
+        node_info.is_master_candidate, node_info.is_master)
+
+    backend.RemoveNodeSshKey(node_info.uuid, node_name,
+                             self._master_candidate_uuids,
+                             self._potential_master_candidates,
+                             self._ssh_port_map,
+                             from_authorized_keys=False,
+                             from_public_keys=True,
+                             clear_authorized_keys=False,
+                             clear_public_keys=False,
+                             pub_key_file=self._pub_key_file,
+                             ssconf_store=self._ssconf_mock,
+                             noded_cert_file=self.noded_cert_file,
+                             run_cmd_fn=self._run_cmd_mock)
+
+    self._ssh_file_manager.AssertNoNodeHasPublicKey(
+        node_info.uuid, node_info.key)
+    self._ssh_file_manager.AssertNoNodeHasAuthorizedKey(node_info.key)
+
+  def _GetReducedOnlineNodeList(self):
+    """'Randomly' mark some nodes as offline."""
+    return [name for name in self._all_nodes
+            if '3' not in name and '5' not in name]
+
+  def testAddKeyWithOfflineNodes(self):
+    (new_node_name, new_node_uuid, new_node_key, is_master_candidate,
+     is_potential_master_candidate, is_master) =
self._GetNewMasterCandidate()
+
+    self._AddNewNodeToTestData(
+        new_node_name, new_node_uuid, new_node_key,
+        is_potential_master_candidate, is_master_candidate,
+        is_master)
+    self._online_nodes = self._GetReducedOnlineNodeList()
+    self._ssconf_mock.GetOnlineNodeList.return_value = self._online_nodes
+
+    backend.AddNodeSshKey(new_node_uuid, new_node_name,
+                          self._potential_master_candidates,
+                          self._ssh_port_map,
+                          to_authorized_keys=is_master_candidate,
+                          to_public_keys=is_potential_master_candidate,
+                          get_public_keys=is_potential_master_candidate,
+                          pub_key_file=self._pub_key_file,
+                          ssconf_store=self._ssconf_mock,
+                          noded_cert_file=self.noded_cert_file,
+                          run_cmd_fn=self._run_cmd_mock)
+
+    for node in self._all_nodes:
+      if node in self._online_nodes:
+        self.assertTrue(self._ssh_file_manager.NodeHasAuthorizedKey(
+            node, new_node_key))
+      else:
+        self.assertFalse(self._ssh_file_manager.NodeHasAuthorizedKey(
+            node, new_node_key))
+
+  def testRemoveKeyWithOfflineNodes(self):
+    (node_name, node_info) = \
+        self._ssh_file_manager.GetAllMasterCandidates()[0]
+    self._online_nodes = self._GetReducedOnlineNodeList()
+    self._ssconf_mock.GetOnlineNodeList.return_value = self._online_nodes
+
+    backend.RemoveNodeSshKey(node_info.uuid, node_name,
+                             self._master_candidate_uuids,
+                             self._potential_master_candidates,
+                             self._ssh_port_map,
+                             from_authorized_keys=True,
+                             from_public_keys=True,
+                             clear_authorized_keys=True,
+                             clear_public_keys=True,
+                             pub_key_file=self._pub_key_file,
+                             ssconf_store=self._ssconf_mock,
+                             noded_cert_file=self.noded_cert_file,
+                             run_cmd_fn=self._run_cmd_mock)
+
+    offline_nodes = [node for node in self._all_nodes
+                     if node not in self._online_nodes]
+    self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
+        offline_nodes, node_info.key)
+
+  def testAddKeySuccessfullyOnNewNodeWithRetries(self):
+    """Tests adding a new node's key when updating that node takes
retries.
+
+    This test checks whether adding a new node's key successfully updates
+    the SSH key files of all nodes, even if updating the new node's key
files
+    itself takes a couple of retries to succeed.
+
+    """
+    (new_node_name, new_node_uuid, new_node_key, is_master_candidate,
+     is_potential_master_candidate, is_master) =
self._GetNewMasterCandidate()
+
+    self._AddNewNodeToTestData(
+        new_node_name, new_node_uuid, new_node_key,
+        is_potential_master_candidate, is_master_candidate,
+        is_master)
+    self._ssh_file_manager.SetMaxRetries(
+        new_node_name, constants.SSHS_MAX_RETRIES)
+
+    backend.AddNodeSshKey(new_node_uuid, new_node_name,
+                          self._potential_master_candidates,
+                          self._ssh_port_map,
+                          to_authorized_keys=is_master_candidate,
+                          to_public_keys=is_potential_master_candidate,
+                          get_public_keys=is_potential_master_candidate,
+                          pub_key_file=self._pub_key_file,
+                          ssconf_store=self._ssconf_mock,
+                          noded_cert_file=self.noded_cert_file,
+                          run_cmd_fn=self._run_cmd_mock)
+
+
self._ssh_file_manager.AssertPotentialMasterCandidatesOnlyHavePublicKey(
+        new_node_name)
+    self._ssh_file_manager.AssertAllNodesHaveAuthorizedKey(
+        new_node_key)
+
+  def testAddKeyFailedOnNewNodeWithRetries(self):
+    """Tests clean up if updating a new node's SSH setup fails.
+
+    If adding the keys of a new node fails, because updating the SSH key
files
+    of that new node fails, check whether already carried out operations
are
+    successfully rolled back and thus the state of the cluster is cleaned
up.
+
+    """
+    (new_node_name, new_node_uuid, new_node_key, is_master_candidate,
+     is_potential_master_candidate, is_master) =
self._GetNewMasterCandidate()
+
+    self._AddNewNodeToTestData(
+        new_node_name, new_node_uuid, new_node_key,
+        is_potential_master_candidate, is_master_candidate,
+        is_master)
+    self._ssh_file_manager.SetMaxRetries(
+        new_node_name, constants.SSHS_MAX_RETRIES + 1)
+
+    self.assertRaises(
+        errors.SshUpdateError, backend.AddNodeSshKey, new_node_uuid,
+        new_node_name, self._potential_master_candidates,
self._ssh_port_map,
+        to_authorized_keys=is_master_candidate,
+        to_public_keys=is_potential_master_candidate,
+        get_public_keys=is_potential_master_candidate,
+        pub_key_file=self._pub_key_file,
+        ssconf_store=self._ssconf_mock,
+        noded_cert_file=self.noded_cert_file,
+        run_cmd_fn=self._run_cmd_mock)
+
+    for node in self._all_nodes:
+      if node == new_node_name:
+        self.assertTrue(self._ssh_file_manager.NodeHasAuthorizedKey(
+          node, new_node_key))
+      else:
+        self.assertFalse(self._ssh_file_manager.NodeHasAuthorizedKey(
+          node, new_node_key))
+
+    self._ssh_file_manager.AssertNoNodeHasPublicKey(new_node_uuid,
new_node_key)
+
+  def testAddKeySuccessfullyOnOldNodeWithRetries(self):
+    """Tests adding a new key even if updating nodes takes retries.
+
+    This tests whether adding a new node's key successfully finishes,
+    even if one of the other cluster nodes takes a couple of retries
+    to succeed.
+
+    """
+    (new_node_name, new_node_uuid, new_node_key, is_master_candidate,
+     is_potential_master_candidate, is_master) =
self._GetNewMasterCandidate()
+
+    other_node_name, _ =
self._ssh_file_manager.GetAllMasterCandidates()[0]
+    self._ssh_file_manager.SetMaxRetries(
+        other_node_name, constants.SSHS_MAX_RETRIES)
+    assert other_node_name != new_node_name
+    self._AddNewNodeToTestData(
+        new_node_name, new_node_uuid, new_node_key,
+        is_potential_master_candidate, is_master_candidate,
+        is_master)
+
+    backend.AddNodeSshKey(new_node_uuid, new_node_name,
+                          self._potential_master_candidates,
+                          self._ssh_port_map,
+                          to_authorized_keys=is_master_candidate,
+                          to_public_keys=is_potential_master_candidate,
+                          get_public_keys=is_potential_master_candidate,
+                          pub_key_file=self._pub_key_file,
+                          ssconf_store=self._ssconf_mock,
+                          noded_cert_file=self.noded_cert_file,
+                          run_cmd_fn=self._run_cmd_mock)
+
+    self._ssh_file_manager.AssertAllNodesHaveAuthorizedKey(new_node_key)
+
+  def testAddKeyFailedOnOldNodeWithRetries(self):
+    """Tests adding keys when updating one node's SSH setup fails.
+
+    This tests whether when adding a new node's key and one node is
+    unreachable (but not marked as offline) the operation still finishes
+    properly and only that unreachable node's SSH key setup did not get
+    updated.
+
+    """
+    (new_node_name, new_node_uuid, new_node_key, is_master_candidate,
+     is_potential_master_candidate, is_master) =
self._GetNewMasterCandidate()
+
+    other_node_name, _ =
self._ssh_file_manager.GetAllMasterCandidates()[0]
+    self._ssh_file_manager.SetMaxRetries(
+        other_node_name, constants.SSHS_MAX_RETRIES + 1)
+    assert other_node_name != new_node_name
+    self._AddNewNodeToTestData(
+        new_node_name, new_node_uuid, new_node_key,
+        is_potential_master_candidate, is_master_candidate,
+        is_master)
+
+    node_errors = backend.AddNodeSshKey(
+        new_node_uuid, new_node_name, self._potential_master_candidates,
+        self._ssh_port_map, to_authorized_keys=is_master_candidate,
+        to_public_keys=is_potential_master_candidate,
+        get_public_keys=is_potential_master_candidate,
+        pub_key_file=self._pub_key_file,
+        ssconf_store=self._ssconf_mock,
+        noded_cert_file=self.noded_cert_file,
+        run_cmd_fn=self._run_cmd_mock)
+
+    rest_nodes = [node for node in self._all_nodes
+                  if node != other_node_name]
+    rest_nodes.append(new_node_name)
+    self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
+        rest_nodes, new_node_key)
+    self.assertTrue([error_msg for (node, error_msg) in node_errors
+                     if node == other_node_name])
+
+  def testRemoveKeySuccessfullyWithRetriesOnOtherNode(self):
+    """Test removing keys even if one of the old nodes needs retries.
+
+    This tests checks whether a key can be removed successfully even
+    when one of the other nodes needs to be contacted with several
+    retries.
+
+    """
+    all_master_candidates =
self._ssh_file_manager.GetAllMasterCandidates()
+    node_name, node_info = all_master_candidates[0]
+    other_node_name, _ = all_master_candidates[1]
+    assert node_name != self._master_node
+    assert other_node_name != self._master_node
+    assert node_name != other_node_name
+    self._ssh_file_manager.SetMaxRetries(
+        other_node_name, constants.SSHS_MAX_RETRIES)
+
+    backend.RemoveNodeSshKey(node_info.uuid, node_name,
+                             self._master_candidate_uuids,
+                             self._potential_master_candidates,
+                             self._ssh_port_map,
+                             from_authorized_keys=True,
+                             from_public_keys=True,
+                             clear_authorized_keys=True,
+                             clear_public_keys=True,
+                             pub_key_file=self._pub_key_file,
+                             ssconf_store=self._ssconf_mock,
+                             noded_cert_file=self.noded_cert_file,
+                             run_cmd_fn=self._run_cmd_mock)
+
+    self._ssh_file_manager.AssertNoNodeHasPublicKey(
+        node_info.uuid, node_info.key)
+    self._ssh_file_manager.AssertNoNodeHasAuthorizedKey(node_info.key)
+
+  def testRemoveKeyFailedWithRetriesOnOtherNode(self):
+    """Test removing keys even if one of the old nodes fails even with
retries.
+
+    This tests checks whether the removal of a key finishes properly,
even if
+    the update of the key files on one of the other nodes fails despite
several
+    retries.
+
+    """
+    all_master_candidates =
self._ssh_file_manager.GetAllMasterCandidates()
+    node_name, node_info = all_master_candidates[0]
+    other_node_name, _ = all_master_candidates[1]
+    assert node_name != self._master_node
+    assert other_node_name != self._master_node
+    assert node_name != other_node_name
+    self._ssh_file_manager.SetMaxRetries(
+        other_node_name, constants.SSHS_MAX_RETRIES + 1)
+
+    error_msgs = backend.RemoveNodeSshKey(
+        node_info.uuid, node_name, self._master_candidate_uuids,
+        self._potential_master_candidates, self._ssh_port_map,
+        from_authorized_keys=True, from_public_keys=True,
+        clear_authorized_keys=True, clear_public_keys=True,
+        pub_key_file=self._pub_key_file, ssconf_store=self._ssconf_mock,
+        noded_cert_file=self.noded_cert_file,
run_cmd_fn=self._run_cmd_mock)
+
+    self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
+        [other_node_name], node_info.key)
+    self.assertTrue([error_msg for (node, error_msg) in error_msgs
+                     if node == other_node_name])
+
+  def testRemoveKeySuccessfullyWithRetriesOnTargetNode(self):
+    """Test removing keys even if the target nodes needs retries.
+
+    This tests checks whether a key can be removed successfully even
+    when removing the key on the node itself needs retries.
+
+    """
+    all_master_candidates =
self._ssh_file_manager.GetAllMasterCandidates()
+    node_name, node_info = all_master_candidates[0]
+    assert node_name != self._master_node
+    self._ssh_file_manager.SetMaxRetries(
+        node_name, constants.SSHS_MAX_RETRIES)
+
+    backend.RemoveNodeSshKey(node_info.uuid, node_name,
+                             self._master_candidate_uuids,
+                             self._potential_master_candidates,
+                             self._ssh_port_map,
+                             from_authorized_keys=True,
+                             from_public_keys=True,
+                             clear_authorized_keys=True,
+                             clear_public_keys=True,
+                             pub_key_file=self._pub_key_file,
+                             ssconf_store=self._ssconf_mock,
+                             noded_cert_file=self.noded_cert_file,
+                             run_cmd_fn=self._run_cmd_mock)
+
+    self._ssh_file_manager.AssertNoNodeHasPublicKey(
+        node_info.uuid, node_info.key)
+    self._ssh_file_manager.AssertNoNodeHasAuthorizedKey(node_info.key)
+
+  def testRemoveKeyFailedWithRetriesOnTargetNode(self):
+    """Test removing keys even if contacting the node fails with retries.
+
+    This tests checks whether the removal of a key finishes properly,
even if
+    the update of the key files on the node itself fails despite several
+    retries.
+
+    """
+    all_master_candidates =
self._ssh_file_manager.GetAllMasterCandidates()
+    node_name, node_info = all_master_candidates[0]
+    assert node_name != self._master_node
+    self._ssh_file_manager.SetMaxRetries(
+        node_name, constants.SSHS_MAX_RETRIES + 1)
+
+    error_msgs = backend.RemoveNodeSshKey(
+        node_info.uuid, node_name, self._master_candidate_uuids,
+        self._potential_master_candidates, self._ssh_port_map,
+        from_authorized_keys=True, from_public_keys=True,
+        clear_authorized_keys=True, clear_public_keys=True,
+        pub_key_file=self._pub_key_file, ssconf_store=self._ssconf_mock,
+        noded_cert_file=self.noded_cert_file,
run_cmd_fn=self._run_cmd_mock)
+
+    self._ssh_file_manager.AssertNodeSetOnlyHasAuthorizedKey(
+        [node_name], node_info.key)
+    self.assertTrue([error_msg for (node, error_msg) in error_msgs
+                     if node == node_name])
+
+
+class TestVerifySshSetup(testutils.GanetiTestCase):
+
+  _NODE1_UUID = "uuid1"
+  _NODE2_UUID = "uuid2"
+  _NODE3_UUID = "uuid3"
+  _NODE1_NAME = "name1"
+  _NODE2_NAME = "name2"
+  _NODE3_NAME = "name3"
+  _NODE1_KEYS = ["key11"]
+  _NODE2_KEYS = ["key21"]
+  _NODE3_KEYS = ["key31"]
+
+  _NODE_STATUS_LIST = [
+    (_NODE1_UUID, _NODE1_NAME, True, True),
+    (_NODE2_UUID, _NODE2_NAME, False, True),
+    (_NODE3_UUID, _NODE3_NAME, False, False),
+    ]
+
+  _PUB_KEY_RESULT = {
+    _NODE1_UUID: _NODE1_KEYS,
+    _NODE2_UUID: _NODE2_KEYS,
+    _NODE3_UUID: _NODE3_KEYS,
+    }
+
+  _AUTH_RESULT = {
+    _NODE1_KEYS[0]: True,
+    _NODE2_KEYS[0]: False,
+    _NODE3_KEYS[0]: False,
+  }
+
+  def setUp(self):
+    testutils.GanetiTestCase.setUp(self)
+    self._has_authorized_patcher = testutils \
+      .patch_object(ssh, "HasAuthorizedKey")
+    self._has_authorized_mock = self._has_authorized_patcher.start()
+    self._query_patcher = testutils \
+      .patch_object(ssh, "QueryPubKeyFile")
+    self._query_mock = self._query_patcher.start()
+    self._read_file_patcher = testutils \
+      .patch_object(utils, "ReadFile")
+    self._read_file_mock = self._read_file_patcher.start()
+    self._read_file_mock.return_value = self._NODE1_KEYS[0]
+    self.tmpdir = tempfile.mkdtemp()
+    self.pub_key_file = os.path.join(self.tmpdir, "pub_key_file")
+    open(self.pub_key_file, "w").close()
+
+  def tearDown(self):
+    super(testutils.GanetiTestCase, self).tearDown()
+    self._has_authorized_patcher.stop()
+    self._query_patcher.stop()
+    self._read_file_patcher.stop()
+    shutil.rmtree(self.tmpdir)
+
+  def testValidData(self):
+    self._has_authorized_mock.side_effect = \
+      lambda _, key : self._AUTH_RESULT[key]
+    self._query_mock.return_value = self._PUB_KEY_RESULT
+    result = backend._VerifySshSetup(self._NODE_STATUS_LIST,
+                                     self._NODE1_NAME,
+                                     pub_key_file=self.pub_key_file)
+    self.assertEqual(result, [])
+
+  def testMissingKey(self):
+    self._has_authorized_mock.side_effect = \
+      lambda _, key : self._AUTH_RESULT[key]
+    pub_key_missing = copy.deepcopy(self._PUB_KEY_RESULT)
+    del pub_key_missing[self._NODE2_UUID]
+    self._query_mock.return_value = pub_key_missing
+    result = backend._VerifySshSetup(self._NODE_STATUS_LIST,
+                                     self._NODE1_NAME,
+                                     pub_key_file=self.pub_key_file)
+    self.assertTrue(self._NODE2_UUID in result[0])
+
+  def testUnknownKey(self):
+    self._has_authorized_mock.side_effect = \
+      lambda _, key : self._AUTH_RESULT[key]
+    pub_key_missing = copy.deepcopy(self._PUB_KEY_RESULT)
+    pub_key_missing["unkownnodeuuid"] = "pinkbunny"
+    self._query_mock.return_value = pub_key_missing
+    result = backend._VerifySshSetup(self._NODE_STATUS_LIST,
+                                     self._NODE1_NAME,
+                                     pub_key_file=self.pub_key_file)
+    self.assertTrue("unkownnodeuuid" in result[0])
+
+  def testMissingMasterCandidate(self):
+    auth_result = copy.deepcopy(self._AUTH_RESULT)
+    auth_result["key11"] = False
+    self._has_authorized_mock.side_effect = \
+      lambda _, key : auth_result[key]
+    self._query_mock.return_value = self._PUB_KEY_RESULT
+    result = backend._VerifySshSetup(self._NODE_STATUS_LIST,
+                                     self._NODE1_NAME,
+                                     pub_key_file=self.pub_key_file)
+    self.assertTrue(self._NODE1_UUID in result[0])
+
+  def testSuperfluousNormalNode(self):
+    auth_result = copy.deepcopy(self._AUTH_RESULT)
+    auth_result["key31"] = True
+    self._has_authorized_mock.side_effect = \
+      lambda _, key : auth_result[key]
+    self._query_mock.return_value = self._PUB_KEY_RESULT
+    result = backend._VerifySshSetup(self._NODE_STATUS_LIST,
+                                     self._NODE1_NAME,
+                                     pub_key_file=self.pub_key_file)
+    self.assertTrue(self._NODE3_UUID in result[0])
+
+
+ class TestOSEnvironment(unittest.TestCase):
+   """Ensure the presence of public and private parameters.
+
+   They have to be present inside os environment variables.
+
+   """
+
+   def _CreateEnv(self):
+     """Create and return an environment."""
+     config_mock = ConfigMock()
+     inst = config_mock.AddNewInstance(
+              osparams={"public_param": "public_info"},
+              osparams_private=serializer.PrivateDict({"private_param":
+                                                      "private_info",
+
"another_private_param":
+                                                      "more_privacy"}),
+              nics = [])
+     inst.disks_info = ""
+     inst.secondary_nodes = []
+
+     return backend.OSEnvironment(inst, config_mock.CreateOs())
+
+   def testParamPresence(self):
+     env = self._CreateEnv()
+     env_keys = env.keys()
+     self.assertTrue("OSP_PUBLIC_PARAM" in env)
+     self.assertTrue("OSP_PRIVATE_PARAM" in env)
+     self.assertTrue("OSP_ANOTHER_PRIVATE_PARAM" in env)
+     self.assertEqual("public_info", env["OSP_PUBLIC_PARAM"])
+     self.assertEqual("private_info", env["OSP_PRIVATE_PARAM"])
+     self.assertEqual("more_privacy", env["OSP_ANOTHER_PRIVATE_PARAM"])
+
+
 if __name__ == "__main__":
   testutils.GanetiTestProgram()

LGTM, thanks

Reply via email to