| #!/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. |
| |
| |
| """Script for unittesting the cmdlib module""" |
| |
| |
| import mock |
| import unittest |
| import itertools |
| import copy |
| |
| from ganeti import constants |
| from ganeti import mcpu |
| from ganeti import cmdlib |
| from ganeti.cmdlib import cluster |
| from ganeti.cmdlib.cluster import verify |
| from ganeti.cmdlib import instance_storage |
| from ganeti.cmdlib import instance_utils |
| from ganeti.cmdlib import common |
| from ganeti.cmdlib import query |
| from ganeti import opcodes |
| from ganeti import errors |
| from ganeti import luxi |
| from ganeti import ht |
| from ganeti import objects |
| from ganeti import locking |
| from ganeti.masterd import iallocator |
| |
| import testutils |
| import mocks |
| |
| |
| class TestOpcodeParams(testutils.GanetiTestCase): |
| def testParamsStructures(self): |
| for op in sorted(mcpu.Processor.DISPATCH_TABLE): |
| lu = mcpu.Processor.DISPATCH_TABLE[op] |
| lu_name = lu.__name__ |
| self.failIf(hasattr(lu, "_OP_REQP"), |
| msg=("LU '%s' has old-style _OP_REQP" % lu_name)) |
| self.failIf(hasattr(lu, "_OP_DEFS"), |
| msg=("LU '%s' has old-style _OP_DEFS" % lu_name)) |
| self.failIf(hasattr(lu, "_OP_PARAMS"), |
| msg=("LU '%s' has old-style _OP_PARAMS" % lu_name)) |
| |
| |
| class TestIAllocatorChecks(testutils.GanetiTestCase): |
| def testFunction(self): |
| class TestLU(object): |
| def __init__(self, opcode): |
| self.cfg = mocks.FakeConfig() |
| self.op = opcode |
| |
| class OpTest(opcodes.OpCode): |
| OP_PARAMS = [ |
| ("iallocator", None, ht.TAny, None), |
| ("node", None, ht.TAny, None), |
| ] |
| |
| default_iallocator = mocks.FakeConfig().GetDefaultIAllocator() |
| other_iallocator = default_iallocator + "_not" |
| |
| op = OpTest() |
| lu = TestLU(op) |
| |
| c_i = lambda: common.CheckIAllocatorOrNode(lu, "iallocator", "node") |
| |
| # Neither node nor iallocator given |
| for n in (None, []): |
| op.iallocator = None |
| op.node = n |
| c_i() |
| self.assertEqual(lu.op.iallocator, default_iallocator) |
| self.assertEqual(lu.op.node, n) |
| |
| # Both, iallocator and node given |
| for a in ("test", constants.DEFAULT_IALLOCATOR_SHORTCUT): |
| op.iallocator = a |
| op.node = "test" |
| self.assertRaises(errors.OpPrereqError, c_i) |
| |
| # Only iallocator given |
| for n in (None, []): |
| op.iallocator = other_iallocator |
| op.node = n |
| c_i() |
| self.assertEqual(lu.op.iallocator, other_iallocator) |
| self.assertEqual(lu.op.node, n) |
| |
| # Only node given |
| op.iallocator = None |
| op.node = "node" |
| c_i() |
| self.assertEqual(lu.op.iallocator, None) |
| self.assertEqual(lu.op.node, "node") |
| |
| # Asked for default iallocator, no node given |
| op.iallocator = constants.DEFAULT_IALLOCATOR_SHORTCUT |
| op.node = None |
| c_i() |
| self.assertEqual(lu.op.iallocator, default_iallocator) |
| self.assertEqual(lu.op.node, None) |
| |
| # No node, iallocator or default iallocator |
| op.iallocator = None |
| op.node = None |
| lu.cfg.GetDefaultIAllocator = lambda: None |
| self.assertRaises(errors.OpPrereqError, c_i) |
| |
| |
| class TestLUTestJqueue(unittest.TestCase): |
| def test(self): |
| self.assert_(cmdlib.LUTestJqueue._CLIENT_CONNECT_TIMEOUT < |
| (luxi.WFJC_TIMEOUT * 0.75), |
| msg=("Client timeout too high, might not notice bugs" |
| " in WaitForJobChange")) |
| |
| |
| class TestLUQuery(unittest.TestCase): |
| def test(self): |
| self.assertEqual(sorted(query._QUERY_IMPL.keys()), |
| sorted(constants.QR_VIA_OP)) |
| |
| assert constants.QR_NODE in constants.QR_VIA_LUXI |
| assert constants.QR_INSTANCE in constants.QR_VIA_LUXI |
| |
| for i in constants.QR_VIA_OP: |
| self.assert_(query._GetQueryImplementation(i)) |
| |
| self.assertRaises(errors.OpPrereqError, query._GetQueryImplementation, |
| "") |
| self.assertRaises(errors.OpPrereqError, query._GetQueryImplementation, |
| "xyz") |
| |
| |
| class _FakeLU: |
| def __init__(self, cfg=NotImplemented, proc=NotImplemented, |
| rpc=NotImplemented): |
| self.warning_log = [] |
| self.info_log = [] |
| self.cfg = cfg |
| self.proc = proc |
| self.rpc = rpc |
| |
| def LogWarning(self, text, *args): |
| self.warning_log.append((text, args)) |
| |
| def LogInfo(self, text, *args): |
| self.info_log.append((text, args)) |
| |
| |
| class TestLoadNodeEvacResult(unittest.TestCase): |
| def testSuccess(self): |
| for moved in [[], [ |
| ("inst20153.example.com", "grp2", ["nodeA4509", "nodeB2912"]), |
| ]]: |
| for early_release in [False, True]: |
| for use_nodes in [False, True]: |
| jobs = [ |
| [opcodes.OpInstanceReplaceDisks().__getstate__()], |
| [opcodes.OpInstanceMigrate().__getstate__()], |
| ] |
| |
| alloc_result = (moved, [], jobs) |
| assert iallocator._NEVAC_RESULT(alloc_result) |
| |
| lu = _FakeLU() |
| result = common.LoadNodeEvacResult(lu, alloc_result, |
| early_release, use_nodes) |
| |
| if moved: |
| (_, (info_args, )) = lu.info_log.pop(0) |
| for (instname, instgroup, instnodes) in moved: |
| self.assertTrue(instname in info_args) |
| if use_nodes: |
| for i in instnodes: |
| self.assertTrue(i in info_args) |
| else: |
| self.assertTrue(instgroup in info_args) |
| |
| self.assertFalse(lu.info_log) |
| self.assertFalse(lu.warning_log) |
| |
| for op in itertools.chain(*result): |
| if hasattr(op.__class__, "early_release"): |
| self.assertEqual(op.early_release, early_release) |
| else: |
| self.assertFalse(hasattr(op, "early_release")) |
| |
| def testFailed(self): |
| alloc_result = ([], [ |
| ("inst5191.example.com", "errormsg21178"), |
| ], []) |
| assert iallocator._NEVAC_RESULT(alloc_result) |
| |
| lu = _FakeLU() |
| self.assertRaises(errors.OpExecError, common.LoadNodeEvacResult, |
| lu, alloc_result, False, False) |
| self.assertFalse(lu.info_log) |
| (_, (args, )) = lu.warning_log.pop(0) |
| self.assertTrue("inst5191.example.com" in args) |
| self.assertTrue("errormsg21178" in args) |
| self.assertFalse(lu.warning_log) |
| |
| |
| class TestUpdateAndVerifySubDict(unittest.TestCase): |
| def setUp(self): |
| self.type_check = { |
| "a": constants.VTYPE_INT, |
| "b": constants.VTYPE_STRING, |
| "c": constants.VTYPE_BOOL, |
| "d": constants.VTYPE_STRING, |
| } |
| |
| def test(self): |
| old_test = { |
| "foo": { |
| "d": "blubb", |
| "a": 321, |
| }, |
| "baz": { |
| "a": 678, |
| "b": "678", |
| "c": True, |
| }, |
| } |
| test = { |
| "foo": { |
| "a": 123, |
| "b": "123", |
| "c": True, |
| }, |
| "bar": { |
| "a": 321, |
| "b": "321", |
| "c": False, |
| }, |
| } |
| |
| mv = { |
| "foo": { |
| "a": 123, |
| "b": "123", |
| "c": True, |
| "d": "blubb" |
| }, |
| "bar": { |
| "a": 321, |
| "b": "321", |
| "c": False, |
| }, |
| "baz": { |
| "a": 678, |
| "b": "678", |
| "c": True, |
| }, |
| } |
| |
| verified = common._UpdateAndVerifySubDict(old_test, test, self.type_check) |
| self.assertEqual(verified, mv) |
| |
| def testWrong(self): |
| test = { |
| "foo": { |
| "a": "blubb", |
| "b": "123", |
| "c": True, |
| }, |
| "bar": { |
| "a": 321, |
| "b": "321", |
| "c": False, |
| }, |
| } |
| |
| self.assertRaises(errors.TypeEnforcementError, |
| common._UpdateAndVerifySubDict, {}, test, |
| self.type_check) |
| |
| |
| class TestHvStateHelper(unittest.TestCase): |
| def testWithoutOpData(self): |
| self.assertEqual(common.MergeAndVerifyHvState(None, NotImplemented), |
| None) |
| |
| def testWithoutOldData(self): |
| new = { |
| constants.HT_XEN_PVM: { |
| constants.HVST_MEMORY_TOTAL: 4096, |
| }, |
| } |
| self.assertEqual(common.MergeAndVerifyHvState(new, None), new) |
| |
| def testWithWrongHv(self): |
| new = { |
| "i-dont-exist": { |
| constants.HVST_MEMORY_TOTAL: 4096, |
| }, |
| } |
| self.assertRaises(errors.OpPrereqError, common.MergeAndVerifyHvState, |
| new, None) |
| |
| class TestDiskStateHelper(unittest.TestCase): |
| def testWithoutOpData(self): |
| self.assertEqual(common.MergeAndVerifyDiskState(None, NotImplemented), |
| None) |
| |
| def testWithoutOldData(self): |
| new = { |
| constants.DT_PLAIN: { |
| "xenvg": { |
| constants.DS_DISK_RESERVED: 1024, |
| }, |
| }, |
| } |
| self.assertEqual(common.MergeAndVerifyDiskState(new, None), new) |
| |
| def testWithWrongStorageType(self): |
| new = { |
| "i-dont-exist": { |
| "xenvg": { |
| constants.DS_DISK_RESERVED: 1024, |
| }, |
| }, |
| } |
| self.assertRaises(errors.OpPrereqError, common.MergeAndVerifyDiskState, |
| new, None) |
| |
| |
| class TestComputeMinMaxSpec(unittest.TestCase): |
| def setUp(self): |
| self.ispecs = { |
| constants.ISPECS_MAX: { |
| constants.ISPEC_MEM_SIZE: 512, |
| constants.ISPEC_DISK_SIZE: 1024, |
| }, |
| constants.ISPECS_MIN: { |
| constants.ISPEC_MEM_SIZE: 128, |
| constants.ISPEC_DISK_COUNT: 1, |
| }, |
| } |
| |
| def testNoneValue(self): |
| self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_MEM_SIZE, None, |
| self.ispecs, None) is None) |
| |
| def testAutoValue(self): |
| self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_MEM_SIZE, None, |
| self.ispecs, |
| constants.VALUE_AUTO) is None) |
| |
| def testNotDefined(self): |
| self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_NIC_COUNT, None, |
| self.ispecs, 3) is None) |
| |
| def testNoMinDefined(self): |
| self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_DISK_SIZE, None, |
| self.ispecs, 128) is None) |
| |
| def testNoMaxDefined(self): |
| self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_DISK_COUNT, |
| None, self.ispecs, 16) is None) |
| |
| def testOutOfRange(self): |
| for (name, val) in ((constants.ISPEC_MEM_SIZE, 64), |
| (constants.ISPEC_MEM_SIZE, 768), |
| (constants.ISPEC_DISK_SIZE, 4096), |
| (constants.ISPEC_DISK_COUNT, 0)): |
| min_v = self.ispecs[constants.ISPECS_MIN].get(name, val) |
| max_v = self.ispecs[constants.ISPECS_MAX].get(name, val) |
| self.assertEqual(common._ComputeMinMaxSpec(name, None, |
| self.ispecs, val), |
| "%s value %s is not in range [%s, %s]" % |
| (name, val,min_v, max_v)) |
| self.assertEqual(common._ComputeMinMaxSpec(name, "1", |
| self.ispecs, val), |
| "%s/1 value %s is not in range [%s, %s]" % |
| (name, val,min_v, max_v)) |
| |
| def test(self): |
| for (name, val) in ((constants.ISPEC_MEM_SIZE, 256), |
| (constants.ISPEC_MEM_SIZE, 128), |
| (constants.ISPEC_MEM_SIZE, 512), |
| (constants.ISPEC_DISK_SIZE, 1024), |
| (constants.ISPEC_DISK_SIZE, 0), |
| (constants.ISPEC_DISK_COUNT, 1), |
| (constants.ISPEC_DISK_COUNT, 5)): |
| self.assertTrue(common._ComputeMinMaxSpec(name, None, self.ispecs, val) |
| is None) |
| |
| |
| def _ValidateComputeMinMaxSpec(name, *_): |
| assert name in constants.ISPECS_PARAMETERS |
| return None |
| |
| |
| def _NoDiskComputeMinMaxSpec(name, *_): |
| if name == constants.ISPEC_DISK_COUNT: |
| return name |
| else: |
| return None |
| |
| |
| class _SpecWrapper: |
| def __init__(self, spec): |
| self.spec = spec |
| |
| def ComputeMinMaxSpec(self, *args): |
| return self.spec.pop(0) |
| |
| |
| class TestComputeIPolicySpecViolation(unittest.TestCase): |
| # Minimal policy accepted by _ComputeIPolicySpecViolation() |
| _MICRO_IPOL = { |
| constants.IPOLICY_DTS: [constants.DT_PLAIN, constants.DT_DISKLESS], |
| constants.ISPECS_MINMAX: [NotImplemented], |
| } |
| |
| def test(self): |
| compute_fn = _ValidateComputeMinMaxSpec |
| ret = common.ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1, |
| [1024], 1, [constants.DT_PLAIN], |
| _compute_fn=compute_fn) |
| self.assertEqual(ret, []) |
| |
| def testDiskFull(self): |
| compute_fn = _NoDiskComputeMinMaxSpec |
| ret = common.ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1, |
| [1024], 1, [constants.DT_PLAIN], |
| _compute_fn=compute_fn) |
| self.assertEqual(ret, [constants.ISPEC_DISK_COUNT]) |
| |
| def testDiskLess(self): |
| compute_fn = _NoDiskComputeMinMaxSpec |
| ret = common.ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 0, 1, |
| [], 1, [], |
| _compute_fn=compute_fn) |
| self.assertEqual(ret, []) |
| |
| def testWrongTemplates(self): |
| compute_fn = _ValidateComputeMinMaxSpec |
| ret = common.ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1, |
| [1024], 1, [constants.DT_DRBD8], |
| _compute_fn=compute_fn) |
| self.assertEqual(len(ret), 1) |
| self.assertTrue("Disk template" in ret[0]) |
| |
| def testInvalidArguments(self): |
| self.assertRaises(AssertionError, common.ComputeIPolicySpecViolation, |
| self._MICRO_IPOL, 1024, 1, 1, 1, constants.DT_DISKLESS, 1, |
| constants.DT_PLAIN,) |
| |
| def testInvalidSpec(self): |
| spec = _SpecWrapper([None, False, "foo", None, "bar", None]) |
| compute_fn = spec.ComputeMinMaxSpec |
| ret = common.ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1, |
| [1024], 1, [constants.DT_PLAIN], |
| _compute_fn=compute_fn) |
| self.assertEqual(ret, ["foo", "bar"]) |
| self.assertFalse(spec.spec) |
| |
| def testWithIPolicy(self): |
| mem_size = 2048 |
| cpu_count = 2 |
| disk_count = 1 |
| disk_sizes = [512] |
| nic_count = 1 |
| spindle_use = 4 |
| disk_template = "mytemplate" |
| ispec = { |
| constants.ISPEC_MEM_SIZE: mem_size, |
| constants.ISPEC_CPU_COUNT: cpu_count, |
| constants.ISPEC_DISK_COUNT: disk_count, |
| constants.ISPEC_DISK_SIZE: disk_sizes[0], |
| constants.ISPEC_NIC_COUNT: nic_count, |
| constants.ISPEC_SPINDLE_USE: spindle_use, |
| } |
| ipolicy1 = { |
| constants.ISPECS_MINMAX: [{ |
| constants.ISPECS_MIN: ispec, |
| constants.ISPECS_MAX: ispec, |
| }], |
| constants.IPOLICY_DTS: [disk_template], |
| } |
| ispec_copy = copy.deepcopy(ispec) |
| ipolicy2 = { |
| constants.ISPECS_MINMAX: [ |
| { |
| constants.ISPECS_MIN: ispec_copy, |
| constants.ISPECS_MAX: ispec_copy, |
| }, |
| { |
| constants.ISPECS_MIN: ispec, |
| constants.ISPECS_MAX: ispec, |
| }, |
| ], |
| constants.IPOLICY_DTS: [disk_template], |
| } |
| ipolicy3 = { |
| constants.ISPECS_MINMAX: [ |
| { |
| constants.ISPECS_MIN: ispec, |
| constants.ISPECS_MAX: ispec, |
| }, |
| { |
| constants.ISPECS_MIN: ispec_copy, |
| constants.ISPECS_MAX: ispec_copy, |
| }, |
| ], |
| constants.IPOLICY_DTS: [disk_template], |
| } |
| def AssertComputeViolation(ipolicy, violations): |
| ret = common.ComputeIPolicySpecViolation(ipolicy, mem_size, cpu_count, |
| disk_count, nic_count, |
| disk_sizes, spindle_use, |
| [disk_template]*disk_count) |
| self.assertEqual(len(ret), violations) |
| |
| AssertComputeViolation(ipolicy1, 0) |
| AssertComputeViolation(ipolicy2, 0) |
| AssertComputeViolation(ipolicy3, 0) |
| for par in constants.ISPECS_PARAMETERS: |
| ispec[par] += 1 |
| AssertComputeViolation(ipolicy1, 1) |
| AssertComputeViolation(ipolicy2, 0) |
| AssertComputeViolation(ipolicy3, 0) |
| ispec[par] -= 2 |
| AssertComputeViolation(ipolicy1, 1) |
| AssertComputeViolation(ipolicy2, 0) |
| AssertComputeViolation(ipolicy3, 0) |
| ispec[par] += 1 # Restore |
| ipolicy1[constants.IPOLICY_DTS] = ["another_template"] |
| AssertComputeViolation(ipolicy1, 1) |
| |
| |
| class TestComputeIPolicyDiskSizesViolation(unittest.TestCase): |
| # Minimal policy accepted by _ComputeIPolicyDiskSizesViolation() |
| _MICRO_IPOL = { |
| constants.IPOLICY_DTS: [constants.DT_PLAIN, constants.DT_DISKLESS], |
| constants.ISPECS_MINMAX: [None], |
| } |
| |
| def MakeDisks(self, *dev_types): |
| return [mock.Mock(dev_type=d) for d in dev_types] |
| |
| def test(self): |
| compute_fn = _ValidateComputeMinMaxSpec |
| ret = common.ComputeIPolicyDiskSizesViolation( |
| self._MICRO_IPOL, [1024], self.MakeDisks(constants.DT_PLAIN), |
| _compute_fn=compute_fn) |
| self.assertEqual(ret, []) |
| |
| def testDiskFull(self): |
| compute_fn = _NoDiskComputeMinMaxSpec |
| ret = common.ComputeIPolicyDiskSizesViolation( |
| self._MICRO_IPOL, [1024], self.MakeDisks(constants.DT_PLAIN), |
| _compute_fn=compute_fn) |
| self.assertEqual(ret, [constants.ISPEC_DISK_COUNT]) |
| |
| def testDisksMixed(self): |
| compute_fn = _ValidateComputeMinMaxSpec |
| ipol = copy.deepcopy(self._MICRO_IPOL) |
| ipol[constants.IPOLICY_DTS].append(constants.DT_DRBD8) |
| ret = common.ComputeIPolicyDiskSizesViolation( |
| ipol, [1024, 1024], |
| self.MakeDisks(constants.DT_DRBD8, constants.DT_PLAIN), |
| _compute_fn=compute_fn) |
| self.assertEqual(ret, []) |
| |
| |
| def testDiskLess(self): |
| compute_fn = _NoDiskComputeMinMaxSpec |
| ret = common.ComputeIPolicyDiskSizesViolation(self._MICRO_IPOL, [], |
| [], |
| _compute_fn=compute_fn) |
| self.assertEqual(ret, []) |
| |
| def testWrongTemplates(self): |
| compute_fn = _ValidateComputeMinMaxSpec |
| |
| ret = common.ComputeIPolicyDiskSizesViolation( |
| self._MICRO_IPOL, [1024], self.MakeDisks(constants.DT_DRBD8), |
| _compute_fn=compute_fn) |
| self.assertEqual(len(ret), 1) |
| self.assertTrue("Disk template" in ret[0]) |
| |
| def _AssertComputeViolation(self, ipolicy, disk_sizes, dev_types, |
| violations): |
| ret = common.ComputeIPolicyDiskSizesViolation( |
| ipolicy, disk_sizes, self.MakeDisks(*dev_types)) |
| self.assertEqual(len(ret), violations) |
| |
| def testWithIPolicy(self): |
| mem_size = 2048 |
| cpu_count = 2 |
| disk_count = 1 |
| disk_sizes = [512] |
| nic_count = 1 |
| spindle_use = 4 |
| disk_template = "mytemplate" |
| ispec = { |
| constants.ISPEC_MEM_SIZE: mem_size, |
| constants.ISPEC_CPU_COUNT: cpu_count, |
| constants.ISPEC_DISK_COUNT: disk_count, |
| constants.ISPEC_DISK_SIZE: disk_sizes[0], |
| constants.ISPEC_NIC_COUNT: nic_count, |
| constants.ISPEC_SPINDLE_USE: spindle_use, |
| } |
| |
| ipolicy = { |
| constants.ISPECS_MINMAX: [{ |
| constants.ISPECS_MIN: ispec, |
| constants.ISPECS_MAX: ispec, |
| }], |
| constants.IPOLICY_DTS: [disk_template], |
| } |
| |
| self._AssertComputeViolation(ipolicy, [512], [disk_template], 0) |
| self._AssertComputeViolation(ipolicy, [], [disk_template], 1) |
| self._AssertComputeViolation(ipolicy, [], [], 1) |
| self._AssertComputeViolation(ipolicy, [512, 512], |
| [disk_template, disk_template], 1) |
| self._AssertComputeViolation(ipolicy, [511], [disk_template], 1) |
| self._AssertComputeViolation(ipolicy, [513], [disk_template], 1) |
| |
| |
| class _StubComputeIPolicySpecViolation: |
| def __init__(self, mem_size, cpu_count, disk_count, nic_count, disk_sizes, |
| spindle_use, disk_template): |
| self.mem_size = mem_size |
| self.cpu_count = cpu_count |
| self.disk_count = disk_count |
| self.nic_count = nic_count |
| self.disk_sizes = disk_sizes |
| self.spindle_use = spindle_use |
| self.disk_template = disk_template |
| |
| def __call__(self, _, mem_size, cpu_count, disk_count, nic_count, disk_sizes, |
| spindle_use, disk_template): |
| assert self.mem_size == mem_size |
| assert self.cpu_count == cpu_count |
| assert self.disk_count == disk_count |
| assert self.nic_count == nic_count |
| assert self.disk_sizes == disk_sizes |
| assert self.spindle_use == spindle_use |
| assert self.disk_template == disk_template |
| |
| return [] |
| |
| |
| class _FakeConfigForComputeIPolicyInstanceViolation: |
| def __init__(self, be, excl_stor): |
| self.cluster = objects.Cluster(beparams={"default": be}) |
| self.excl_stor = excl_stor |
| |
| def GetClusterInfo(self): |
| return self.cluster |
| |
| def GetNodeInfo(self, _): |
| return {} |
| |
| def GetNdParams(self, _): |
| return { |
| constants.ND_EXCLUSIVE_STORAGE: self.excl_stor, |
| } |
| |
| def GetInstanceNodes(self, instance_uuid): |
| return ("pnode_uuid", ) |
| |
| def GetInstanceDisks(self, _): |
| return [objects.Disk(size=512, spindles=13, uuid="disk_uuid", |
| dev_type=constants.DT_PLAIN)] |
| |
| |
| class TestComputeIPolicyInstanceViolation(unittest.TestCase): |
| def setUp(self): |
| self.beparams = { |
| constants.BE_MAXMEM: 2048, |
| constants.BE_VCPUS: 2, |
| constants.BE_SPINDLE_USE: 4, |
| } |
| self.cfg = _FakeConfigForComputeIPolicyInstanceViolation( |
| self.beparams, False) |
| self.cfg_exclusive = _FakeConfigForComputeIPolicyInstanceViolation( |
| self.beparams, True) |
| self.stub = mock.MagicMock() |
| self.stub.return_value = [] |
| |
| def testPlain(self): |
| instance = objects.Instance(beparams=self.beparams, disks=["disk_uuid"], |
| nics=[], primary_node="pnode_uuid", |
| disk_template=constants.DT_PLAIN) |
| ret = common.ComputeIPolicyInstanceViolation( |
| NotImplemented, instance, self.cfg, _compute_fn=self.stub) |
| self.assertEqual(ret, []) |
| self.stub.assert_called_with(NotImplemented, 2048, 2, 1, 0, [512], 4, |
| [constants.DT_PLAIN]) |
| |
| def testNoBeparams(self): |
| instance = objects.Instance(beparams={}, disks=["disk_uuid"], |
| nics=[], primary_node="pnode_uuid", |
| disk_template=constants.DT_PLAIN) |
| ret = common.ComputeIPolicyInstanceViolation( |
| NotImplemented, instance, self.cfg, _compute_fn=self.stub) |
| self.assertEqual(ret, []) |
| self.stub.assert_called_with(NotImplemented, 2048, 2, 1, 0, [512], 4, |
| [constants.DT_PLAIN]) |
| |
| def testExclusiveStorage(self): |
| instance = objects.Instance(beparams=self.beparams, disks=["disk_uuid"], |
| nics=[], primary_node="pnode_uuid", |
| disk_template=constants.DT_PLAIN) |
| ret = common.ComputeIPolicyInstanceViolation( |
| NotImplemented, instance, self.cfg_exclusive, _compute_fn=self.stub) |
| self.assertEqual(ret, []) |
| self.stub.assert_called_with(NotImplemented, 2048, 2, 1, 0, [512], 13, |
| [constants.DT_PLAIN]) |
| |
| def testExclusiveStorageNoBeparams(self): |
| instance = objects.Instance(beparams={}, disks=["disk_uuid"], |
| nics=[], primary_node="pnode_uuid", |
| disk_template=constants.DT_PLAIN) |
| ret = common.ComputeIPolicyInstanceViolation( |
| NotImplemented, instance, self.cfg_exclusive, _compute_fn=self.stub) |
| self.assertEqual(ret, []) |
| self.stub.assert_called_with(NotImplemented, 2048, 2, 1, 0, [512], 13, |
| [constants.DT_PLAIN]) |
| |
| |
| class _CallRecorder: |
| def __init__(self, return_value=None): |
| self.called = False |
| self.return_value = return_value |
| |
| def __call__(self, *args): |
| self.called = True |
| return self.return_value |
| |
| |
| class TestComputeIPolicyNodeViolation(unittest.TestCase): |
| def setUp(self): |
| self.recorder = _CallRecorder(return_value=[]) |
| |
| def testSameGroup(self): |
| ret = instance_utils._ComputeIPolicyNodeViolation( |
| NotImplemented, |
| NotImplemented, |
| "foo", "foo", NotImplemented, |
| _compute_fn=self.recorder) |
| self.assertFalse(self.recorder.called) |
| self.assertEqual(ret, []) |
| |
| def testDifferentGroup(self): |
| ret = instance_utils._ComputeIPolicyNodeViolation( |
| NotImplemented, |
| NotImplemented, |
| "foo", "bar", NotImplemented, |
| _compute_fn=self.recorder) |
| self.assertTrue(self.recorder.called) |
| self.assertEqual(ret, []) |
| |
| |
| class TestDiskSizeInBytesToMebibytes(unittest.TestCase): |
| def testLessThanOneMebibyte(self): |
| for i in [1, 2, 7, 512, 1000, 1023]: |
| lu = _FakeLU() |
| result = instance_storage._DiskSizeInBytesToMebibytes(lu, i) |
| self.assertEqual(result, 1) |
| self.assertEqual(len(lu.warning_log), 1) |
| self.assertEqual(len(lu.warning_log[0]), 2) |
| (_, (warnsize, )) = lu.warning_log[0] |
| self.assertEqual(warnsize, (1024 * 1024) - i) |
| |
| def testEven(self): |
| for i in [1, 2, 7, 512, 1000, 1023]: |
| lu = _FakeLU() |
| result = instance_storage._DiskSizeInBytesToMebibytes(lu, |
| i * 1024 * 1024) |
| self.assertEqual(result, i) |
| self.assertFalse(lu.warning_log) |
| |
| def testLargeNumber(self): |
| for i in [1, 2, 7, 512, 1000, 1023, 2724, 12420]: |
| for j in [1, 2, 486, 326, 986, 1023]: |
| lu = _FakeLU() |
| size = (1024 * 1024 * i) + j |
| result = instance_storage._DiskSizeInBytesToMebibytes(lu, size) |
| self.assertEqual(result, i + 1, msg="Amount was not rounded up") |
| self.assertEqual(len(lu.warning_log), 1) |
| self.assertEqual(len(lu.warning_log[0]), 2) |
| (_, (warnsize, )) = lu.warning_log[0] |
| self.assertEqual(warnsize, (1024 * 1024) - j) |
| |
| |
| class _OpTestVerifyErrors(opcodes.OpCode): |
| OP_PARAMS = [ |
| ("debug_simulate_errors", False, ht.TBool, ""), |
| ("error_codes", False, ht.TBool, ""), |
| ("ignore_errors", |
| [], |
| ht.TListOf(ht.TElemOf(constants.CV_ALL_ECODES_STRINGS)), |
| "") |
| ] |
| |
| |
| class _LuTestVerifyErrors(verify._VerifyErrors): |
| def __init__(self, **kwargs): |
| super(_LuTestVerifyErrors, self).__init__() |
| self.op = _OpTestVerifyErrors(**kwargs) |
| self.op.Validate(True) |
| self.msglist = [] |
| self._feedback_fn = self.Feedback |
| self.bad = False |
| |
| # TODO: Cleanup calling conventions, make them explicit |
| def Feedback(self, *args): |
| |
| # TODO: Remove this once calling conventions are explicit. |
| # Feedback can be called with anything, we interpret ELogMessageList as |
| # messages that have to be individually added to the log list, but pushed |
| # in a single update. Other types are only transparently passed forward. |
| if len(args) == 1: |
| log_type = constants.ELOG_MESSAGE |
| log_msg = args[0] |
| else: |
| log_type, log_msg = args |
| |
| if log_type != constants.ELOG_MESSAGE_LIST: |
| log_msg = [log_msg] |
| |
| self.msglist.extend(log_msg) |
| |
| def DispatchCallError(self, which, *args, **kwargs): |
| if which: |
| self._Error(*args, **kwargs) |
| else: |
| self._ErrorIf(True, *args, **kwargs) |
| |
| def CallErrorIf(self, c, *args, **kwargs): |
| self._ErrorIf(c, *args, **kwargs) |
| |
| |
| class TestVerifyErrors(unittest.TestCase): |
| # Fake cluster-verify error code structures; we use two arbitary real error |
| # codes to pass validation of ignore_errors |
| (_, _ERR1ID, _) = constants.CV_ECLUSTERCFG |
| _NODESTR = "node" |
| _NODENAME = "mynode" |
| _ERR1CODE = (_NODESTR, _ERR1ID, "Error one") |
| (_, _ERR2ID, _) = constants.CV_ECLUSTERCERT |
| _INSTSTR = "instance" |
| _INSTNAME = "myinstance" |
| _ERR2CODE = (_INSTSTR, _ERR2ID, "Error two") |
| # Arguments used to call _Error() or _ErrorIf() |
| _ERR1ARGS = (_ERR1CODE, _NODENAME, "Error1 is %s", "an error") |
| _ERR2ARGS = (_ERR2CODE, _INSTNAME, "Error2 has no argument") |
| # Expected error messages |
| _ERR1MSG = _ERR1ARGS[2] % _ERR1ARGS[3] |
| _ERR2MSG = _ERR2ARGS[2] |
| |
| def testNoError(self): |
| lu = _LuTestVerifyErrors() |
| lu.CallErrorIf(False, self._ERR1CODE, *self._ERR1ARGS) |
| self.assertFalse(lu.bad) |
| self.assertFalse(lu.msglist) |
| |
| def _InitTest(self, **kwargs): |
| self.lu1 = _LuTestVerifyErrors(**kwargs) |
| self.lu2 = _LuTestVerifyErrors(**kwargs) |
| |
| def _CallError(self, *args, **kwargs): |
| # Check that _Error() and _ErrorIf() produce the same results |
| self.lu1.DispatchCallError(True, *args, **kwargs) |
| self.lu2.DispatchCallError(False, *args, **kwargs) |
| self.assertEqual(self.lu1.bad, self.lu2.bad) |
| self.assertEqual(self.lu1.msglist, self.lu2.msglist) |
| # Test-specific checks are made on one LU |
| return self.lu1 |
| |
| def _checkMsgCommon(self, logstr, errmsg, itype, item, warning): |
| self.assertTrue(errmsg in logstr) |
| if warning: |
| self.assertTrue("WARNING" in logstr) |
| else: |
| self.assertTrue("ERROR" in logstr) |
| self.assertTrue(itype in logstr) |
| self.assertTrue(item in logstr) |
| |
| def _checkMsg1(self, logstr, warning=False): |
| self._checkMsgCommon(logstr, self._ERR1MSG, self._NODESTR, |
| self._NODENAME, warning) |
| |
| def _checkMsg2(self, logstr, warning=False): |
| self._checkMsgCommon(logstr, self._ERR2MSG, self._INSTSTR, |
| self._INSTNAME, warning) |
| |
| def testPlain(self): |
| self._InitTest() |
| lu = self._CallError(*self._ERR1ARGS) |
| self.assertTrue(lu.bad) |
| self.assertEqual(len(lu.msglist), 1) |
| self._checkMsg1(lu.msglist[0]) |
| |
| def testMultiple(self): |
| self._InitTest() |
| self._CallError(*self._ERR1ARGS) |
| lu = self._CallError(*self._ERR2ARGS) |
| self.assertTrue(lu.bad) |
| self.assertEqual(len(lu.msglist), 2) |
| self._checkMsg1(lu.msglist[0]) |
| self._checkMsg2(lu.msglist[1]) |
| |
| def testIgnore(self): |
| self._InitTest(ignore_errors=[self._ERR1ID]) |
| lu = self._CallError(*self._ERR1ARGS) |
| self.assertFalse(lu.bad) |
| self.assertEqual(len(lu.msglist), 1) |
| self._checkMsg1(lu.msglist[0], warning=True) |
| |
| def testWarning(self): |
| self._InitTest() |
| lu = self._CallError(*self._ERR1ARGS, |
| code=_LuTestVerifyErrors.ETYPE_WARNING) |
| self.assertFalse(lu.bad) |
| self.assertEqual(len(lu.msglist), 1) |
| self._checkMsg1(lu.msglist[0], warning=True) |
| |
| def testWarning2(self): |
| self._InitTest() |
| self._CallError(*self._ERR1ARGS) |
| lu = self._CallError(*self._ERR2ARGS, |
| code=_LuTestVerifyErrors.ETYPE_WARNING) |
| self.assertTrue(lu.bad) |
| self.assertEqual(len(lu.msglist), 2) |
| self._checkMsg1(lu.msglist[0]) |
| self._checkMsg2(lu.msglist[1], warning=True) |
| |
| def testDebugSimulate(self): |
| lu = _LuTestVerifyErrors(debug_simulate_errors=True) |
| lu.CallErrorIf(False, *self._ERR1ARGS) |
| self.assertTrue(lu.bad) |
| self.assertEqual(len(lu.msglist), 1) |
| self._checkMsg1(lu.msglist[0]) |
| |
| def testErrCodes(self): |
| self._InitTest(error_codes=True) |
| lu = self._CallError(*self._ERR1ARGS) |
| self.assertTrue(lu.bad) |
| self.assertEqual(len(lu.msglist), 1) |
| self._checkMsg1(lu.msglist[0]) |
| self.assertTrue(self._ERR1ID in lu.msglist[0]) |
| |
| |
| class TestGetUpdatedIPolicy(unittest.TestCase): |
| """Tests for cmdlib._GetUpdatedIPolicy()""" |
| _OLD_CLUSTER_POLICY = { |
| constants.IPOLICY_VCPU_RATIO: 1.5, |
| constants.ISPECS_MINMAX: [ |
| { |
| constants.ISPECS_MIN: { |
| constants.ISPEC_MEM_SIZE: 32768, |
| constants.ISPEC_CPU_COUNT: 8, |
| constants.ISPEC_DISK_COUNT: 1, |
| constants.ISPEC_DISK_SIZE: 1024, |
| constants.ISPEC_NIC_COUNT: 1, |
| constants.ISPEC_SPINDLE_USE: 1, |
| }, |
| constants.ISPECS_MAX: { |
| constants.ISPEC_MEM_SIZE: 65536, |
| constants.ISPEC_CPU_COUNT: 10, |
| constants.ISPEC_DISK_COUNT: 5, |
| constants.ISPEC_DISK_SIZE: 1024 * 1024, |
| constants.ISPEC_NIC_COUNT: 3, |
| constants.ISPEC_SPINDLE_USE: 12, |
| }, |
| }, |
| constants.ISPECS_MINMAX_DEFAULTS, |
| ], |
| constants.ISPECS_STD: constants.IPOLICY_DEFAULTS[constants.ISPECS_STD], |
| } |
| _OLD_GROUP_POLICY = { |
| constants.IPOLICY_SPINDLE_RATIO: 2.5, |
| constants.ISPECS_MINMAX: [{ |
| constants.ISPECS_MIN: { |
| constants.ISPEC_MEM_SIZE: 128, |
| constants.ISPEC_CPU_COUNT: 1, |
| constants.ISPEC_DISK_COUNT: 1, |
| constants.ISPEC_DISK_SIZE: 1024, |
| constants.ISPEC_NIC_COUNT: 1, |
| constants.ISPEC_SPINDLE_USE: 1, |
| }, |
| constants.ISPECS_MAX: { |
| constants.ISPEC_MEM_SIZE: 32768, |
| constants.ISPEC_CPU_COUNT: 8, |
| constants.ISPEC_DISK_COUNT: 5, |
| constants.ISPEC_DISK_SIZE: 1024 * 1024, |
| constants.ISPEC_NIC_COUNT: 3, |
| constants.ISPEC_SPINDLE_USE: 12, |
| }, |
| }], |
| } |
| |
| def _TestSetSpecs(self, old_policy, isgroup): |
| diff_minmax = [{ |
| constants.ISPECS_MIN: { |
| constants.ISPEC_MEM_SIZE: 64, |
| constants.ISPEC_CPU_COUNT: 1, |
| constants.ISPEC_DISK_COUNT: 2, |
| constants.ISPEC_DISK_SIZE: 64, |
| constants.ISPEC_NIC_COUNT: 1, |
| constants.ISPEC_SPINDLE_USE: 1, |
| }, |
| constants.ISPECS_MAX: { |
| constants.ISPEC_MEM_SIZE: 16384, |
| constants.ISPEC_CPU_COUNT: 10, |
| constants.ISPEC_DISK_COUNT: 12, |
| constants.ISPEC_DISK_SIZE: 1024, |
| constants.ISPEC_NIC_COUNT: 9, |
| constants.ISPEC_SPINDLE_USE: 18, |
| }, |
| }] |
| diff_std = { |
| constants.ISPEC_DISK_COUNT: 10, |
| constants.ISPEC_DISK_SIZE: 512, |
| } |
| diff_policy = { |
| constants.ISPECS_MINMAX: diff_minmax |
| } |
| if not isgroup: |
| diff_policy[constants.ISPECS_STD] = diff_std |
| new_policy = common.GetUpdatedIPolicy(old_policy, diff_policy, |
| group_policy=isgroup) |
| |
| self.assertTrue(constants.ISPECS_MINMAX in new_policy) |
| self.assertEqual(new_policy[constants.ISPECS_MINMAX], diff_minmax) |
| for key in old_policy: |
| if not key in diff_policy: |
| self.assertTrue(key in new_policy) |
| self.assertEqual(new_policy[key], old_policy[key]) |
| |
| if not isgroup: |
| new_std = new_policy[constants.ISPECS_STD] |
| for key in diff_std: |
| self.assertTrue(key in new_std) |
| self.assertEqual(new_std[key], diff_std[key]) |
| old_std = old_policy.get(constants.ISPECS_STD, {}) |
| for key in old_std: |
| self.assertTrue(key in new_std) |
| if key not in diff_std: |
| self.assertEqual(new_std[key], old_std[key]) |
| |
| def _TestSet(self, old_policy, diff_policy, isgroup): |
| new_policy = common.GetUpdatedIPolicy(old_policy, diff_policy, |
| group_policy=isgroup) |
| for key in diff_policy: |
| self.assertTrue(key in new_policy) |
| self.assertEqual(new_policy[key], diff_policy[key]) |
| for key in old_policy: |
| if not key in diff_policy: |
| self.assertTrue(key in new_policy) |
| self.assertEqual(new_policy[key], old_policy[key]) |
| |
| def testSet(self): |
| diff_policy = { |
| constants.IPOLICY_VCPU_RATIO: 3, |
| constants.IPOLICY_DTS: [constants.DT_FILE], |
| } |
| self._TestSet(self._OLD_GROUP_POLICY, diff_policy, True) |
| self._TestSetSpecs(self._OLD_GROUP_POLICY, True) |
| self._TestSet({}, diff_policy, True) |
| self._TestSetSpecs({}, True) |
| self._TestSet(self._OLD_CLUSTER_POLICY, diff_policy, False) |
| self._TestSetSpecs(self._OLD_CLUSTER_POLICY, False) |
| |
| def testUnset(self): |
| old_policy = self._OLD_GROUP_POLICY |
| diff_policy = { |
| constants.IPOLICY_SPINDLE_RATIO: constants.VALUE_DEFAULT, |
| } |
| new_policy = common.GetUpdatedIPolicy(old_policy, diff_policy, |
| group_policy=True) |
| for key in diff_policy: |
| self.assertFalse(key in new_policy) |
| for key in old_policy: |
| if not key in diff_policy: |
| self.assertTrue(key in new_policy) |
| self.assertEqual(new_policy[key], old_policy[key]) |
| |
| self.assertRaises(errors.OpPrereqError, common.GetUpdatedIPolicy, |
| old_policy, diff_policy, group_policy=False) |
| |
| def testUnsetEmpty(self): |
| old_policy = {} |
| for key in constants.IPOLICY_ALL_KEYS: |
| diff_policy = { |
| key: constants.VALUE_DEFAULT, |
| } |
| new_policy = common.GetUpdatedIPolicy(old_policy, diff_policy, |
| group_policy=True) |
| self.assertEqual(new_policy, old_policy) |
| |
| def _TestInvalidKeys(self, old_policy, isgroup): |
| INVALID_KEY = "this_key_shouldnt_be_allowed" |
| INVALID_DICT = { |
| INVALID_KEY: 3, |
| } |
| invalid_policy = INVALID_DICT |
| self.assertRaises(errors.OpPrereqError, common.GetUpdatedIPolicy, |
| old_policy, invalid_policy, group_policy=isgroup) |
| invalid_ispecs = { |
| constants.ISPECS_MINMAX: [INVALID_DICT], |
| } |
| self.assertRaises(errors.TypeEnforcementError, common.GetUpdatedIPolicy, |
| old_policy, invalid_ispecs, group_policy=isgroup) |
| if isgroup: |
| invalid_for_group = { |
| constants.ISPECS_STD: constants.IPOLICY_DEFAULTS[constants.ISPECS_STD], |
| } |
| self.assertRaises(errors.OpPrereqError, common.GetUpdatedIPolicy, |
| old_policy, invalid_for_group, group_policy=isgroup) |
| good_ispecs = self._OLD_CLUSTER_POLICY[constants.ISPECS_MINMAX] |
| invalid_ispecs = copy.deepcopy(good_ispecs) |
| invalid_policy = { |
| constants.ISPECS_MINMAX: invalid_ispecs, |
| } |
| for minmax in invalid_ispecs: |
| for key in constants.ISPECS_MINMAX_KEYS: |
| ispec = minmax[key] |
| ispec[INVALID_KEY] = None |
| self.assertRaises(errors.TypeEnforcementError, |
| common.GetUpdatedIPolicy, old_policy, |
| invalid_policy, group_policy=isgroup) |
| del ispec[INVALID_KEY] |
| for par in constants.ISPECS_PARAMETERS: |
| oldv = ispec[par] |
| ispec[par] = "this_is_not_good" |
| self.assertRaises(errors.TypeEnforcementError, |
| common.GetUpdatedIPolicy, |
| old_policy, invalid_policy, group_policy=isgroup) |
| ispec[par] = oldv |
| # This is to make sure that no two errors were present during the tests |
| common.GetUpdatedIPolicy(old_policy, invalid_policy, |
| group_policy=isgroup) |
| |
| def testInvalidKeys(self): |
| self._TestInvalidKeys(self._OLD_GROUP_POLICY, True) |
| self._TestInvalidKeys(self._OLD_CLUSTER_POLICY, False) |
| |
| def testInvalidValues(self): |
| for par in (constants.IPOLICY_PARAMETERS | |
| frozenset([constants.IPOLICY_DTS])): |
| bad_policy = { |
| par: "invalid_value", |
| } |
| self.assertRaises(errors.OpPrereqError, common.GetUpdatedIPolicy, {}, |
| bad_policy, group_policy=True) |
| |
| |
| class TestCopyLockList(unittest.TestCase): |
| def test(self): |
| self.assertEqual(instance_utils.CopyLockList([]), []) |
| self.assertEqual(instance_utils.CopyLockList(None), None) |
| self.assertEqual(instance_utils.CopyLockList(locking.ALL_SET), |
| locking.ALL_SET) |
| |
| names = ["foo", "bar"] |
| output = instance_utils.CopyLockList(names) |
| self.assertEqual(names, output) |
| self.assertNotEqual(id(names), id(output), msg="List was not copied") |
| |
| |
| if __name__ == "__main__": |
| testutils.GanetiTestProgram() |