| #!/usr/bin/python |
| # |
| |
| # Copyright (C) 2013 Google Inc. |
| # All rights reserved. |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions are |
| # met: |
| # |
| # 1. Redistributions of source code must retain the above copyright notice, |
| # this list of conditions and the following disclaimer. |
| # |
| # 2. Redistributions in binary form must reproduce the above copyright |
| # notice, this list of conditions and the following disclaimer in the |
| # documentation and/or other materials provided with the distribution. |
| # |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS |
| # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
| # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR |
| # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
| # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
| # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| |
| """Tests for LUNode* |
| |
| """ |
| |
| from collections import defaultdict |
| import mock |
| |
| from ganeti import compat |
| from ganeti import constants |
| from ganeti import objects |
| from ganeti import opcodes |
| from ganeti.cmdlib import node |
| |
| from testsupport import * |
| |
| import testutils |
| |
| |
| # pylint: disable=W0613 |
| def _TcpPingFailSecondary(cfg, mock_fct, target, port, timeout=None, |
| live_port_needed=None, source=None): |
| # This will return True if target is in 192.0.2.0/24 (primary range) |
| # and False if not. |
| return "192.0.2." in target |
| |
| |
| class TestLUNodeAdd(CmdlibTestCase): |
| def setUp(self): |
| super(TestLUNodeAdd, self).setUp() |
| |
| # One node for testing readding: |
| self.node_readd = self.cfg.AddNewNode() |
| self.op_readd = opcodes.OpNodeAdd(node_name=self.node_readd.name, |
| readd=True, |
| primary_ip=self.node_readd.primary_ip, |
| secondary_ip=self.node_readd.secondary_ip) |
| |
| # One node for testing adding: |
| # don't add to configuration now! |
| self.node_add = objects.Node(name="node_add", |
| primary_ip="192.0.2.200", |
| secondary_ip="203.0.113.200") |
| |
| self.op_add = opcodes.OpNodeAdd(node_name=self.node_add.name, |
| primary_ip=self.node_add.primary_ip, |
| secondary_ip=self.node_add.secondary_ip) |
| |
| self.netutils_mod.TcpPing.return_value = True |
| |
| self.mocked_dns_rpc = self.rpc_mod.DnsOnlyRunner.return_value |
| |
| self.mocked_dns_rpc.call_version.return_value = \ |
| self.RpcResultsBuilder(use_node_names=True) \ |
| .AddSuccessfulNode(self.node_add, constants.CONFIG_VERSION) \ |
| .AddSuccessfulNode(self.node_readd, constants.CONFIG_VERSION) \ |
| .Build() |
| |
| node_verify_result = \ |
| self.RpcResultsBuilder() \ |
| .CreateSuccessfulNodeResult(self.node_add, {constants.NV_NODELIST: []}) |
| # we can't know the node's UUID in advance, so use defaultdict here |
| self.rpc.call_node_verify.return_value = \ |
| defaultdict(lambda: node_verify_result, {}) |
| self.rpc.call_node_crypto_tokens.return_value = \ |
| self.RpcResultsBuilder() \ |
| .CreateSuccessfulNodeResult(self.node_add, |
| [(constants.CRYPTO_TYPE_SSL_DIGEST, "IA:MA:FA:KE:DI:GE:ST")]) |
| |
| def testOvsNoLink(self): |
| ndparams = { |
| constants.ND_OVS: True, |
| constants.ND_OVS_NAME: "testswitch", |
| constants.ND_OVS_LINK: None, |
| } |
| |
| op = self.CopyOpCode(self.op_add, |
| ndparams=ndparams) |
| |
| self.ExecOpCode(op) |
| self.assertLogContainsRegex( |
| "No physical interface for OpenvSwitch was given." |
| " OpenvSwitch will not have an outside connection." |
| " This might not be what you want") |
| |
| created_node = self.cfg.GetNodeInfoByName(op.node_name) |
| self.assertEqual(ndparams[constants.ND_OVS], |
| created_node.ndparams.get(constants.ND_OVS, None)) |
| self.assertEqual(ndparams[constants.ND_OVS_NAME], |
| created_node.ndparams.get(constants.ND_OVS_NAME, None)) |
| self.assertEqual(ndparams[constants.ND_OVS_LINK], |
| created_node.ndparams.get(constants.ND_OVS_LINK, None)) |
| |
| def testAddCandidateCert(self): |
| self.ExecOpCode(self.op_add) |
| |
| created_node = self.cfg.GetNodeInfoByName(self.op_add.node_name) |
| cluster = self.cfg.GetClusterInfo() |
| self.assertTrue(created_node.uuid in cluster.candidate_certs) |
| |
| def testReAddCandidateCert(self): |
| cluster = self.cfg.GetClusterInfo() |
| self.ExecOpCode(self.op_readd) |
| created_node = self.cfg.GetNodeInfoByName(self.op_readd.node_name) |
| self.assertTrue(created_node.uuid in cluster.candidate_certs) |
| |
| def testAddNoCandidateCert(self): |
| op = self.CopyOpCode(self.op_add, |
| master_capable=False) |
| self.ExecOpCode(op) |
| |
| created_node = self.cfg.GetNodeInfoByName(self.op_add.node_name) |
| cluster = self.cfg.GetClusterInfo() |
| self.assertFalse(created_node.uuid in cluster.candidate_certs) |
| |
| def testWithoutOVS(self): |
| self.ExecOpCode(self.op_add) |
| |
| created_node = self.cfg.GetNodeInfoByName(self.op_add.node_name) |
| self.assertEqual(None, |
| created_node.ndparams.get(constants.ND_OVS, None)) |
| |
| def testWithOVS(self): |
| ndparams = { |
| constants.ND_OVS: True, |
| constants.ND_OVS_LINK: "eth2", |
| } |
| |
| op = self.CopyOpCode(self.op_add, |
| ndparams=ndparams) |
| |
| self.ExecOpCode(op) |
| |
| created_node = self.cfg.GetNodeInfoByName(op.node_name) |
| self.assertEqual(ndparams[constants.ND_OVS], |
| created_node.ndparams.get(constants.ND_OVS, None)) |
| self.assertEqual(ndparams[constants.ND_OVS_LINK], |
| created_node.ndparams.get(constants.ND_OVS_LINK, None)) |
| |
| def testReaddingMaster(self): |
| op = opcodes.OpNodeAdd(node_name=self.cfg.GetMasterNodeName(), |
| readd=True) |
| |
| self.ExecOpCodeExpectOpPrereqError(op, "Cannot readd the master node") |
| |
| def testReaddNotVmCapableNode(self): |
| self.cfg.AddNewInstance(primary_node=self.node_readd) |
| self.netutils_mod.GetHostname.return_value = \ |
| HostnameMock(self.node_readd.name, self.node_readd.primary_ip) |
| |
| op = self.CopyOpCode(self.op_readd, vm_capable=False) |
| |
| self.ExecOpCodeExpectOpPrereqError(op, "Node .* being re-added with" |
| " vm_capable flag set to false, but it" |
| " already holds instances") |
| |
| def testReaddAndPassNodeGroup(self): |
| op = self.CopyOpCode(self.op_readd,group="groupname") |
| |
| self.ExecOpCodeExpectOpPrereqError(op, "Cannot pass a node group when a" |
| " node is being readded") |
| |
| def testPrimaryIPv6(self): |
| self.master.secondary_ip = self.master.primary_ip |
| |
| op = self.CopyOpCode(self.op_add, primary_ip="2001:DB8::1", |
| secondary_ip=self.REMOVE) |
| |
| self.ExecOpCode(op) |
| |
| def testInvalidSecondaryIP(self): |
| op = self.CopyOpCode(self.op_add, secondary_ip="333.444.555.777") |
| |
| self.ExecOpCodeExpectOpPrereqError(op, "Secondary IP .* needs to be a valid" |
| " IPv4 address") |
| |
| def testNodeAlreadyInCluster(self): |
| op = self.CopyOpCode(self.op_readd, readd=False) |
| |
| self.ExecOpCodeExpectOpPrereqError(op, "Node %s is already in the" |
| " configuration" % self.node_readd.name) |
| |
| def testReaddNodeNotInConfiguration(self): |
| op = self.CopyOpCode(self.op_add, readd=True) |
| |
| self.ExecOpCodeExpectOpPrereqError(op, "Node %s is not in the" |
| " configuration" % self.node_add.name) |
| |
| def testPrimaryIpConflict(self): |
| # In LUNodeAdd, DNS will resolve the node name to an IP address, that is |
| # used to overwrite any given primary_ip value! |
| # Thus we need to mock this DNS resolver here! |
| self.netutils_mod.GetHostname.return_value = \ |
| HostnameMock(self.node_add.name, self.node_readd.primary_ip) |
| |
| op = self.CopyOpCode(self.op_add) |
| |
| self.ExecOpCodeExpectOpPrereqError(op, "New node ip address.* conflict with" |
| " existing node") |
| |
| def testSecondaryIpConflict(self): |
| op = self.CopyOpCode(self.op_add, secondary_ip=self.node_readd.secondary_ip) |
| |
| self.ExecOpCodeExpectOpPrereqError(op, "New node ip address.* conflict with" |
| " existing node") |
| |
| def testReaddWithDifferentIP(self): |
| op = self.CopyOpCode(self.op_readd, primary_ip="192.0.2.100", |
| secondary_ip="230.0.113.100") |
| |
| self.ExecOpCodeExpectOpPrereqError(op, "Readded node doesn't have the same" |
| " IP address configuration as before") |
| |
| def testNodeHasSecondaryIpButNotMaster(self): |
| self.master.secondary_ip = self.master.primary_ip |
| |
| self.ExecOpCodeExpectOpPrereqError(self.op_add, "The master has no" |
| " secondary ip but the new node has one") |
| |
| def testMasterHasSecondaryIpButNotNode(self): |
| op = self.CopyOpCode(self.op_add, secondary_ip=None) |
| |
| self.ExecOpCodeExpectOpPrereqError(op, "The master has a secondary ip but" |
| " the new node doesn't have one") |
| |
| def testNodeNotReachableByPing(self): |
| self.netutils_mod.TcpPing.return_value = False |
| |
| op = self.CopyOpCode(self.op_add) |
| |
| self.ExecOpCodeExpectOpPrereqError(op, "Node not reachable by ping") |
| |
| def testNodeNotReachableByPingOnSecondary(self): |
| self.netutils_mod.GetHostname.return_value = \ |
| HostnameMock(self.node_add.name, self.node_add.primary_ip) |
| self.netutils_mod.TcpPing.side_effect = \ |
| compat.partial(_TcpPingFailSecondary, self.cfg, self.netutils_mod.TcpPing) |
| |
| op = self.CopyOpCode(self.op_add) |
| |
| self.ExecOpCodeExpectOpPrereqError(op, "Node secondary ip not reachable by" |
| " TCP based ping to node daemon port") |
| |
| def testCantGetVersion(self): |
| self.mocked_dns_rpc.call_version.return_value = \ |
| self.RpcResultsBuilder(use_node_names=True) \ |
| .AddErrorNode(self.node_add) \ |
| .Build() |
| |
| op = self.CopyOpCode(self.op_add) |
| self.ExecOpCodeExpectOpPrereqError(op, "Can't get version information from" |
| " node %s" % self.node_add.name) |
| |
| |
| class TestLUNodeSetParams(CmdlibTestCase): |
| def setUp(self): |
| super(TestLUNodeSetParams, self).setUp() |
| |
| self.MockOut(node, 'netutils', self.netutils_mod) |
| node.netutils.TcpPing.return_value = True |
| |
| self.node = self.cfg.AddNewNode( |
| primary_ip='192.168.168.191', |
| secondary_ip='192.168.168.192', |
| master_candidate=True, uuid='blue_bunny') |
| |
| self.snode = self.cfg.AddNewNode( |
| primary_ip='192.168.168.193', |
| secondary_ip='192.168.168.194', |
| master_candidate=True, uuid='pink_bunny') |
| |
| def testSetSecondaryIp(self): |
| self.instance = self.cfg.AddNewInstance(primary_node=self.node, |
| secondary_node=self.snode, |
| disk_template='drbd') |
| op = opcodes.OpNodeSetParams(node_name=self.node.name, |
| secondary_ip='254.254.254.254') |
| self.ExecOpCode(op) |
| |
| self.assertEqual('254.254.254.254', self.node.secondary_ip) |
| self.assertEqual(sorted(self.wconfd.all_locks.items()), [ |
| ('cluster/BGL', 'shared'), |
| ('instance/mock_inst_1.example.com', 'shared'), |
| ('node-res/blue_bunny', 'exclusive'), |
| ('node/blue_bunny', 'exclusive')]) |
| |
| def testSetSecondaryIpNoLock(self): |
| self.instance = self.cfg.AddNewInstance(primary_node=self.node, |
| secondary_node=self.snode, |
| disk_template='file') |
| op = opcodes.OpNodeSetParams(node_name=self.node.name, |
| secondary_ip='254.254.254.254') |
| self.ExecOpCode(op) |
| |
| self.assertEqual('254.254.254.254', self.node.secondary_ip) |
| self.assertEqual(sorted(self.wconfd.all_locks.items()), [ |
| ('cluster/BGL', 'shared'), |
| ('node-res/blue_bunny', 'exclusive'), |
| ('node/blue_bunny', 'exclusive')]) |
| |
| |
| if __name__ == "__main__": |
| testutils.GanetiTestProgram() |