| #!/usr/bin/python |
| # |
| |
| # Copyright (C) 2008, 2011, 2012, 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 LUGroup* |
| |
| """ |
| |
| import itertools |
| |
| from ganeti import constants |
| from ganeti import opcodes |
| from ganeti import query |
| |
| from testsupport import * |
| |
| import testutils |
| |
| |
| class TestLUGroupAdd(CmdlibTestCase): |
| def testAddExistingGroup(self): |
| self.cfg.AddNewNodeGroup(name="existing_group") |
| |
| op = opcodes.OpGroupAdd(group_name="existing_group") |
| self.ExecOpCodeExpectOpPrereqError( |
| op, "Desired group name 'existing_group' already exists") |
| |
| def testAddNewGroup(self): |
| op = opcodes.OpGroupAdd(group_name="new_group") |
| |
| self.ExecOpCode(op) |
| |
| self.mcpu.assertLogIsEmpty() |
| |
| def testAddNewGroupParams(self): |
| ndparams = {constants.ND_EXCLUSIVE_STORAGE: True} |
| hv_state = {constants.HT_FAKE: {constants.HVST_CPU_TOTAL: 8}} |
| disk_state = { |
| constants.DT_PLAIN: { |
| "mock_vg": {constants.DS_DISK_TOTAL: 10} |
| } |
| } |
| diskparams = {constants.DT_RBD: {constants.RBD_POOL: "mock_pool"}} |
| ipolicy = constants.IPOLICY_DEFAULTS |
| op = opcodes.OpGroupAdd(group_name="new_group", |
| ndparams=ndparams, |
| hv_state=hv_state, |
| disk_state=disk_state, |
| diskparams=diskparams, |
| ipolicy=ipolicy) |
| |
| self.ExecOpCode(op) |
| |
| self.mcpu.assertLogIsEmpty() |
| |
| def testAddNewGroupInvalidDiskparams(self): |
| diskparams = {constants.DT_RBD: {constants.LV_STRIPES: 1}} |
| op = opcodes.OpGroupAdd(group_name="new_group", |
| diskparams=diskparams) |
| |
| self.ExecOpCodeExpectOpPrereqError( |
| op, "Provided option keys not supported") |
| |
| def testAddNewGroupInvalidIPolic(self): |
| ipolicy = {"invalid_key": "value"} |
| op = opcodes.OpGroupAdd(group_name="new_group", |
| ipolicy=ipolicy) |
| |
| self.ExecOpCodeExpectOpPrereqError(op, "Invalid keys in ipolicy") |
| |
| |
| class TestLUGroupAssignNodes(CmdlibTestCase): |
| def __init__(self, methodName='runTest'): |
| super(TestLUGroupAssignNodes, self).__init__(methodName) |
| |
| self.op = opcodes.OpGroupAssignNodes(group_name="default", |
| nodes=[]) |
| |
| def testAssignSingleNode(self): |
| node = self.cfg.AddNewNode() |
| op = self.CopyOpCode(self.op, nodes=[node.name]) |
| |
| self.ExecOpCode(op) |
| |
| self.mcpu.assertLogIsEmpty() |
| |
| def _BuildSplitInstanceSituation(self): |
| node = self.cfg.AddNewNode() |
| self.cfg.AddNewInstance(disk_template=constants.DT_DRBD8, |
| primary_node=self.master, |
| secondary_node=node) |
| group = self.cfg.AddNewNodeGroup() |
| |
| return (node, group) |
| |
| def testSplitInstanceNoForce(self): |
| (node, group) = self._BuildSplitInstanceSituation() |
| op = opcodes.OpGroupAssignNodes(group_name=group.name, |
| nodes=[node.name]) |
| |
| self.ExecOpCodeExpectOpExecError( |
| op, "instances get split by this change and --force was not given") |
| |
| def testSplitInstanceForce(self): |
| (node, group) = self._BuildSplitInstanceSituation() |
| |
| node2 = self.cfg.AddNewNode(group=group) |
| self.cfg.AddNewInstance(disk_template=constants.DT_DRBD8, |
| primary_node=self.master, |
| secondary_node=node2) |
| |
| op = opcodes.OpGroupAssignNodes(group_name=group.name, |
| nodes=[node.name], |
| force=True) |
| |
| self.ExecOpCode(op) |
| |
| self.mcpu.assertLogContainsRegex("will split the following instances") |
| self.mcpu.assertLogContainsRegex( |
| "instances continue to be split across groups") |
| |
| |
| @withLockedLU |
| def testCheckAssignmentForSplitInstances(self, lu): |
| self.cfg._OpenConfig(True) |
| g1 = self.cfg.AddNewNodeGroup() |
| g2 = self.cfg.AddNewNodeGroup() |
| g3 = self.cfg.AddNewNodeGroup() |
| |
| for (n, g) in [("n1a", g1), ("n1b", g1), ("n2a", g2), ("n2b", g2), |
| ("n3a", g3), ("n3b", g3), ("n3c", g3)]: |
| self.cfg.AddNewNode(uuid=n, group=g.uuid) |
| |
| for uuid, pnode, snode in [("inst1a", "n1a", "n1b"), |
| ("inst1b", "n1b", "n1a"), |
| ("inst2a", "n2a", "n2b"), |
| ("inst3a", "n3a", None), |
| ("inst3b", "n3b", "n1b"), |
| ("inst3c", "n3b", "n2b")]: |
| dt = constants.DT_DISKLESS if snode is None else constants.DT_DRBD8 |
| self.cfg.AddNewInstance(uuid=uuid, |
| disk_template=dt, |
| primary_node=pnode, |
| secondary_node=snode) |
| |
| # Test first with the existing state. |
| (new, prev) = lu.CheckAssignmentForSplitInstances( |
| [], self.cfg.GetAllNodesInfo(), self.cfg.GetAllInstancesInfo()) |
| |
| self.assertEqual([], new) |
| self.assertEqual(set(["inst3b", "inst3c"]), set(prev)) |
| |
| # And now some changes. |
| (new, prev) = lu.CheckAssignmentForSplitInstances( |
| [("n1b", g3.uuid)], |
| self.cfg.GetAllNodesInfo(), |
| self.cfg.GetAllInstancesInfo()) |
| |
| self.assertEqual(set(["inst1a", "inst1b"]), set(new)) |
| self.assertEqual(set(["inst3c"]), set(prev)) |
| |
| |
| class TestLUGroupSetParams(CmdlibTestCase): |
| def testNoModifications(self): |
| op = opcodes.OpGroupSetParams(group_name=self.group.name) |
| |
| self.ExecOpCodeExpectOpPrereqError(op, |
| "Please pass at least one modification") |
| |
| def testModifyingAll(self): |
| ndparams = {constants.ND_EXCLUSIVE_STORAGE: True} |
| hv_state = {constants.HT_FAKE: {constants.HVST_CPU_TOTAL: 8}} |
| disk_state = { |
| constants.DT_PLAIN: { |
| "mock_vg": {constants.DS_DISK_TOTAL: 10} |
| } |
| } |
| diskparams = {constants.DT_RBD: {constants.RBD_POOL: "mock_pool"}} |
| ipolicy = {constants.IPOLICY_DTS: [constants.DT_DRBD8]} |
| op = opcodes.OpGroupSetParams(group_name=self.group.name, |
| ndparams=ndparams, |
| hv_state=hv_state, |
| disk_state=disk_state, |
| diskparams=diskparams, |
| ipolicy=ipolicy) |
| |
| self.ExecOpCode(op) |
| |
| self.mcpu.assertLogIsEmpty() |
| |
| def testInvalidDiskparams(self): |
| diskparams = {constants.DT_RBD: {constants.LV_STRIPES: 1}} |
| op = opcodes.OpGroupSetParams(group_name=self.group.name, |
| diskparams=diskparams) |
| |
| self.ExecOpCodeExpectOpPrereqError( |
| op, "Provided option keys not supported") |
| |
| def testIPolicyNewViolations(self): |
| self.cfg.AddNewInstance(beparams={constants.BE_VCPUS: 8}) |
| |
| min_max = dict(constants.ISPECS_MINMAX_DEFAULTS) |
| min_max[constants.ISPECS_MAX].update({constants.ISPEC_CPU_COUNT: 2}) |
| ipolicy = {constants.ISPECS_MINMAX: [min_max]} |
| op = opcodes.OpGroupSetParams(group_name=self.group.name, |
| ipolicy=ipolicy) |
| |
| self.ExecOpCode(op) |
| |
| self.assertLogContainsRegex( |
| "After the ipolicy change the following instances violate them") |
| |
| |
| class TestLUGroupRemove(CmdlibTestCase): |
| def testNonEmptyGroup(self): |
| group = self.cfg.AddNewNodeGroup() |
| self.cfg.AddNewNode(group=group) |
| op = opcodes.OpGroupRemove(group_name=group.name) |
| |
| self.ExecOpCodeExpectOpPrereqError(op, "Group .* not empty") |
| |
| def testRemoveLastGroup(self): |
| self.master.group = "invalid_group" |
| op = opcodes.OpGroupRemove(group_name=self.group.name) |
| |
| self.ExecOpCodeExpectOpPrereqError( |
| op, "Group .* is the only group, cannot be removed") |
| |
| def testRemoveGroup(self): |
| group = self.cfg.AddNewNodeGroup() |
| op = opcodes.OpGroupRemove(group_name=group.name) |
| |
| self.ExecOpCode(op) |
| |
| self.mcpu.assertLogIsEmpty() |
| |
| |
| class TestLUGroupRename(CmdlibTestCase): |
| def testRenameToExistingName(self): |
| group = self.cfg.AddNewNodeGroup() |
| op = opcodes.OpGroupRename(group_name=group.name, |
| new_name=self.group.name) |
| |
| self.ExecOpCodeExpectOpPrereqError( |
| op, "Desired new name .* clashes with existing node group") |
| |
| def testRename(self): |
| group = self.cfg.AddNewNodeGroup() |
| op = opcodes.OpGroupRename(group_name=group.name, |
| new_name="new_group_name") |
| |
| self.ExecOpCode(op) |
| |
| self.mcpu.assertLogIsEmpty() |
| |
| |
| class TestLUGroupEvacuate(CmdlibTestCase): |
| def testEvacuateEmptyGroup(self): |
| group = self.cfg.AddNewNodeGroup() |
| op = opcodes.OpGroupEvacuate(group_name=group.name) |
| |
| self.iallocator_cls.return_value.result = ([], [], []) |
| |
| self.ExecOpCode(op) |
| |
| def testEvacuateOnlyGroup(self): |
| op = opcodes.OpGroupEvacuate(group_name=self.group.name) |
| |
| self.ExecOpCodeExpectOpPrereqError( |
| op, "There are no possible target groups") |
| |
| def testEvacuateWithTargetGroups(self): |
| group = self.cfg.AddNewNodeGroup() |
| self.cfg.AddNewNode(group=group) |
| self.cfg.AddNewNode(group=group) |
| |
| target_group1 = self.cfg.AddNewNodeGroup() |
| target_group2 = self.cfg.AddNewNodeGroup() |
| op = opcodes.OpGroupEvacuate(group_name=group.name, |
| target_groups=[target_group1.name, |
| target_group2.name]) |
| |
| self.iallocator_cls.return_value.result = ([], [], []) |
| |
| self.ExecOpCode(op) |
| |
| def testFailingIAllocator(self): |
| group = self.cfg.AddNewNodeGroup() |
| op = opcodes.OpGroupEvacuate(group_name=group.name) |
| |
| self.iallocator_cls.return_value.success = False |
| |
| self.ExecOpCodeExpectOpPrereqError( |
| op, "Can't compute group evacuation using iallocator") |
| |
| |
| class TestLUGroupVerifyDisks(CmdlibTestCase): |
| def testNoInstances(self): |
| op = opcodes.OpGroupVerifyDisks(group_name=self.group.name) |
| |
| self.ExecOpCode(op) |
| |
| self.mcpu.assertLogIsEmpty() |
| |
| def testOfflineAndFailingNode(self): |
| node = self.cfg.AddNewNode(offline=True) |
| self.cfg.AddNewInstance(primary_node=node, |
| admin_state=constants.ADMINST_UP) |
| self.cfg.AddNewInstance(admin_state=constants.ADMINST_UP) |
| self.rpc.call_lv_list.return_value = \ |
| self.RpcResultsBuilder() \ |
| .AddFailedNode(self.master) \ |
| .AddOfflineNode(node) \ |
| .Build() |
| |
| op = opcodes.OpGroupVerifyDisks(group_name=self.group.name) |
| |
| (nerrors, offline, missing) = self.ExecOpCode(op) |
| |
| self.assertEqual(1, len(nerrors)) |
| self.assertEqual(0, len(offline)) |
| self.assertEqual(2, len(missing)) |
| |
| def testValidNodeResult(self): |
| self.cfg.AddNewInstance( |
| disks=[self.cfg.CreateDisk(dev_type=constants.DT_PLAIN), |
| self.cfg.CreateDisk(dev_type=constants.DT_PLAIN) |
| ], |
| admin_state=constants.ADMINST_UP) |
| self.rpc.call_lv_list.return_value = \ |
| self.RpcResultsBuilder() \ |
| .AddSuccessfulNode(self.master, { |
| "mockvg/mock_disk_1": (None, None, True), |
| "mockvg/mock_disk_2": (None, None, False) |
| }) \ |
| .Build() |
| |
| op = opcodes.OpGroupVerifyDisks(group_name=self.group.name) |
| |
| (nerrors, offline, missing) = self.ExecOpCode(op) |
| |
| self.assertEqual(0, len(nerrors)) |
| self.assertEqual(1, len(offline)) |
| self.assertEqual(0, len(missing)) |
| |
| def testDrbdDisk(self): |
| node1 = self.cfg.AddNewNode() |
| node2 = self.cfg.AddNewNode() |
| node3 = self.cfg.AddNewNode() |
| node4 = self.cfg.AddNewNode() |
| |
| valid_disk = self.cfg.CreateDisk(dev_type=constants.DT_DRBD8, |
| primary_node=node1, |
| secondary_node=node2) |
| broken_disk = self.cfg.CreateDisk(dev_type=constants.DT_DRBD8, |
| primary_node=node1, |
| secondary_node=node2) |
| failing_node_disk = self.cfg.CreateDisk(dev_type=constants.DT_DRBD8, |
| primary_node=node3, |
| secondary_node=node4) |
| |
| self.cfg.AddNewInstance(disks=[valid_disk, broken_disk], |
| primary_node=node1, |
| admin_state=constants.ADMINST_UP) |
| self.cfg.AddNewInstance(disks=[failing_node_disk], |
| primary_node=node3, |
| admin_state=constants.ADMINST_UP) |
| |
| lv_list_result = dict(("/".join(disk.logical_id), (None, None, True)) |
| for disk in itertools.chain(valid_disk.children, |
| broken_disk.children)) |
| self.rpc.call_lv_list.return_value = \ |
| self.RpcResultsBuilder() \ |
| .AddSuccessfulNode(node1, lv_list_result) \ |
| .AddSuccessfulNode(node2, lv_list_result) \ |
| .AddFailedNode(node3) \ |
| .AddFailedNode(node4) \ |
| .Build() |
| |
| def GetDrbdNeedsActivationResult(node_uuid, *_): |
| if node_uuid == node1.uuid: |
| return self.RpcResultsBuilder() \ |
| .CreateSuccessfulNodeResult(node1, []) |
| elif node_uuid == node2.uuid: |
| return self.RpcResultsBuilder() \ |
| .CreateSuccessfulNodeResult(node2, [broken_disk.uuid]) |
| elif node_uuid == node3.uuid or node_uuid == node4.uuid: |
| return self.RpcResultsBuilder() \ |
| .CreateFailedNodeResult(node_uuid) |
| |
| self.rpc.call_drbd_needs_activation.side_effect = \ |
| GetDrbdNeedsActivationResult |
| |
| op = opcodes.OpGroupVerifyDisks(group_name=self.group.name) |
| |
| (nerrors, offline, missing) = self.ExecOpCode(op) |
| |
| self.assertEqual(2, len(nerrors)) |
| self.assertEqual(1, len(offline)) |
| self.assertEqual(1, len(missing)) |
| |
| |
| if __name__ == "__main__": |
| testutils.GanetiTestProgram() |