cmdlib: Extract storage related functionality
[ganeti-github.git] / test / py / ganeti.cmdlib_unittest.py
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2008, 2011, 2012, 2013 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21
22 """Script for unittesting the cmdlib module"""
23
24
25 import os
26 import unittest
27 import tempfile
28 import shutil
29 import operator
30 import itertools
31 import copy
32
33 from ganeti import constants
34 from ganeti import mcpu
35 from ganeti import cmdlib
36 from ganeti.cmdlib import cluster
37 from ganeti.cmdlib import group
38 from ganeti.cmdlib import instance
39 from ganeti.cmdlib import instance_storage
40 from ganeti.cmdlib import instance_utils
41 from ganeti.cmdlib import common
42 from ganeti.cmdlib import query
43 from ganeti import opcodes
44 from ganeti import errors
45 from ganeti import utils
46 from ganeti import luxi
47 from ganeti import ht
48 from ganeti import objects
49 from ganeti import compat
50 from ganeti import rpc
51 from ganeti import locking
52 from ganeti import pathutils
53 from ganeti.masterd import iallocator
54 from ganeti.hypervisor import hv_xen
55
56 import testutils
57 import mocks
58
59
60 class TestCertVerification(testutils.GanetiTestCase):
61 def setUp(self):
62 testutils.GanetiTestCase.setUp(self)
63
64 self.tmpdir = tempfile.mkdtemp()
65
66 def tearDown(self):
67 shutil.rmtree(self.tmpdir)
68
69 def testVerifyCertificate(self):
70 cluster._VerifyCertificate(testutils.TestDataFilename("cert1.pem"))
71
72 nonexist_filename = os.path.join(self.tmpdir, "does-not-exist")
73
74 (errcode, msg) = cluster._VerifyCertificate(nonexist_filename)
75 self.assertEqual(errcode, cluster.LUClusterVerifyConfig.ETYPE_ERROR)
76
77 # Try to load non-certificate file
78 invalid_cert = testutils.TestDataFilename("bdev-net.txt")
79 (errcode, msg) = cluster._VerifyCertificate(invalid_cert)
80 self.assertEqual(errcode, cluster.LUClusterVerifyConfig.ETYPE_ERROR)
81
82
83 class TestOpcodeParams(testutils.GanetiTestCase):
84 def testParamsStructures(self):
85 for op in sorted(mcpu.Processor.DISPATCH_TABLE):
86 lu = mcpu.Processor.DISPATCH_TABLE[op]
87 lu_name = lu.__name__
88 self.failIf(hasattr(lu, "_OP_REQP"),
89 msg=("LU '%s' has old-style _OP_REQP" % lu_name))
90 self.failIf(hasattr(lu, "_OP_DEFS"),
91 msg=("LU '%s' has old-style _OP_DEFS" % lu_name))
92 self.failIf(hasattr(lu, "_OP_PARAMS"),
93 msg=("LU '%s' has old-style _OP_PARAMS" % lu_name))
94
95
96 class TestIAllocatorChecks(testutils.GanetiTestCase):
97 def testFunction(self):
98 class TestLU(object):
99 def __init__(self, opcode):
100 self.cfg = mocks.FakeConfig()
101 self.op = opcode
102
103 class OpTest(opcodes.OpCode):
104 OP_PARAMS = [
105 ("iallocator", None, ht.NoType, None),
106 ("node", None, ht.NoType, None),
107 ]
108
109 default_iallocator = mocks.FakeConfig().GetDefaultIAllocator()
110 other_iallocator = default_iallocator + "_not"
111
112 op = OpTest()
113 lu = TestLU(op)
114
115 c_i = lambda: common._CheckIAllocatorOrNode(lu, "iallocator", "node")
116
117 # Neither node nor iallocator given
118 for n in (None, []):
119 op.iallocator = None
120 op.node = n
121 c_i()
122 self.assertEqual(lu.op.iallocator, default_iallocator)
123 self.assertEqual(lu.op.node, n)
124
125 # Both, iallocator and node given
126 for a in ("test", constants.DEFAULT_IALLOCATOR_SHORTCUT):
127 op.iallocator = a
128 op.node = "test"
129 self.assertRaises(errors.OpPrereqError, c_i)
130
131 # Only iallocator given
132 for n in (None, []):
133 op.iallocator = other_iallocator
134 op.node = n
135 c_i()
136 self.assertEqual(lu.op.iallocator, other_iallocator)
137 self.assertEqual(lu.op.node, n)
138
139 # Only node given
140 op.iallocator = None
141 op.node = "node"
142 c_i()
143 self.assertEqual(lu.op.iallocator, None)
144 self.assertEqual(lu.op.node, "node")
145
146 # Asked for default iallocator, no node given
147 op.iallocator = constants.DEFAULT_IALLOCATOR_SHORTCUT
148 op.node = None
149 c_i()
150 self.assertEqual(lu.op.iallocator, default_iallocator)
151 self.assertEqual(lu.op.node, None)
152
153 # No node, iallocator or default iallocator
154 op.iallocator = None
155 op.node = None
156 lu.cfg.GetDefaultIAllocator = lambda: None
157 self.assertRaises(errors.OpPrereqError, c_i)
158
159
160 class TestLUTestJqueue(unittest.TestCase):
161 def test(self):
162 self.assert_(cmdlib.LUTestJqueue._CLIENT_CONNECT_TIMEOUT <
163 (luxi.WFJC_TIMEOUT * 0.75),
164 msg=("Client timeout too high, might not notice bugs"
165 " in WaitForJobChange"))
166
167
168 class TestLUQuery(unittest.TestCase):
169 def test(self):
170 self.assertEqual(sorted(query._QUERY_IMPL.keys()),
171 sorted(constants.QR_VIA_OP))
172
173 assert constants.QR_NODE in constants.QR_VIA_OP
174 assert constants.QR_INSTANCE in constants.QR_VIA_OP
175
176 for i in constants.QR_VIA_OP:
177 self.assert_(query._GetQueryImplementation(i))
178
179 self.assertRaises(errors.OpPrereqError, query._GetQueryImplementation,
180 "")
181 self.assertRaises(errors.OpPrereqError, query._GetQueryImplementation,
182 "xyz")
183
184
185 class TestLUGroupAssignNodes(unittest.TestCase):
186
187 def testCheckAssignmentForSplitInstances(self):
188 node_data = dict((n, objects.Node(name=n, group=g))
189 for (n, g) in [("n1a", "g1"), ("n1b", "g1"),
190 ("n2a", "g2"), ("n2b", "g2"),
191 ("n3a", "g3"), ("n3b", "g3"),
192 ("n3c", "g3"),
193 ])
194
195 def Instance(name, pnode, snode):
196 if snode is None:
197 disks = []
198 disk_template = constants.DT_DISKLESS
199 else:
200 disks = [objects.Disk(dev_type=constants.LD_DRBD8,
201 logical_id=[pnode, snode, 1, 17, 17])]
202 disk_template = constants.DT_DRBD8
203
204 return objects.Instance(name=name, primary_node=pnode, disks=disks,
205 disk_template=disk_template)
206
207 instance_data = dict((name, Instance(name, pnode, snode))
208 for name, pnode, snode in [("inst1a", "n1a", "n1b"),
209 ("inst1b", "n1b", "n1a"),
210 ("inst2a", "n2a", "n2b"),
211 ("inst3a", "n3a", None),
212 ("inst3b", "n3b", "n1b"),
213 ("inst3c", "n3b", "n2b"),
214 ])
215
216 # Test first with the existing state.
217 (new, prev) = \
218 group.LUGroupAssignNodes.CheckAssignmentForSplitInstances([],
219 node_data,
220 instance_data)
221
222 self.assertEqual([], new)
223 self.assertEqual(set(["inst3b", "inst3c"]), set(prev))
224
225 # And now some changes.
226 (new, prev) = \
227 group.LUGroupAssignNodes.CheckAssignmentForSplitInstances([("n1b",
228 "g3")],
229 node_data,
230 instance_data)
231
232 self.assertEqual(set(["inst1a", "inst1b"]), set(new))
233 self.assertEqual(set(["inst3c"]), set(prev))
234
235
236 class TestClusterVerifySsh(unittest.TestCase):
237 def testMultipleGroups(self):
238 fn = cluster.LUClusterVerifyGroup._SelectSshCheckNodes
239 mygroupnodes = [
240 objects.Node(name="node20", group="my", offline=False),
241 objects.Node(name="node21", group="my", offline=False),
242 objects.Node(name="node22", group="my", offline=False),
243 objects.Node(name="node23", group="my", offline=False),
244 objects.Node(name="node24", group="my", offline=False),
245 objects.Node(name="node25", group="my", offline=False),
246 objects.Node(name="node26", group="my", offline=True),
247 ]
248 nodes = [
249 objects.Node(name="node1", group="g1", offline=True),
250 objects.Node(name="node2", group="g1", offline=False),
251 objects.Node(name="node3", group="g1", offline=False),
252 objects.Node(name="node4", group="g1", offline=True),
253 objects.Node(name="node5", group="g1", offline=False),
254 objects.Node(name="node10", group="xyz", offline=False),
255 objects.Node(name="node11", group="xyz", offline=False),
256 objects.Node(name="node40", group="alloff", offline=True),
257 objects.Node(name="node41", group="alloff", offline=True),
258 objects.Node(name="node50", group="aaa", offline=False),
259 ] + mygroupnodes
260 assert not utils.FindDuplicates(map(operator.attrgetter("name"), nodes))
261
262 (online, perhost) = fn(mygroupnodes, "my", nodes)
263 self.assertEqual(online, ["node%s" % i for i in range(20, 26)])
264 self.assertEqual(set(perhost.keys()), set(online))
265
266 self.assertEqual(perhost, {
267 "node20": ["node10", "node2", "node50"],
268 "node21": ["node11", "node3", "node50"],
269 "node22": ["node10", "node5", "node50"],
270 "node23": ["node11", "node2", "node50"],
271 "node24": ["node10", "node3", "node50"],
272 "node25": ["node11", "node5", "node50"],
273 })
274
275 def testSingleGroup(self):
276 fn = cluster.LUClusterVerifyGroup._SelectSshCheckNodes
277 nodes = [
278 objects.Node(name="node1", group="default", offline=True),
279 objects.Node(name="node2", group="default", offline=False),
280 objects.Node(name="node3", group="default", offline=False),
281 objects.Node(name="node4", group="default", offline=True),
282 ]
283 assert not utils.FindDuplicates(map(operator.attrgetter("name"), nodes))
284
285 (online, perhost) = fn(nodes, "default", nodes)
286 self.assertEqual(online, ["node2", "node3"])
287 self.assertEqual(set(perhost.keys()), set(online))
288
289 self.assertEqual(perhost, {
290 "node2": [],
291 "node3": [],
292 })
293
294
295 class TestClusterVerifyFiles(unittest.TestCase):
296 @staticmethod
297 def _FakeErrorIf(errors, cond, ecode, item, msg, *args, **kwargs):
298 assert ((ecode == constants.CV_ENODEFILECHECK and
299 ht.TNonEmptyString(item)) or
300 (ecode == constants.CV_ECLUSTERFILECHECK and
301 item is None))
302
303 if args:
304 msg = msg % args
305
306 if cond:
307 errors.append((item, msg))
308
309 _VerifyFiles = cluster.LUClusterVerifyGroup._VerifyFiles
310
311 def test(self):
312 errors = []
313 master_name = "master.example.com"
314 nodeinfo = [
315 objects.Node(name=master_name, offline=False, vm_capable=True),
316 objects.Node(name="node2.example.com", offline=False, vm_capable=True),
317 objects.Node(name="node3.example.com", master_candidate=True,
318 vm_capable=False),
319 objects.Node(name="node4.example.com", offline=False, vm_capable=True),
320 objects.Node(name="nodata.example.com", offline=False, vm_capable=True),
321 objects.Node(name="offline.example.com", offline=True),
322 ]
323 cluster = objects.Cluster(modify_etc_hosts=True,
324 enabled_hypervisors=[constants.HT_XEN_HVM])
325 files_all = set([
326 pathutils.CLUSTER_DOMAIN_SECRET_FILE,
327 pathutils.RAPI_CERT_FILE,
328 pathutils.RAPI_USERS_FILE,
329 ])
330 files_opt = set([
331 pathutils.RAPI_USERS_FILE,
332 hv_xen.XL_CONFIG_FILE,
333 pathutils.VNC_PASSWORD_FILE,
334 ])
335 files_mc = set([
336 pathutils.CLUSTER_CONF_FILE,
337 ])
338 files_vm = set([
339 hv_xen.XEND_CONFIG_FILE,
340 hv_xen.XL_CONFIG_FILE,
341 pathutils.VNC_PASSWORD_FILE,
342 ])
343 nvinfo = {
344 master_name: rpc.RpcResult(data=(True, {
345 constants.NV_FILELIST: {
346 pathutils.CLUSTER_CONF_FILE: "82314f897f38b35f9dab2f7c6b1593e0",
347 pathutils.RAPI_CERT_FILE: "babbce8f387bc082228e544a2146fee4",
348 pathutils.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
349 hv_xen.XEND_CONFIG_FILE: "b4a8a824ab3cac3d88839a9adeadf310",
350 hv_xen.XL_CONFIG_FILE: "77935cee92afd26d162f9e525e3d49b9"
351 }})),
352 "node2.example.com": rpc.RpcResult(data=(True, {
353 constants.NV_FILELIST: {
354 pathutils.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
355 hv_xen.XEND_CONFIG_FILE: "b4a8a824ab3cac3d88839a9adeadf310",
356 }
357 })),
358 "node3.example.com": rpc.RpcResult(data=(True, {
359 constants.NV_FILELIST: {
360 pathutils.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
361 pathutils.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
362 }
363 })),
364 "node4.example.com": rpc.RpcResult(data=(True, {
365 constants.NV_FILELIST: {
366 pathutils.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
367 pathutils.CLUSTER_CONF_FILE: "conf-a6d4b13e407867f7a7b4f0f232a8f527",
368 pathutils.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
369 pathutils.RAPI_USERS_FILE: "rapiusers-ea3271e8d810ef3",
370 hv_xen.XL_CONFIG_FILE: "77935cee92afd26d162f9e525e3d49b9"
371 }
372 })),
373 "nodata.example.com": rpc.RpcResult(data=(True, {})),
374 "offline.example.com": rpc.RpcResult(offline=True),
375 }
376 assert set(nvinfo.keys()) == set(map(operator.attrgetter("name"), nodeinfo))
377
378 self._VerifyFiles(compat.partial(self._FakeErrorIf, errors), nodeinfo,
379 master_name, nvinfo,
380 (files_all, files_opt, files_mc, files_vm))
381 self.assertEqual(sorted(errors), sorted([
382 (None, ("File %s found with 2 different checksums (variant 1 on"
383 " node2.example.com, node3.example.com, node4.example.com;"
384 " variant 2 on master.example.com)" % pathutils.RAPI_CERT_FILE)),
385 (None, ("File %s is missing from node(s) node2.example.com" %
386 pathutils.CLUSTER_DOMAIN_SECRET_FILE)),
387 (None, ("File %s should not exist on node(s) node4.example.com" %
388 pathutils.CLUSTER_CONF_FILE)),
389 (None, ("File %s is missing from node(s) node4.example.com" %
390 hv_xen.XEND_CONFIG_FILE)),
391 (None, ("File %s is missing from node(s) node3.example.com" %
392 pathutils.CLUSTER_CONF_FILE)),
393 (None, ("File %s found with 2 different checksums (variant 1 on"
394 " master.example.com; variant 2 on node4.example.com)" %
395 pathutils.CLUSTER_CONF_FILE)),
396 (None, ("File %s is optional, but it must exist on all or no nodes (not"
397 " found on master.example.com, node2.example.com,"
398 " node3.example.com)" % pathutils.RAPI_USERS_FILE)),
399 (None, ("File %s is optional, but it must exist on all or no nodes (not"
400 " found on node2.example.com)" % hv_xen.XL_CONFIG_FILE)),
401 ("nodata.example.com", "Node did not return file checksum data"),
402 ]))
403
404
405 class _FakeLU:
406 def __init__(self, cfg=NotImplemented, proc=NotImplemented,
407 rpc=NotImplemented):
408 self.warning_log = []
409 self.info_log = []
410 self.cfg = cfg
411 self.proc = proc
412 self.rpc = rpc
413
414 def LogWarning(self, text, *args):
415 self.warning_log.append((text, args))
416
417 def LogInfo(self, text, *args):
418 self.info_log.append((text, args))
419
420
421 class TestLoadNodeEvacResult(unittest.TestCase):
422 def testSuccess(self):
423 for moved in [[], [
424 ("inst20153.example.com", "grp2", ["nodeA4509", "nodeB2912"]),
425 ]]:
426 for early_release in [False, True]:
427 for use_nodes in [False, True]:
428 jobs = [
429 [opcodes.OpInstanceReplaceDisks().__getstate__()],
430 [opcodes.OpInstanceMigrate().__getstate__()],
431 ]
432
433 alloc_result = (moved, [], jobs)
434 assert iallocator._NEVAC_RESULT(alloc_result)
435
436 lu = _FakeLU()
437 result = common._LoadNodeEvacResult(lu, alloc_result,
438 early_release, use_nodes)
439
440 if moved:
441 (_, (info_args, )) = lu.info_log.pop(0)
442 for (instname, instgroup, instnodes) in moved:
443 self.assertTrue(instname in info_args)
444 if use_nodes:
445 for i in instnodes:
446 self.assertTrue(i in info_args)
447 else:
448 self.assertTrue(instgroup in info_args)
449
450 self.assertFalse(lu.info_log)
451 self.assertFalse(lu.warning_log)
452
453 for op in itertools.chain(*result):
454 if hasattr(op.__class__, "early_release"):
455 self.assertEqual(op.early_release, early_release)
456 else:
457 self.assertFalse(hasattr(op, "early_release"))
458
459 def testFailed(self):
460 alloc_result = ([], [
461 ("inst5191.example.com", "errormsg21178"),
462 ], [])
463 assert iallocator._NEVAC_RESULT(alloc_result)
464
465 lu = _FakeLU()
466 self.assertRaises(errors.OpExecError, common._LoadNodeEvacResult,
467 lu, alloc_result, False, False)
468 self.assertFalse(lu.info_log)
469 (_, (args, )) = lu.warning_log.pop(0)
470 self.assertTrue("inst5191.example.com" in args)
471 self.assertTrue("errormsg21178" in args)
472 self.assertFalse(lu.warning_log)
473
474
475 class TestUpdateAndVerifySubDict(unittest.TestCase):
476 def setUp(self):
477 self.type_check = {
478 "a": constants.VTYPE_INT,
479 "b": constants.VTYPE_STRING,
480 "c": constants.VTYPE_BOOL,
481 "d": constants.VTYPE_STRING,
482 }
483
484 def test(self):
485 old_test = {
486 "foo": {
487 "d": "blubb",
488 "a": 321,
489 },
490 "baz": {
491 "a": 678,
492 "b": "678",
493 "c": True,
494 },
495 }
496 test = {
497 "foo": {
498 "a": 123,
499 "b": "123",
500 "c": True,
501 },
502 "bar": {
503 "a": 321,
504 "b": "321",
505 "c": False,
506 },
507 }
508
509 mv = {
510 "foo": {
511 "a": 123,
512 "b": "123",
513 "c": True,
514 "d": "blubb"
515 },
516 "bar": {
517 "a": 321,
518 "b": "321",
519 "c": False,
520 },
521 "baz": {
522 "a": 678,
523 "b": "678",
524 "c": True,
525 },
526 }
527
528 verified = common._UpdateAndVerifySubDict(old_test, test, self.type_check)
529 self.assertEqual(verified, mv)
530
531 def testWrong(self):
532 test = {
533 "foo": {
534 "a": "blubb",
535 "b": "123",
536 "c": True,
537 },
538 "bar": {
539 "a": 321,
540 "b": "321",
541 "c": False,
542 },
543 }
544
545 self.assertRaises(errors.TypeEnforcementError,
546 common._UpdateAndVerifySubDict, {}, test,
547 self.type_check)
548
549
550 class TestHvStateHelper(unittest.TestCase):
551 def testWithoutOpData(self):
552 self.assertEqual(common._MergeAndVerifyHvState(None, NotImplemented),
553 None)
554
555 def testWithoutOldData(self):
556 new = {
557 constants.HT_XEN_PVM: {
558 constants.HVST_MEMORY_TOTAL: 4096,
559 },
560 }
561 self.assertEqual(common._MergeAndVerifyHvState(new, None), new)
562
563 def testWithWrongHv(self):
564 new = {
565 "i-dont-exist": {
566 constants.HVST_MEMORY_TOTAL: 4096,
567 },
568 }
569 self.assertRaises(errors.OpPrereqError, common._MergeAndVerifyHvState,
570 new, None)
571
572 class TestDiskStateHelper(unittest.TestCase):
573 def testWithoutOpData(self):
574 self.assertEqual(common._MergeAndVerifyDiskState(None, NotImplemented),
575 None)
576
577 def testWithoutOldData(self):
578 new = {
579 constants.LD_LV: {
580 "xenvg": {
581 constants.DS_DISK_RESERVED: 1024,
582 },
583 },
584 }
585 self.assertEqual(common._MergeAndVerifyDiskState(new, None), new)
586
587 def testWithWrongStorageType(self):
588 new = {
589 "i-dont-exist": {
590 "xenvg": {
591 constants.DS_DISK_RESERVED: 1024,
592 },
593 },
594 }
595 self.assertRaises(errors.OpPrereqError, common._MergeAndVerifyDiskState,
596 new, None)
597
598
599 class TestComputeMinMaxSpec(unittest.TestCase):
600 def setUp(self):
601 self.ispecs = {
602 constants.ISPECS_MAX: {
603 constants.ISPEC_MEM_SIZE: 512,
604 constants.ISPEC_DISK_SIZE: 1024,
605 },
606 constants.ISPECS_MIN: {
607 constants.ISPEC_MEM_SIZE: 128,
608 constants.ISPEC_DISK_COUNT: 1,
609 },
610 }
611
612 def testNoneValue(self):
613 self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_MEM_SIZE, None,
614 self.ispecs, None) is None)
615
616 def testAutoValue(self):
617 self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_MEM_SIZE, None,
618 self.ispecs,
619 constants.VALUE_AUTO) is None)
620
621 def testNotDefined(self):
622 self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_NIC_COUNT, None,
623 self.ispecs, 3) is None)
624
625 def testNoMinDefined(self):
626 self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_DISK_SIZE, None,
627 self.ispecs, 128) is None)
628
629 def testNoMaxDefined(self):
630 self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_DISK_COUNT,
631 None, self.ispecs, 16) is None)
632
633 def testOutOfRange(self):
634 for (name, val) in ((constants.ISPEC_MEM_SIZE, 64),
635 (constants.ISPEC_MEM_SIZE, 768),
636 (constants.ISPEC_DISK_SIZE, 4096),
637 (constants.ISPEC_DISK_COUNT, 0)):
638 min_v = self.ispecs[constants.ISPECS_MIN].get(name, val)
639 max_v = self.ispecs[constants.ISPECS_MAX].get(name, val)
640 self.assertEqual(common._ComputeMinMaxSpec(name, None,
641 self.ispecs, val),
642 "%s value %s is not in range [%s, %s]" %
643 (name, val,min_v, max_v))
644 self.assertEqual(common._ComputeMinMaxSpec(name, "1",
645 self.ispecs, val),
646 "%s/1 value %s is not in range [%s, %s]" %
647 (name, val,min_v, max_v))
648
649 def test(self):
650 for (name, val) in ((constants.ISPEC_MEM_SIZE, 256),
651 (constants.ISPEC_MEM_SIZE, 128),
652 (constants.ISPEC_MEM_SIZE, 512),
653 (constants.ISPEC_DISK_SIZE, 1024),
654 (constants.ISPEC_DISK_SIZE, 0),
655 (constants.ISPEC_DISK_COUNT, 1),
656 (constants.ISPEC_DISK_COUNT, 5)):
657 self.assertTrue(common._ComputeMinMaxSpec(name, None, self.ispecs, val)
658 is None)
659
660
661 def _ValidateComputeMinMaxSpec(name, *_):
662 assert name in constants.ISPECS_PARAMETERS
663 return None
664
665
666 def _NoDiskComputeMinMaxSpec(name, *_):
667 if name == constants.ISPEC_DISK_COUNT:
668 return name
669 else:
670 return None
671
672
673 class _SpecWrapper:
674 def __init__(self, spec):
675 self.spec = spec
676
677 def ComputeMinMaxSpec(self, *args):
678 return self.spec.pop(0)
679
680
681 class TestComputeIPolicySpecViolation(unittest.TestCase):
682 # Minimal policy accepted by _ComputeIPolicySpecViolation()
683 _MICRO_IPOL = {
684 constants.IPOLICY_DTS: [constants.DT_PLAIN, constants.DT_DISKLESS],
685 constants.ISPECS_MINMAX: [NotImplemented],
686 }
687
688 def test(self):
689 compute_fn = _ValidateComputeMinMaxSpec
690 ret = common._ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1,
691 [1024], 1, constants.DT_PLAIN,
692 _compute_fn=compute_fn)
693 self.assertEqual(ret, [])
694
695 def testDiskFull(self):
696 compute_fn = _NoDiskComputeMinMaxSpec
697 ret = common._ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1,
698 [1024], 1, constants.DT_PLAIN,
699 _compute_fn=compute_fn)
700 self.assertEqual(ret, [constants.ISPEC_DISK_COUNT])
701
702 def testDiskLess(self):
703 compute_fn = _NoDiskComputeMinMaxSpec
704 ret = common._ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1,
705 [1024], 1, constants.DT_DISKLESS,
706 _compute_fn=compute_fn)
707 self.assertEqual(ret, [])
708
709 def testWrongTemplates(self):
710 compute_fn = _ValidateComputeMinMaxSpec
711 ret = common._ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1,
712 [1024], 1, constants.DT_DRBD8,
713 _compute_fn=compute_fn)
714 self.assertEqual(len(ret), 1)
715 self.assertTrue("Disk template" in ret[0])
716
717 def testInvalidArguments(self):
718 self.assertRaises(AssertionError, common._ComputeIPolicySpecViolation,
719 self._MICRO_IPOL, 1024, 1, 1, 1, [], 1,
720 constants.DT_PLAIN,)
721
722 def testInvalidSpec(self):
723 spec = _SpecWrapper([None, False, "foo", None, "bar", None])
724 compute_fn = spec.ComputeMinMaxSpec
725 ret = common._ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1,
726 [1024], 1, constants.DT_PLAIN,
727 _compute_fn=compute_fn)
728 self.assertEqual(ret, ["foo", "bar"])
729 self.assertFalse(spec.spec)
730
731 def testWithIPolicy(self):
732 mem_size = 2048
733 cpu_count = 2
734 disk_count = 1
735 disk_sizes = [512]
736 nic_count = 1
737 spindle_use = 4
738 disk_template = "mytemplate"
739 ispec = {
740 constants.ISPEC_MEM_SIZE: mem_size,
741 constants.ISPEC_CPU_COUNT: cpu_count,
742 constants.ISPEC_DISK_COUNT: disk_count,
743 constants.ISPEC_DISK_SIZE: disk_sizes[0],
744 constants.ISPEC_NIC_COUNT: nic_count,
745 constants.ISPEC_SPINDLE_USE: spindle_use,
746 }
747 ipolicy1 = {
748 constants.ISPECS_MINMAX: [{
749 constants.ISPECS_MIN: ispec,
750 constants.ISPECS_MAX: ispec,
751 }],
752 constants.IPOLICY_DTS: [disk_template],
753 }
754 ispec_copy = copy.deepcopy(ispec)
755 ipolicy2 = {
756 constants.ISPECS_MINMAX: [
757 {
758 constants.ISPECS_MIN: ispec_copy,
759 constants.ISPECS_MAX: ispec_copy,
760 },
761 {
762 constants.ISPECS_MIN: ispec,
763 constants.ISPECS_MAX: ispec,
764 },
765 ],
766 constants.IPOLICY_DTS: [disk_template],
767 }
768 ipolicy3 = {
769 constants.ISPECS_MINMAX: [
770 {
771 constants.ISPECS_MIN: ispec,
772 constants.ISPECS_MAX: ispec,
773 },
774 {
775 constants.ISPECS_MIN: ispec_copy,
776 constants.ISPECS_MAX: ispec_copy,
777 },
778 ],
779 constants.IPOLICY_DTS: [disk_template],
780 }
781 def AssertComputeViolation(ipolicy, violations):
782 ret = common._ComputeIPolicySpecViolation(ipolicy, mem_size, cpu_count,
783 disk_count, nic_count,
784 disk_sizes, spindle_use,
785 disk_template)
786 self.assertEqual(len(ret), violations)
787
788 AssertComputeViolation(ipolicy1, 0)
789 AssertComputeViolation(ipolicy2, 0)
790 AssertComputeViolation(ipolicy3, 0)
791 for par in constants.ISPECS_PARAMETERS:
792 ispec[par] += 1
793 AssertComputeViolation(ipolicy1, 1)
794 AssertComputeViolation(ipolicy2, 0)
795 AssertComputeViolation(ipolicy3, 0)
796 ispec[par] -= 2
797 AssertComputeViolation(ipolicy1, 1)
798 AssertComputeViolation(ipolicy2, 0)
799 AssertComputeViolation(ipolicy3, 0)
800 ispec[par] += 1 # Restore
801 ipolicy1[constants.IPOLICY_DTS] = ["another_template"]
802 AssertComputeViolation(ipolicy1, 1)
803
804
805 class _StubComputeIPolicySpecViolation:
806 def __init__(self, mem_size, cpu_count, disk_count, nic_count, disk_sizes,
807 spindle_use, disk_template):
808 self.mem_size = mem_size
809 self.cpu_count = cpu_count
810 self.disk_count = disk_count
811 self.nic_count = nic_count
812 self.disk_sizes = disk_sizes
813 self.spindle_use = spindle_use
814 self.disk_template = disk_template
815
816 def __call__(self, _, mem_size, cpu_count, disk_count, nic_count, disk_sizes,
817 spindle_use, disk_template):
818 assert self.mem_size == mem_size
819 assert self.cpu_count == cpu_count
820 assert self.disk_count == disk_count
821 assert self.nic_count == nic_count
822 assert self.disk_sizes == disk_sizes
823 assert self.spindle_use == spindle_use
824 assert self.disk_template == disk_template
825
826 return []
827
828
829 class _FakeConfigForComputeIPolicyInstanceViolation:
830 def __init__(self, be):
831 self.cluster = objects.Cluster(beparams={"default": be})
832
833 def GetClusterInfo(self):
834 return self.cluster
835
836
837 class TestComputeIPolicyInstanceViolation(unittest.TestCase):
838 def test(self):
839 beparams = {
840 constants.BE_MAXMEM: 2048,
841 constants.BE_VCPUS: 2,
842 constants.BE_SPINDLE_USE: 4,
843 }
844 disks = [objects.Disk(size=512)]
845 cfg = _FakeConfigForComputeIPolicyInstanceViolation(beparams)
846 instance = objects.Instance(beparams=beparams, disks=disks, nics=[],
847 disk_template=constants.DT_PLAIN)
848 stub = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512], 4,
849 constants.DT_PLAIN)
850 ret = common._ComputeIPolicyInstanceViolation(NotImplemented, instance,
851 cfg, _compute_fn=stub)
852 self.assertEqual(ret, [])
853 instance2 = objects.Instance(beparams={}, disks=disks, nics=[],
854 disk_template=constants.DT_PLAIN)
855 ret = common._ComputeIPolicyInstanceViolation(NotImplemented, instance2,
856 cfg, _compute_fn=stub)
857 self.assertEqual(ret, [])
858
859
860 class TestComputeIPolicyInstanceSpecViolation(unittest.TestCase):
861 def test(self):
862 ispec = {
863 constants.ISPEC_MEM_SIZE: 2048,
864 constants.ISPEC_CPU_COUNT: 2,
865 constants.ISPEC_DISK_COUNT: 1,
866 constants.ISPEC_DISK_SIZE: [512],
867 constants.ISPEC_NIC_COUNT: 0,
868 constants.ISPEC_SPINDLE_USE: 1,
869 }
870 stub = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512], 1,
871 constants.DT_PLAIN)
872 ret = instance._ComputeIPolicyInstanceSpecViolation(NotImplemented, ispec,
873 constants.DT_PLAIN,
874 _compute_fn=stub)
875 self.assertEqual(ret, [])
876
877
878 class _CallRecorder:
879 def __init__(self, return_value=None):
880 self.called = False
881 self.return_value = return_value
882
883 def __call__(self, *args):
884 self.called = True
885 return self.return_value
886
887
888 class TestComputeIPolicyNodeViolation(unittest.TestCase):
889 def setUp(self):
890 self.recorder = _CallRecorder(return_value=[])
891
892 def testSameGroup(self):
893 ret = instance_utils._ComputeIPolicyNodeViolation(
894 NotImplemented,
895 NotImplemented,
896 "foo", "foo", NotImplemented,
897 _compute_fn=self.recorder)
898 self.assertFalse(self.recorder.called)
899 self.assertEqual(ret, [])
900
901 def testDifferentGroup(self):
902 ret = instance_utils._ComputeIPolicyNodeViolation(
903 NotImplemented,
904 NotImplemented,
905 "foo", "bar", NotImplemented,
906 _compute_fn=self.recorder)
907 self.assertTrue(self.recorder.called)
908 self.assertEqual(ret, [])
909
910
911 class _FakeConfigForTargetNodeIPolicy:
912 def __init__(self, node_info=NotImplemented):
913 self._node_info = node_info
914
915 def GetNodeInfo(self, _):
916 return self._node_info
917
918
919 class TestCheckTargetNodeIPolicy(unittest.TestCase):
920 def setUp(self):
921 self.instance = objects.Instance(primary_node="blubb")
922 self.target_node = objects.Node(group="bar")
923 node_info = objects.Node(group="foo")
924 fake_cfg = _FakeConfigForTargetNodeIPolicy(node_info=node_info)
925 self.lu = _FakeLU(cfg=fake_cfg)
926
927 def testNoViolation(self):
928 compute_recoder = _CallRecorder(return_value=[])
929 instance._CheckTargetNodeIPolicy(self.lu, NotImplemented, self.instance,
930 self.target_node, NotImplemented,
931 _compute_fn=compute_recoder)
932 self.assertTrue(compute_recoder.called)
933 self.assertEqual(self.lu.warning_log, [])
934
935 def testNoIgnore(self):
936 compute_recoder = _CallRecorder(return_value=["mem_size not in range"])
937 self.assertRaises(errors.OpPrereqError, instance._CheckTargetNodeIPolicy,
938 self.lu, NotImplemented, self.instance,
939 self.target_node, NotImplemented,
940 _compute_fn=compute_recoder)
941 self.assertTrue(compute_recoder.called)
942 self.assertEqual(self.lu.warning_log, [])
943
944 def testIgnoreViolation(self):
945 compute_recoder = _CallRecorder(return_value=["mem_size not in range"])
946 instance._CheckTargetNodeIPolicy(self.lu, NotImplemented, self.instance,
947 self.target_node, NotImplemented,
948 ignore=True, _compute_fn=compute_recoder)
949 self.assertTrue(compute_recoder.called)
950 msg = ("Instance does not meet target node group's (bar) instance policy:"
951 " mem_size not in range")
952 self.assertEqual(self.lu.warning_log, [(msg, ())])
953
954
955 class TestApplyContainerMods(unittest.TestCase):
956 def testEmptyContainer(self):
957 container = []
958 chgdesc = []
959 instance.ApplyContainerMods("test", container, chgdesc, [], None, None,
960 None)
961 self.assertEqual(container, [])
962 self.assertEqual(chgdesc, [])
963
964 def testAdd(self):
965 container = []
966 chgdesc = []
967 mods = instance.PrepareContainerMods([
968 (constants.DDM_ADD, -1, "Hello"),
969 (constants.DDM_ADD, -1, "World"),
970 (constants.DDM_ADD, 0, "Start"),
971 (constants.DDM_ADD, -1, "End"),
972 ], None)
973 instance.ApplyContainerMods("test", container, chgdesc, mods,
974 None, None, None)
975 self.assertEqual(container, ["Start", "Hello", "World", "End"])
976 self.assertEqual(chgdesc, [])
977
978 mods = instance.PrepareContainerMods([
979 (constants.DDM_ADD, 0, "zero"),
980 (constants.DDM_ADD, 3, "Added"),
981 (constants.DDM_ADD, 5, "four"),
982 (constants.DDM_ADD, 7, "xyz"),
983 ], None)
984 instance.ApplyContainerMods("test", container, chgdesc, mods,
985 None, None, None)
986 self.assertEqual(container,
987 ["zero", "Start", "Hello", "Added", "World", "four",
988 "End", "xyz"])
989 self.assertEqual(chgdesc, [])
990
991 for idx in [-2, len(container) + 1]:
992 mods = instance.PrepareContainerMods([
993 (constants.DDM_ADD, idx, "error"),
994 ], None)
995 self.assertRaises(IndexError, instance.ApplyContainerMods,
996 "test", container, None, mods, None, None, None)
997
998 def testRemoveError(self):
999 for idx in [0, 1, 2, 100, -1, -4]:
1000 mods = instance.PrepareContainerMods([
1001 (constants.DDM_REMOVE, idx, None),
1002 ], None)
1003 self.assertRaises(IndexError, instance.ApplyContainerMods,
1004 "test", [], None, mods, None, None, None)
1005
1006 mods = instance.PrepareContainerMods([
1007 (constants.DDM_REMOVE, 0, object()),
1008 ], None)
1009 self.assertRaises(AssertionError, instance.ApplyContainerMods,
1010 "test", [""], None, mods, None, None, None)
1011
1012 def testAddError(self):
1013 for idx in range(-100, -1) + [100]:
1014 mods = instance.PrepareContainerMods([
1015 (constants.DDM_ADD, idx, None),
1016 ], None)
1017 self.assertRaises(IndexError, instance.ApplyContainerMods,
1018 "test", [], None, mods, None, None, None)
1019
1020 def testRemove(self):
1021 container = ["item 1", "item 2"]
1022 mods = instance.PrepareContainerMods([
1023 (constants.DDM_ADD, -1, "aaa"),
1024 (constants.DDM_REMOVE, -1, None),
1025 (constants.DDM_ADD, -1, "bbb"),
1026 ], None)
1027 chgdesc = []
1028 instance.ApplyContainerMods("test", container, chgdesc, mods,
1029 None, None, None)
1030 self.assertEqual(container, ["item 1", "item 2", "bbb"])
1031 self.assertEqual(chgdesc, [
1032 ("test/2", "remove"),
1033 ])
1034
1035 def testModify(self):
1036 container = ["item 1", "item 2"]
1037 mods = instance.PrepareContainerMods([
1038 (constants.DDM_MODIFY, -1, "a"),
1039 (constants.DDM_MODIFY, 0, "b"),
1040 (constants.DDM_MODIFY, 1, "c"),
1041 ], None)
1042 chgdesc = []
1043 instance.ApplyContainerMods("test", container, chgdesc, mods,
1044 None, None, None)
1045 self.assertEqual(container, ["item 1", "item 2"])
1046 self.assertEqual(chgdesc, [])
1047
1048 for idx in [-2, len(container) + 1]:
1049 mods = instance.PrepareContainerMods([
1050 (constants.DDM_MODIFY, idx, "error"),
1051 ], None)
1052 self.assertRaises(IndexError, instance.ApplyContainerMods,
1053 "test", container, None, mods, None, None, None)
1054
1055 class _PrivateData:
1056 def __init__(self):
1057 self.data = None
1058
1059 @staticmethod
1060 def _CreateTestFn(idx, params, private):
1061 private.data = ("add", idx, params)
1062 return ((100 * idx, params), [
1063 ("test/%s" % idx, hex(idx)),
1064 ])
1065
1066 @staticmethod
1067 def _ModifyTestFn(idx, item, params, private):
1068 private.data = ("modify", idx, params)
1069 return [
1070 ("test/%s" % idx, "modify %s" % params),
1071 ]
1072
1073 @staticmethod
1074 def _RemoveTestFn(idx, item, private):
1075 private.data = ("remove", idx, item)
1076
1077 def testAddWithCreateFunction(self):
1078 container = []
1079 chgdesc = []
1080 mods = instance.PrepareContainerMods([
1081 (constants.DDM_ADD, -1, "Hello"),
1082 (constants.DDM_ADD, -1, "World"),
1083 (constants.DDM_ADD, 0, "Start"),
1084 (constants.DDM_ADD, -1, "End"),
1085 (constants.DDM_REMOVE, 2, None),
1086 (constants.DDM_MODIFY, -1, "foobar"),
1087 (constants.DDM_REMOVE, 2, None),
1088 (constants.DDM_ADD, 1, "More"),
1089 ], self._PrivateData)
1090 instance.ApplyContainerMods("test", container, chgdesc, mods,
1091 self._CreateTestFn, self._ModifyTestFn,
1092 self._RemoveTestFn)
1093 self.assertEqual(container, [
1094 (000, "Start"),
1095 (100, "More"),
1096 (000, "Hello"),
1097 ])
1098 self.assertEqual(chgdesc, [
1099 ("test/0", "0x0"),
1100 ("test/1", "0x1"),
1101 ("test/0", "0x0"),
1102 ("test/3", "0x3"),
1103 ("test/2", "remove"),
1104 ("test/2", "modify foobar"),
1105 ("test/2", "remove"),
1106 ("test/1", "0x1")
1107 ])
1108 self.assertTrue(compat.all(op == private.data[0]
1109 for (op, _, _, private) in mods))
1110 self.assertEqual([private.data for (op, _, _, private) in mods], [
1111 ("add", 0, "Hello"),
1112 ("add", 1, "World"),
1113 ("add", 0, "Start"),
1114 ("add", 3, "End"),
1115 ("remove", 2, (100, "World")),
1116 ("modify", 2, "foobar"),
1117 ("remove", 2, (300, "End")),
1118 ("add", 1, "More"),
1119 ])
1120
1121
1122 class _FakeConfigForGenDiskTemplate:
1123 def __init__(self):
1124 self._unique_id = itertools.count()
1125 self._drbd_minor = itertools.count(20)
1126 self._port = itertools.count(constants.FIRST_DRBD_PORT)
1127 self._secret = itertools.count()
1128
1129 def GetVGName(self):
1130 return "testvg"
1131
1132 def GenerateUniqueID(self, ec_id):
1133 return "ec%s-uq%s" % (ec_id, self._unique_id.next())
1134
1135 def AllocateDRBDMinor(self, nodes, instance):
1136 return [self._drbd_minor.next()
1137 for _ in nodes]
1138
1139 def AllocatePort(self):
1140 return self._port.next()
1141
1142 def GenerateDRBDSecret(self, ec_id):
1143 return "ec%s-secret%s" % (ec_id, self._secret.next())
1144
1145 def GetInstanceInfo(self, _):
1146 return "foobar"
1147
1148
1149 class _FakeProcForGenDiskTemplate:
1150 def GetECId(self):
1151 return 0
1152
1153
1154 class TestGenerateDiskTemplate(unittest.TestCase):
1155 def setUp(self):
1156 nodegroup = objects.NodeGroup(name="ng")
1157 nodegroup.UpgradeConfig()
1158
1159 cfg = _FakeConfigForGenDiskTemplate()
1160 proc = _FakeProcForGenDiskTemplate()
1161
1162 self.lu = _FakeLU(cfg=cfg, proc=proc)
1163 self.nodegroup = nodegroup
1164
1165 @staticmethod
1166 def GetDiskParams():
1167 return copy.deepcopy(constants.DISK_DT_DEFAULTS)
1168
1169 def testWrongDiskTemplate(self):
1170 gdt = instance._GenerateDiskTemplate
1171 disk_template = "##unknown##"
1172
1173 assert disk_template not in constants.DISK_TEMPLATES
1174
1175 self.assertRaises(errors.ProgrammerError, gdt, self.lu, disk_template,
1176 "inst26831.example.com", "node30113.example.com", [], [],
1177 NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1178 self.GetDiskParams())
1179
1180 def testDiskless(self):
1181 gdt = instance._GenerateDiskTemplate
1182
1183 result = gdt(self.lu, constants.DT_DISKLESS, "inst27734.example.com",
1184 "node30113.example.com", [], [],
1185 NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1186 self.GetDiskParams())
1187 self.assertEqual(result, [])
1188
1189 def _TestTrivialDisk(self, template, disk_info, base_index, exp_dev_type,
1190 file_storage_dir=NotImplemented,
1191 file_driver=NotImplemented,
1192 req_file_storage=NotImplemented,
1193 req_shr_file_storage=NotImplemented):
1194 gdt = instance._GenerateDiskTemplate
1195
1196 map(lambda params: utils.ForceDictType(params,
1197 constants.IDISK_PARAMS_TYPES),
1198 disk_info)
1199
1200 # Check if non-empty list of secondaries is rejected
1201 self.assertRaises(errors.ProgrammerError, gdt, self.lu,
1202 template, "inst25088.example.com",
1203 "node185.example.com", ["node323.example.com"], [],
1204 NotImplemented, NotImplemented, base_index,
1205 self.lu.LogInfo, self.GetDiskParams(),
1206 _req_file_storage=req_file_storage,
1207 _req_shr_file_storage=req_shr_file_storage)
1208
1209 result = gdt(self.lu, template, "inst21662.example.com",
1210 "node21741.example.com", [],
1211 disk_info, file_storage_dir, file_driver, base_index,
1212 self.lu.LogInfo, self.GetDiskParams(),
1213 _req_file_storage=req_file_storage,
1214 _req_shr_file_storage=req_shr_file_storage)
1215
1216 for (idx, disk) in enumerate(result):
1217 self.assertTrue(isinstance(disk, objects.Disk))
1218 self.assertEqual(disk.dev_type, exp_dev_type)
1219 self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
1220 self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
1221 self.assertTrue(disk.children is None)
1222
1223 self._CheckIvNames(result, base_index, base_index + len(disk_info))
1224 instance._UpdateIvNames(base_index, result)
1225 self._CheckIvNames(result, base_index, base_index + len(disk_info))
1226
1227 return result
1228
1229 def _CheckIvNames(self, disks, base_index, end_index):
1230 self.assertEqual(map(operator.attrgetter("iv_name"), disks),
1231 ["disk/%s" % i for i in range(base_index, end_index)])
1232
1233 def testPlain(self):
1234 disk_info = [{
1235 constants.IDISK_SIZE: 1024,
1236 constants.IDISK_MODE: constants.DISK_RDWR,
1237 }, {
1238 constants.IDISK_SIZE: 4096,
1239 constants.IDISK_VG: "othervg",
1240 constants.IDISK_MODE: constants.DISK_RDWR,
1241 }]
1242
1243 result = self._TestTrivialDisk(constants.DT_PLAIN, disk_info, 3,
1244 constants.LD_LV)
1245
1246 self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1247 ("testvg", "ec0-uq0.disk3"),
1248 ("othervg", "ec0-uq1.disk4"),
1249 ])
1250
1251 @staticmethod
1252 def _AllowFileStorage():
1253 pass
1254
1255 @staticmethod
1256 def _ForbidFileStorage():
1257 raise errors.OpPrereqError("Disallowed in test")
1258
1259 def testFile(self):
1260 self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
1261 constants.DT_FILE, [], 0, NotImplemented,
1262 req_file_storage=self._ForbidFileStorage)
1263 self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
1264 constants.DT_SHARED_FILE, [], 0, NotImplemented,
1265 req_shr_file_storage=self._ForbidFileStorage)
1266
1267 for disk_template in [constants.DT_FILE, constants.DT_SHARED_FILE]:
1268 disk_info = [{
1269 constants.IDISK_SIZE: 80 * 1024,
1270 constants.IDISK_MODE: constants.DISK_RDONLY,
1271 }, {
1272 constants.IDISK_SIZE: 4096,
1273 constants.IDISK_MODE: constants.DISK_RDWR,
1274 }, {
1275 constants.IDISK_SIZE: 6 * 1024,
1276 constants.IDISK_MODE: constants.DISK_RDWR,
1277 }]
1278
1279 result = self._TestTrivialDisk(disk_template, disk_info, 2,
1280 constants.LD_FILE, file_storage_dir="/tmp",
1281 file_driver=constants.FD_BLKTAP,
1282 req_file_storage=self._AllowFileStorage,
1283 req_shr_file_storage=self._AllowFileStorage)
1284
1285 self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1286 (constants.FD_BLKTAP, "/tmp/disk2"),
1287 (constants.FD_BLKTAP, "/tmp/disk3"),
1288 (constants.FD_BLKTAP, "/tmp/disk4"),
1289 ])
1290
1291 def testBlock(self):
1292 disk_info = [{
1293 constants.IDISK_SIZE: 8 * 1024,
1294 constants.IDISK_MODE: constants.DISK_RDWR,
1295 constants.IDISK_ADOPT: "/tmp/some/block/dev",
1296 }]
1297
1298 result = self._TestTrivialDisk(constants.DT_BLOCK, disk_info, 10,
1299 constants.LD_BLOCKDEV)
1300
1301 self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1302 (constants.BLOCKDEV_DRIVER_MANUAL, "/tmp/some/block/dev"),
1303 ])
1304
1305 def testRbd(self):
1306 disk_info = [{
1307 constants.IDISK_SIZE: 8 * 1024,
1308 constants.IDISK_MODE: constants.DISK_RDONLY,
1309 }, {
1310 constants.IDISK_SIZE: 100 * 1024,
1311 constants.IDISK_MODE: constants.DISK_RDWR,
1312 }]
1313
1314 result = self._TestTrivialDisk(constants.DT_RBD, disk_info, 0,
1315 constants.LD_RBD)
1316
1317 self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1318 ("rbd", "ec0-uq0.rbd.disk0"),
1319 ("rbd", "ec0-uq1.rbd.disk1"),
1320 ])
1321
1322 def testDrbd8(self):
1323 gdt = instance._GenerateDiskTemplate
1324 drbd8_defaults = constants.DISK_LD_DEFAULTS[constants.LD_DRBD8]
1325 drbd8_default_metavg = drbd8_defaults[constants.LDP_DEFAULT_METAVG]
1326
1327 disk_info = [{
1328 constants.IDISK_SIZE: 1024,
1329 constants.IDISK_MODE: constants.DISK_RDWR,
1330 }, {
1331 constants.IDISK_SIZE: 100 * 1024,
1332 constants.IDISK_MODE: constants.DISK_RDONLY,
1333 constants.IDISK_METAVG: "metavg",
1334 }, {
1335 constants.IDISK_SIZE: 4096,
1336 constants.IDISK_MODE: constants.DISK_RDWR,
1337 constants.IDISK_VG: "vgxyz",
1338 },
1339 ]
1340
1341 exp_logical_ids = [[
1342 (self.lu.cfg.GetVGName(), "ec0-uq0.disk0_data"),
1343 (drbd8_default_metavg, "ec0-uq0.disk0_meta"),
1344 ], [
1345 (self.lu.cfg.GetVGName(), "ec0-uq1.disk1_data"),
1346 ("metavg", "ec0-uq1.disk1_meta"),
1347 ], [
1348 ("vgxyz", "ec0-uq2.disk2_data"),
1349 (drbd8_default_metavg, "ec0-uq2.disk2_meta"),
1350 ]]
1351
1352 assert len(exp_logical_ids) == len(disk_info)
1353
1354 map(lambda params: utils.ForceDictType(params,
1355 constants.IDISK_PARAMS_TYPES),
1356 disk_info)
1357
1358 # Check if empty list of secondaries is rejected
1359 self.assertRaises(errors.ProgrammerError, gdt, self.lu, constants.DT_DRBD8,
1360 "inst827.example.com", "node1334.example.com", [],
1361 disk_info, NotImplemented, NotImplemented, 0,
1362 self.lu.LogInfo, self.GetDiskParams())
1363
1364 result = gdt(self.lu, constants.DT_DRBD8, "inst827.example.com",
1365 "node1334.example.com", ["node12272.example.com"],
1366 disk_info, NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1367 self.GetDiskParams())
1368
1369 for (idx, disk) in enumerate(result):
1370 self.assertTrue(isinstance(disk, objects.Disk))
1371 self.assertEqual(disk.dev_type, constants.LD_DRBD8)
1372 self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
1373 self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
1374
1375 for child in disk.children:
1376 self.assertTrue(isinstance(disk, objects.Disk))
1377 self.assertEqual(child.dev_type, constants.LD_LV)
1378 self.assertTrue(child.children is None)
1379
1380 self.assertEqual(map(operator.attrgetter("logical_id"), disk.children),
1381 exp_logical_ids[idx])
1382
1383 self.assertEqual(len(disk.children), 2)
1384 self.assertEqual(disk.children[0].size, disk.size)
1385 self.assertEqual(disk.children[1].size, constants.DRBD_META_SIZE)
1386
1387 self._CheckIvNames(result, 0, len(disk_info))
1388 instance._UpdateIvNames(0, result)
1389 self._CheckIvNames(result, 0, len(disk_info))
1390
1391 self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1392 ("node1334.example.com", "node12272.example.com",
1393 constants.FIRST_DRBD_PORT, 20, 21, "ec0-secret0"),
1394 ("node1334.example.com", "node12272.example.com",
1395 constants.FIRST_DRBD_PORT + 1, 22, 23, "ec0-secret1"),
1396 ("node1334.example.com", "node12272.example.com",
1397 constants.FIRST_DRBD_PORT + 2, 24, 25, "ec0-secret2"),
1398 ])
1399
1400
1401 class _ConfigForDiskWipe:
1402 def __init__(self, exp_node):
1403 self._exp_node = exp_node
1404
1405 def SetDiskID(self, device, node):
1406 assert isinstance(device, objects.Disk)
1407 assert node == self._exp_node
1408
1409
1410 class _RpcForDiskWipe:
1411 def __init__(self, exp_node, pause_cb, wipe_cb):
1412 self._exp_node = exp_node
1413 self._pause_cb = pause_cb
1414 self._wipe_cb = wipe_cb
1415
1416 def call_blockdev_pause_resume_sync(self, node, disks, pause):
1417 assert node == self._exp_node
1418 return rpc.RpcResult(data=self._pause_cb(disks, pause))
1419
1420 def call_blockdev_wipe(self, node, bdev, offset, size):
1421 assert node == self._exp_node
1422 return rpc.RpcResult(data=self._wipe_cb(bdev, offset, size))
1423
1424
1425 class _DiskPauseTracker:
1426 def __init__(self):
1427 self.history = []
1428
1429 def __call__(self, (disks, instance), pause):
1430 assert not (set(disks) - set(instance.disks))
1431
1432 self.history.extend((i.logical_id, i.size, pause)
1433 for i in disks)
1434
1435 return (True, [True] * len(disks))
1436
1437
1438 class _DiskWipeProgressTracker:
1439 def __init__(self, start_offset):
1440 self._start_offset = start_offset
1441 self.progress = {}
1442
1443 def __call__(self, (disk, _), offset, size):
1444 assert isinstance(offset, (long, int))
1445 assert isinstance(size, (long, int))
1446
1447 max_chunk_size = (disk.size / 100.0 * constants.MIN_WIPE_CHUNK_PERCENT)
1448
1449 assert offset >= self._start_offset
1450 assert (offset + size) <= disk.size
1451
1452 assert size > 0
1453 assert size <= constants.MAX_WIPE_CHUNK
1454 assert size <= max_chunk_size
1455
1456 assert offset == self._start_offset or disk.logical_id in self.progress
1457
1458 # Keep track of progress
1459 cur_progress = self.progress.setdefault(disk.logical_id, self._start_offset)
1460
1461 assert cur_progress == offset
1462
1463 # Record progress
1464 self.progress[disk.logical_id] += size
1465
1466 return (True, None)
1467
1468
1469 class TestWipeDisks(unittest.TestCase):
1470 def _FailingPauseCb(self, (disks, _), pause):
1471 self.assertEqual(len(disks), 3)
1472 self.assertTrue(pause)
1473 # Simulate an RPC error
1474 return (False, "error")
1475
1476 def testPauseFailure(self):
1477 node_name = "node1372.example.com"
1478
1479 lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, self._FailingPauseCb,
1480 NotImplemented),
1481 cfg=_ConfigForDiskWipe(node_name))
1482
1483 disks = [
1484 objects.Disk(dev_type=constants.LD_LV),
1485 objects.Disk(dev_type=constants.LD_LV),
1486 objects.Disk(dev_type=constants.LD_LV),
1487 ]
1488
1489 inst = objects.Instance(name="inst21201",
1490 primary_node=node_name,
1491 disk_template=constants.DT_PLAIN,
1492 disks=disks)
1493
1494 self.assertRaises(errors.OpExecError, instance._WipeDisks, lu, inst)
1495
1496 def _FailingWipeCb(self, (disk, _), offset, size):
1497 # This should only ever be called for the first disk
1498 self.assertEqual(disk.logical_id, "disk0")
1499 return (False, None)
1500
1501 def testFailingWipe(self):
1502 node_name = "node13445.example.com"
1503 pt = _DiskPauseTracker()
1504
1505 lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, pt, self._FailingWipeCb),
1506 cfg=_ConfigForDiskWipe(node_name))
1507
1508 disks = [
1509 objects.Disk(dev_type=constants.LD_LV, logical_id="disk0",
1510 size=100 * 1024),
1511 objects.Disk(dev_type=constants.LD_LV, logical_id="disk1",
1512 size=500 * 1024),
1513 objects.Disk(dev_type=constants.LD_LV, logical_id="disk2", size=256),
1514 ]
1515
1516 inst = objects.Instance(name="inst562",
1517 primary_node=node_name,
1518 disk_template=constants.DT_PLAIN,
1519 disks=disks)
1520
1521 try:
1522 instance._WipeDisks(lu, inst)
1523 except errors.OpExecError, err:
1524 self.assertTrue(str(err), "Could not wipe disk 0 at offset 0 ")
1525 else:
1526 self.fail("Did not raise exception")
1527
1528 # Check if all disks were paused and resumed
1529 self.assertEqual(pt.history, [
1530 ("disk0", 100 * 1024, True),
1531 ("disk1", 500 * 1024, True),
1532 ("disk2", 256, True),
1533 ("disk0", 100 * 1024, False),
1534 ("disk1", 500 * 1024, False),
1535 ("disk2", 256, False),
1536 ])
1537
1538 def _PrepareWipeTest(self, start_offset, disks):
1539 node_name = "node-with-offset%s.example.com" % start_offset
1540 pauset = _DiskPauseTracker()
1541 progresst = _DiskWipeProgressTracker(start_offset)
1542
1543 lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, pauset, progresst),
1544 cfg=_ConfigForDiskWipe(node_name))
1545
1546 instance = objects.Instance(name="inst3560",
1547 primary_node=node_name,
1548 disk_template=constants.DT_PLAIN,
1549 disks=disks)
1550
1551 return (lu, instance, pauset, progresst)
1552
1553 def testNormalWipe(self):
1554 disks = [
1555 objects.Disk(dev_type=constants.LD_LV, logical_id="disk0", size=1024),
1556 objects.Disk(dev_type=constants.LD_LV, logical_id="disk1",
1557 size=500 * 1024),
1558 objects.Disk(dev_type=constants.LD_LV, logical_id="disk2", size=128),
1559 objects.Disk(dev_type=constants.LD_LV, logical_id="disk3",
1560 size=constants.MAX_WIPE_CHUNK),
1561 ]
1562
1563 (lu, inst, pauset, progresst) = self._PrepareWipeTest(0, disks)
1564
1565 instance._WipeDisks(lu, inst)
1566
1567 self.assertEqual(pauset.history, [
1568 ("disk0", 1024, True),
1569 ("disk1", 500 * 1024, True),
1570 ("disk2", 128, True),
1571 ("disk3", constants.MAX_WIPE_CHUNK, True),
1572 ("disk0", 1024, False),
1573 ("disk1", 500 * 1024, False),
1574 ("disk2", 128, False),
1575 ("disk3", constants.MAX_WIPE_CHUNK, False),
1576 ])
1577
1578 # Ensure the complete disk has been wiped
1579 self.assertEqual(progresst.progress,
1580 dict((i.logical_id, i.size) for i in disks))
1581
1582 def testWipeWithStartOffset(self):
1583 for start_offset in [0, 280, 8895, 1563204]:
1584 disks = [
1585 objects.Disk(dev_type=constants.LD_LV, logical_id="disk0",
1586 size=128),
1587 objects.Disk(dev_type=constants.LD_LV, logical_id="disk1",
1588 size=start_offset + (100 * 1024)),
1589 ]
1590
1591 (lu, inst, pauset, progresst) = \
1592 self._PrepareWipeTest(start_offset, disks)
1593
1594 # Test start offset with only one disk
1595 instance._WipeDisks(lu, inst,
1596 disks=[(1, disks[1], start_offset)])
1597
1598 # Only the second disk may have been paused and wiped
1599 self.assertEqual(pauset.history, [
1600 ("disk1", start_offset + (100 * 1024), True),
1601 ("disk1", start_offset + (100 * 1024), False),
1602 ])
1603 self.assertEqual(progresst.progress, {
1604 "disk1": disks[1].size,
1605 })
1606
1607
1608 class TestDiskSizeInBytesToMebibytes(unittest.TestCase):
1609 def testLessThanOneMebibyte(self):
1610 for i in [1, 2, 7, 512, 1000, 1023]:
1611 lu = _FakeLU()
1612 result = instance_storage._DiskSizeInBytesToMebibytes(lu, i)
1613 self.assertEqual(result, 1)
1614 self.assertEqual(len(lu.warning_log), 1)
1615 self.assertEqual(len(lu.warning_log[0]), 2)
1616 (_, (warnsize, )) = lu.warning_log[0]
1617 self.assertEqual(warnsize, (1024 * 1024) - i)
1618
1619 def testEven(self):
1620 for i in [1, 2, 7, 512, 1000, 1023]:
1621 lu = _FakeLU()
1622 result = instance_storage._DiskSizeInBytesToMebibytes(lu,
1623 i * 1024 * 1024)
1624 self.assertEqual(result, i)
1625 self.assertFalse(lu.warning_log)
1626
1627 def testLargeNumber(self):
1628 for i in [1, 2, 7, 512, 1000, 1023, 2724, 12420]:
1629 for j in [1, 2, 486, 326, 986, 1023]:
1630 lu = _FakeLU()
1631 size = (1024 * 1024 * i) + j
1632 result = instance_storage._DiskSizeInBytesToMebibytes(lu, size)
1633 self.assertEqual(result, i + 1, msg="Amount was not rounded up")
1634 self.assertEqual(len(lu.warning_log), 1)
1635 self.assertEqual(len(lu.warning_log[0]), 2)
1636 (_, (warnsize, )) = lu.warning_log[0]
1637 self.assertEqual(warnsize, (1024 * 1024) - j)
1638
1639
1640 class TestCopyLockList(unittest.TestCase):
1641 def test(self):
1642 self.assertEqual(instance._CopyLockList([]), [])
1643 self.assertEqual(instance._CopyLockList(None), None)
1644 self.assertEqual(instance._CopyLockList(locking.ALL_SET), locking.ALL_SET)
1645
1646 names = ["foo", "bar"]
1647 output = instance._CopyLockList(names)
1648 self.assertEqual(names, output)
1649 self.assertNotEqual(id(names), id(output), msg="List was not copied")
1650
1651
1652 class TestCheckOpportunisticLocking(unittest.TestCase):
1653 class OpTest(opcodes.OpCode):
1654 OP_PARAMS = [
1655 opcodes._POpportunisticLocking,
1656 opcodes._PIAllocFromDesc(""),
1657 ]
1658
1659 @classmethod
1660 def _MakeOp(cls, **kwargs):
1661 op = cls.OpTest(**kwargs)
1662 op.Validate(True)
1663 return op
1664
1665 def testMissingAttributes(self):
1666 self.assertRaises(AttributeError, instance._CheckOpportunisticLocking,
1667 object())
1668
1669 def testDefaults(self):
1670 op = self._MakeOp()
1671 instance._CheckOpportunisticLocking(op)
1672
1673 def test(self):
1674 for iallocator in [None, "something", "other"]:
1675 for opplock in [False, True]:
1676 op = self._MakeOp(iallocator=iallocator,
1677 opportunistic_locking=opplock)
1678 if opplock and not iallocator:
1679 self.assertRaises(errors.OpPrereqError,
1680 instance._CheckOpportunisticLocking, op)
1681 else:
1682 instance._CheckOpportunisticLocking(op)
1683
1684
1685 class _OpTestVerifyErrors(opcodes.OpCode):
1686 OP_PARAMS = [
1687 opcodes._PDebugSimulateErrors,
1688 opcodes._PErrorCodes,
1689 opcodes._PIgnoreErrors,
1690 ]
1691
1692
1693 class _LuTestVerifyErrors(cluster._VerifyErrors):
1694 def __init__(self, **kwargs):
1695 cluster._VerifyErrors.__init__(self)
1696 self.op = _OpTestVerifyErrors(**kwargs)
1697 self.op.Validate(True)
1698 self.msglist = []
1699 self._feedback_fn = self.msglist.append
1700 self.bad = False
1701
1702 def DispatchCallError(self, which, *args, **kwargs):
1703 if which:
1704 self._Error(*args, **kwargs)
1705 else:
1706 self._ErrorIf(True, *args, **kwargs)
1707
1708 def CallErrorIf(self, c, *args, **kwargs):
1709 self._ErrorIf(c, *args, **kwargs)
1710
1711
1712 class TestVerifyErrors(unittest.TestCase):
1713 # Fake cluster-verify error code structures; we use two arbitary real error
1714 # codes to pass validation of ignore_errors
1715 (_, _ERR1ID, _) = constants.CV_ECLUSTERCFG
1716 _NODESTR = "node"
1717 _NODENAME = "mynode"
1718 _ERR1CODE = (_NODESTR, _ERR1ID, "Error one")
1719 (_, _ERR2ID, _) = constants.CV_ECLUSTERCERT
1720 _INSTSTR = "instance"
1721 _INSTNAME = "myinstance"
1722 _ERR2CODE = (_INSTSTR, _ERR2ID, "Error two")
1723 # Arguments used to call _Error() or _ErrorIf()
1724 _ERR1ARGS = (_ERR1CODE, _NODENAME, "Error1 is %s", "an error")
1725 _ERR2ARGS = (_ERR2CODE, _INSTNAME, "Error2 has no argument")
1726 # Expected error messages
1727 _ERR1MSG = _ERR1ARGS[2] % _ERR1ARGS[3]
1728 _ERR2MSG = _ERR2ARGS[2]
1729
1730 def testNoError(self):
1731 lu = _LuTestVerifyErrors()
1732 lu.CallErrorIf(False, self._ERR1CODE, *self._ERR1ARGS)
1733 self.assertFalse(lu.bad)
1734 self.assertFalse(lu.msglist)
1735
1736 def _InitTest(self, **kwargs):
1737 self.lu1 = _LuTestVerifyErrors(**kwargs)
1738 self.lu2 = _LuTestVerifyErrors(**kwargs)
1739
1740 def _CallError(self, *args, **kwargs):
1741 # Check that _Error() and _ErrorIf() produce the same results
1742 self.lu1.DispatchCallError(True, *args, **kwargs)
1743 self.lu2.DispatchCallError(False, *args, **kwargs)
1744 self.assertEqual(self.lu1.bad, self.lu2.bad)
1745 self.assertEqual(self.lu1.msglist, self.lu2.msglist)
1746 # Test-specific checks are made on one LU
1747 return self.lu1
1748
1749 def _checkMsgCommon(self, logstr, errmsg, itype, item, warning):
1750 self.assertTrue(errmsg in logstr)
1751 if warning:
1752 self.assertTrue("WARNING" in logstr)
1753 else:
1754 self.assertTrue("ERROR" in logstr)
1755 self.assertTrue(itype in logstr)
1756 self.assertTrue(item in logstr)
1757
1758 def _checkMsg1(self, logstr, warning=False):
1759 self._checkMsgCommon(logstr, self._ERR1MSG, self._NODESTR,
1760 self._NODENAME, warning)
1761
1762 def _checkMsg2(self, logstr, warning=False):
1763 self._checkMsgCommon(logstr, self._ERR2MSG, self._INSTSTR,
1764 self._INSTNAME, warning)
1765
1766 def testPlain(self):
1767 self._InitTest()
1768 lu = self._CallError(*self._ERR1ARGS)
1769 self.assertTrue(lu.bad)
1770 self.assertEqual(len(lu.msglist), 1)
1771 self._checkMsg1(lu.msglist[0])
1772
1773 def testMultiple(self):
1774 self._InitTest()
1775 self._CallError(*self._ERR1ARGS)
1776 lu = self._CallError(*self._ERR2ARGS)
1777 self.assertTrue(lu.bad)
1778 self.assertEqual(len(lu.msglist), 2)
1779 self._checkMsg1(lu.msglist[0])
1780 self._checkMsg2(lu.msglist[1])
1781
1782 def testIgnore(self):
1783 self._InitTest(ignore_errors=[self._ERR1ID])
1784 lu = self._CallError(*self._ERR1ARGS)
1785 self.assertFalse(lu.bad)
1786 self.assertEqual(len(lu.msglist), 1)
1787 self._checkMsg1(lu.msglist[0], warning=True)
1788
1789 def testWarning(self):
1790 self._InitTest()
1791 lu = self._CallError(*self._ERR1ARGS,
1792 code=_LuTestVerifyErrors.ETYPE_WARNING)
1793 self.assertFalse(lu.bad)
1794 self.assertEqual(len(lu.msglist), 1)
1795 self._checkMsg1(lu.msglist[0], warning=True)
1796
1797 def testWarning2(self):
1798 self._InitTest()
1799 self._CallError(*self._ERR1ARGS)
1800 lu = self._CallError(*self._ERR2ARGS,
1801 code=_LuTestVerifyErrors.ETYPE_WARNING)
1802 self.assertTrue(lu.bad)
1803 self.assertEqual(len(lu.msglist), 2)
1804 self._checkMsg1(lu.msglist[0])
1805 self._checkMsg2(lu.msglist[1], warning=True)
1806
1807 def testDebugSimulate(self):
1808 lu = _LuTestVerifyErrors(debug_simulate_errors=True)
1809 lu.CallErrorIf(False, *self._ERR1ARGS)
1810 self.assertTrue(lu.bad)
1811 self.assertEqual(len(lu.msglist), 1)
1812 self._checkMsg1(lu.msglist[0])
1813
1814 def testErrCodes(self):
1815 self._InitTest(error_codes=True)
1816 lu = self._CallError(*self._ERR1ARGS)
1817 self.assertTrue(lu.bad)
1818 self.assertEqual(len(lu.msglist), 1)
1819 self._checkMsg1(lu.msglist[0])
1820 self.assertTrue(self._ERR1ID in lu.msglist[0])
1821
1822
1823 class TestGetUpdatedIPolicy(unittest.TestCase):
1824 """Tests for cmdlib._GetUpdatedIPolicy()"""
1825 _OLD_CLUSTER_POLICY = {
1826 constants.IPOLICY_VCPU_RATIO: 1.5,
1827 constants.ISPECS_MINMAX: [
1828 {
1829 constants.ISPECS_MIN: {
1830 constants.ISPEC_MEM_SIZE: 32768,
1831 constants.ISPEC_CPU_COUNT: 8,
1832 constants.ISPEC_DISK_COUNT: 1,
1833 constants.ISPEC_DISK_SIZE: 1024,
1834 constants.ISPEC_NIC_COUNT: 1,
1835 constants.ISPEC_SPINDLE_USE: 1,
1836 },
1837 constants.ISPECS_MAX: {
1838 constants.ISPEC_MEM_SIZE: 65536,
1839 constants.ISPEC_CPU_COUNT: 10,
1840 constants.ISPEC_DISK_COUNT: 5,
1841 constants.ISPEC_DISK_SIZE: 1024 * 1024,
1842 constants.ISPEC_NIC_COUNT: 3,
1843 constants.ISPEC_SPINDLE_USE: 12,
1844 },
1845 },
1846 constants.ISPECS_MINMAX_DEFAULTS,
1847 ],
1848 constants.ISPECS_STD: constants.IPOLICY_DEFAULTS[constants.ISPECS_STD],
1849 }
1850 _OLD_GROUP_POLICY = {
1851 constants.IPOLICY_SPINDLE_RATIO: 2.5,
1852 constants.ISPECS_MINMAX: [{
1853 constants.ISPECS_MIN: {
1854 constants.ISPEC_MEM_SIZE: 128,
1855 constants.ISPEC_CPU_COUNT: 1,
1856 constants.ISPEC_DISK_COUNT: 1,
1857 constants.ISPEC_DISK_SIZE: 1024,
1858 constants.ISPEC_NIC_COUNT: 1,
1859 constants.ISPEC_SPINDLE_USE: 1,
1860 },
1861 constants.ISPECS_MAX: {
1862 constants.ISPEC_MEM_SIZE: 32768,
1863 constants.ISPEC_CPU_COUNT: 8,
1864 constants.ISPEC_DISK_COUNT: 5,
1865 constants.ISPEC_DISK_SIZE: 1024 * 1024,
1866 constants.ISPEC_NIC_COUNT: 3,
1867 constants.ISPEC_SPINDLE_USE: 12,
1868 },
1869 }],
1870 }
1871
1872 def _TestSetSpecs(self, old_policy, isgroup):
1873 diff_minmax = [{
1874 constants.ISPECS_MIN: {
1875 constants.ISPEC_MEM_SIZE: 64,
1876 constants.ISPEC_CPU_COUNT: 1,
1877 constants.ISPEC_DISK_COUNT: 2,
1878 constants.ISPEC_DISK_SIZE: 64,
1879 constants.ISPEC_NIC_COUNT: 1,
1880 constants.ISPEC_SPINDLE_USE: 1,
1881 },
1882 constants.ISPECS_MAX: {
1883 constants.ISPEC_MEM_SIZE: 16384,
1884 constants.ISPEC_CPU_COUNT: 10,
1885 constants.ISPEC_DISK_COUNT: 12,
1886 constants.ISPEC_DISK_SIZE: 1024,
1887 constants.ISPEC_NIC_COUNT: 9,
1888 constants.ISPEC_SPINDLE_USE: 18,
1889 },
1890 }]
1891 diff_std = {
1892 constants.ISPEC_DISK_COUNT: 10,
1893 constants.ISPEC_DISK_SIZE: 512,
1894 }
1895 diff_policy = {
1896 constants.ISPECS_MINMAX: diff_minmax
1897 }
1898 if not isgroup:
1899 diff_policy[constants.ISPECS_STD] = diff_std
1900 new_policy = common._GetUpdatedIPolicy(old_policy, diff_policy,
1901 group_policy=isgroup)
1902
1903 self.assertTrue(constants.ISPECS_MINMAX in new_policy)
1904 self.assertEqual(new_policy[constants.ISPECS_MINMAX], diff_minmax)
1905 for key in old_policy:
1906 if not key in diff_policy:
1907 self.assertTrue(key in new_policy)
1908 self.assertEqual(new_policy[key], old_policy[key])
1909
1910 if not isgroup:
1911 new_std = new_policy[constants.ISPECS_STD]
1912 for key in diff_std:
1913 self.assertTrue(key in new_std)
1914 self.assertEqual(new_std[key], diff_std[key])
1915 old_std = old_policy.get(constants.ISPECS_STD, {})
1916 for key in old_std:
1917 self.assertTrue(key in new_std)
1918 if key not in diff_std:
1919 self.assertEqual(new_std[key], old_std[key])
1920
1921 def _TestSet(self, old_policy, diff_policy, isgroup):
1922 new_policy = common._GetUpdatedIPolicy(old_policy, diff_policy,
1923 group_policy=isgroup)
1924 for key in diff_policy:
1925 self.assertTrue(key in new_policy)
1926 self.assertEqual(new_policy[key], diff_policy[key])
1927 for key in old_policy:
1928 if not key in diff_policy:
1929 self.assertTrue(key in new_policy)
1930 self.assertEqual(new_policy[key], old_policy[key])
1931
1932 def testSet(self):
1933 diff_policy = {
1934 constants.IPOLICY_VCPU_RATIO: 3,
1935 constants.IPOLICY_DTS: [constants.DT_FILE],
1936 }
1937 self._TestSet(self._OLD_GROUP_POLICY, diff_policy, True)
1938 self._TestSetSpecs(self._OLD_GROUP_POLICY, True)
1939 self._TestSet({}, diff_policy, True)
1940 self._TestSetSpecs({}, True)
1941 self._TestSet(self._OLD_CLUSTER_POLICY, diff_policy, False)
1942 self._TestSetSpecs(self._OLD_CLUSTER_POLICY, False)
1943
1944 def testUnset(self):
1945 old_policy = self._OLD_GROUP_POLICY
1946 diff_policy = {
1947 constants.IPOLICY_SPINDLE_RATIO: constants.VALUE_DEFAULT,
1948 }
1949 new_policy = common._GetUpdatedIPolicy(old_policy, diff_policy,
1950 group_policy=True)
1951 for key in diff_policy:
1952 self.assertFalse(key in new_policy)
1953 for key in old_policy:
1954 if not key in diff_policy:
1955 self.assertTrue(key in new_policy)
1956 self.assertEqual(new_policy[key], old_policy[key])
1957
1958 self.assertRaises(errors.OpPrereqError, common._GetUpdatedIPolicy,
1959 old_policy, diff_policy, group_policy=False)
1960
1961 def testUnsetEmpty(self):
1962 old_policy = {}
1963 for key in constants.IPOLICY_ALL_KEYS:
1964 diff_policy = {
1965 key: constants.VALUE_DEFAULT,
1966 }
1967 new_policy = common._GetUpdatedIPolicy(old_policy, diff_policy,
1968 group_policy=True)
1969 self.assertEqual(new_policy, old_policy)
1970
1971 def _TestInvalidKeys(self, old_policy, isgroup):
1972 INVALID_KEY = "this_key_shouldnt_be_allowed"
1973 INVALID_DICT = {
1974 INVALID_KEY: 3,
1975 }
1976 invalid_policy = INVALID_DICT
1977 self.assertRaises(errors.OpPrereqError, common._GetUpdatedIPolicy,
1978 old_policy, invalid_policy, group_policy=isgroup)
1979 invalid_ispecs = {
1980 constants.ISPECS_MINMAX: [INVALID_DICT],
1981 }
1982 self.assertRaises(errors.TypeEnforcementError, common._GetUpdatedIPolicy,
1983 old_policy, invalid_ispecs, group_policy=isgroup)
1984 if isgroup:
1985 invalid_for_group = {
1986 constants.ISPECS_STD: constants.IPOLICY_DEFAULTS[constants.ISPECS_STD],
1987 }
1988 self.assertRaises(errors.OpPrereqError, common._GetUpdatedIPolicy,
1989 old_policy, invalid_for_group, group_policy=isgroup)
1990 good_ispecs = self._OLD_CLUSTER_POLICY[constants.ISPECS_MINMAX]
1991 invalid_ispecs = copy.deepcopy(good_ispecs)
1992 invalid_policy = {
1993 constants.ISPECS_MINMAX: invalid_ispecs,
1994 }
1995 for minmax in invalid_ispecs:
1996 for key in constants.ISPECS_MINMAX_KEYS:
1997 ispec = minmax[key]
1998 ispec[INVALID_KEY] = None
1999 self.assertRaises(errors.TypeEnforcementError,
2000 common._GetUpdatedIPolicy, old_policy,
2001 invalid_policy, group_policy=isgroup)
2002 del ispec[INVALID_KEY]
2003 for par in constants.ISPECS_PARAMETERS:
2004 oldv = ispec[par]
2005 ispec[par] = "this_is_not_good"
2006 self.assertRaises(errors.TypeEnforcementError,
2007 common._GetUpdatedIPolicy,
2008 old_policy, invalid_policy, group_policy=isgroup)
2009 ispec[par] = oldv
2010 # This is to make sure that no two errors were present during the tests
2011 common._GetUpdatedIPolicy(old_policy, invalid_policy,
2012 group_policy=isgroup)
2013
2014 def testInvalidKeys(self):
2015 self._TestInvalidKeys(self._OLD_GROUP_POLICY, True)
2016 self._TestInvalidKeys(self._OLD_CLUSTER_POLICY, False)
2017
2018 def testInvalidValues(self):
2019 for par in (constants.IPOLICY_PARAMETERS |
2020 frozenset([constants.IPOLICY_DTS])):
2021 bad_policy = {
2022 par: "invalid_value",
2023 }
2024 self.assertRaises(errors.OpPrereqError, common._GetUpdatedIPolicy, {},
2025 bad_policy, group_policy=True)
2026
2027 if __name__ == "__main__":
2028 testutils.GanetiTestProgram()