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()