Add unittest for cmdlib._GenerateDiskTemplate
[ganeti-local] / test / ganeti.cmdlib_unittest.py
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2008, 2011 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 time
28 import tempfile
29 import shutil
30 import operator
31 import itertools
32
33 from ganeti import constants
34 from ganeti import mcpu
35 from ganeti import cmdlib
36 from ganeti import opcodes
37 from ganeti import errors
38 from ganeti import utils
39 from ganeti import luxi
40 from ganeti import ht
41 from ganeti import objects
42 from ganeti import compat
43 from ganeti import rpc
44 from ganeti.hypervisor import hv_xen
45
46 import testutils
47 import mocks
48
49
50 class TestCertVerification(testutils.GanetiTestCase):
51   def setUp(self):
52     testutils.GanetiTestCase.setUp(self)
53
54     self.tmpdir = tempfile.mkdtemp()
55
56   def tearDown(self):
57     shutil.rmtree(self.tmpdir)
58
59   def testVerifyCertificate(self):
60     cmdlib._VerifyCertificate(self._TestDataFilename("cert1.pem"))
61
62     nonexist_filename = os.path.join(self.tmpdir, "does-not-exist")
63
64     (errcode, msg) = cmdlib._VerifyCertificate(nonexist_filename)
65     self.assertEqual(errcode, cmdlib.LUClusterVerifyConfig.ETYPE_ERROR)
66
67     # Try to load non-certificate file
68     invalid_cert = self._TestDataFilename("bdev-net.txt")
69     (errcode, msg) = cmdlib._VerifyCertificate(invalid_cert)
70     self.assertEqual(errcode, cmdlib.LUClusterVerifyConfig.ETYPE_ERROR)
71
72
73 class TestOpcodeParams(testutils.GanetiTestCase):
74   def testParamsStructures(self):
75     for op in sorted(mcpu.Processor.DISPATCH_TABLE):
76       lu = mcpu.Processor.DISPATCH_TABLE[op]
77       lu_name = lu.__name__
78       self.failIf(hasattr(lu, "_OP_REQP"),
79                   msg=("LU '%s' has old-style _OP_REQP" % lu_name))
80       self.failIf(hasattr(lu, "_OP_DEFS"),
81                   msg=("LU '%s' has old-style _OP_DEFS" % lu_name))
82       self.failIf(hasattr(lu, "_OP_PARAMS"),
83                   msg=("LU '%s' has old-style _OP_PARAMS" % lu_name))
84
85
86 class TestIAllocatorChecks(testutils.GanetiTestCase):
87   def testFunction(self):
88     class TestLU(object):
89       def __init__(self, opcode):
90         self.cfg = mocks.FakeConfig()
91         self.op = opcode
92
93     class OpTest(opcodes.OpCode):
94        OP_PARAMS = [
95         ("iallocator", None, ht.NoType, None),
96         ("node", None, ht.NoType, None),
97         ]
98
99     default_iallocator = mocks.FakeConfig().GetDefaultIAllocator()
100     other_iallocator = default_iallocator + "_not"
101
102     op = OpTest()
103     lu = TestLU(op)
104
105     c_i = lambda: cmdlib._CheckIAllocatorOrNode(lu, "iallocator", "node")
106
107     # Neither node nor iallocator given
108     op.iallocator = None
109     op.node = None
110     c_i()
111     self.assertEqual(lu.op.iallocator, default_iallocator)
112     self.assertEqual(lu.op.node, None)
113
114     # Both, iallocator and node given
115     op.iallocator = "test"
116     op.node = "test"
117     self.assertRaises(errors.OpPrereqError, c_i)
118
119     # Only iallocator given
120     op.iallocator = other_iallocator
121     op.node = None
122     c_i()
123     self.assertEqual(lu.op.iallocator, other_iallocator)
124     self.assertEqual(lu.op.node, None)
125
126     # Only node given
127     op.iallocator = None
128     op.node = "node"
129     c_i()
130     self.assertEqual(lu.op.iallocator, None)
131     self.assertEqual(lu.op.node, "node")
132
133     # No node, iallocator or default iallocator
134     op.iallocator = None
135     op.node = None
136     lu.cfg.GetDefaultIAllocator = lambda: None
137     self.assertRaises(errors.OpPrereqError, c_i)
138
139
140 class TestLUTestJqueue(unittest.TestCase):
141   def test(self):
142     self.assert_(cmdlib.LUTestJqueue._CLIENT_CONNECT_TIMEOUT <
143                  (luxi.WFJC_TIMEOUT * 0.75),
144                  msg=("Client timeout too high, might not notice bugs"
145                       " in WaitForJobChange"))
146
147
148 class TestLUQuery(unittest.TestCase):
149   def test(self):
150     self.assertEqual(sorted(cmdlib._QUERY_IMPL.keys()),
151                      sorted(constants.QR_VIA_OP))
152
153     assert constants.QR_NODE in constants.QR_VIA_OP
154     assert constants.QR_INSTANCE in constants.QR_VIA_OP
155
156     for i in constants.QR_VIA_OP:
157       self.assert_(cmdlib._GetQueryImplementation(i))
158
159     self.assertRaises(errors.OpPrereqError, cmdlib._GetQueryImplementation, "")
160     self.assertRaises(errors.OpPrereqError, cmdlib._GetQueryImplementation,
161                       "xyz")
162
163
164 class TestLUGroupAssignNodes(unittest.TestCase):
165
166   def testCheckAssignmentForSplitInstances(self):
167     node_data = dict((name, objects.Node(name=name, group=group))
168                      for (name, group) in [("n1a", "g1"), ("n1b", "g1"),
169                                            ("n2a", "g2"), ("n2b", "g2"),
170                                            ("n3a", "g3"), ("n3b", "g3"),
171                                            ("n3c", "g3"),
172                                            ])
173
174     def Instance(name, pnode, snode):
175       if snode is None:
176         disks = []
177         disk_template = constants.DT_DISKLESS
178       else:
179         disks = [objects.Disk(dev_type=constants.LD_DRBD8,
180                               logical_id=[pnode, snode, 1, 17, 17])]
181         disk_template = constants.DT_DRBD8
182
183       return objects.Instance(name=name, primary_node=pnode, disks=disks,
184                               disk_template=disk_template)
185
186     instance_data = dict((name, Instance(name, pnode, snode))
187                          for name, pnode, snode in [("inst1a", "n1a", "n1b"),
188                                                     ("inst1b", "n1b", "n1a"),
189                                                     ("inst2a", "n2a", "n2b"),
190                                                     ("inst3a", "n3a", None),
191                                                     ("inst3b", "n3b", "n1b"),
192                                                     ("inst3c", "n3b", "n2b"),
193                                                     ])
194
195     # Test first with the existing state.
196     (new, prev) = \
197       cmdlib.LUGroupAssignNodes.CheckAssignmentForSplitInstances([],
198                                                                  node_data,
199                                                                  instance_data)
200
201     self.assertEqual([], new)
202     self.assertEqual(set(["inst3b", "inst3c"]), set(prev))
203
204     # And now some changes.
205     (new, prev) = \
206       cmdlib.LUGroupAssignNodes.CheckAssignmentForSplitInstances([("n1b",
207                                                                    "g3")],
208                                                                  node_data,
209                                                                  instance_data)
210
211     self.assertEqual(set(["inst1a", "inst1b"]), set(new))
212     self.assertEqual(set(["inst3c"]), set(prev))
213
214
215 class TestClusterVerifySsh(unittest.TestCase):
216   def testMultipleGroups(self):
217     fn = cmdlib.LUClusterVerifyGroup._SelectSshCheckNodes
218     mygroupnodes = [
219       objects.Node(name="node20", group="my", offline=False),
220       objects.Node(name="node21", group="my", offline=False),
221       objects.Node(name="node22", group="my", offline=False),
222       objects.Node(name="node23", group="my", offline=False),
223       objects.Node(name="node24", group="my", offline=False),
224       objects.Node(name="node25", group="my", offline=False),
225       objects.Node(name="node26", group="my", offline=True),
226       ]
227     nodes = [
228       objects.Node(name="node1", group="g1", offline=True),
229       objects.Node(name="node2", group="g1", offline=False),
230       objects.Node(name="node3", group="g1", offline=False),
231       objects.Node(name="node4", group="g1", offline=True),
232       objects.Node(name="node5", group="g1", offline=False),
233       objects.Node(name="node10", group="xyz", offline=False),
234       objects.Node(name="node11", group="xyz", offline=False),
235       objects.Node(name="node40", group="alloff", offline=True),
236       objects.Node(name="node41", group="alloff", offline=True),
237       objects.Node(name="node50", group="aaa", offline=False),
238       ] + mygroupnodes
239     assert not utils.FindDuplicates(map(operator.attrgetter("name"), nodes))
240
241     (online, perhost) = fn(mygroupnodes, "my", nodes)
242     self.assertEqual(online, ["node%s" % i for i in range(20, 26)])
243     self.assertEqual(set(perhost.keys()), set(online))
244
245     self.assertEqual(perhost, {
246       "node20": ["node10", "node2", "node50"],
247       "node21": ["node11", "node3", "node50"],
248       "node22": ["node10", "node5", "node50"],
249       "node23": ["node11", "node2", "node50"],
250       "node24": ["node10", "node3", "node50"],
251       "node25": ["node11", "node5", "node50"],
252       })
253
254   def testSingleGroup(self):
255     fn = cmdlib.LUClusterVerifyGroup._SelectSshCheckNodes
256     nodes = [
257       objects.Node(name="node1", group="default", offline=True),
258       objects.Node(name="node2", group="default", offline=False),
259       objects.Node(name="node3", group="default", offline=False),
260       objects.Node(name="node4", group="default", offline=True),
261       ]
262     assert not utils.FindDuplicates(map(operator.attrgetter("name"), nodes))
263
264     (online, perhost) = fn(nodes, "default", nodes)
265     self.assertEqual(online, ["node2", "node3"])
266     self.assertEqual(set(perhost.keys()), set(online))
267
268     self.assertEqual(perhost, {
269       "node2": [],
270       "node3": [],
271       })
272
273
274 class TestClusterVerifyFiles(unittest.TestCase):
275   @staticmethod
276   def _FakeErrorIf(errors, cond, ecode, item, msg, *args, **kwargs):
277     assert ((ecode == constants.CV_ENODEFILECHECK and
278              ht.TNonEmptyString(item)) or
279             (ecode == constants.CV_ECLUSTERFILECHECK and
280              item is None))
281
282     if args:
283       msg = msg % args
284
285     if cond:
286       errors.append((item, msg))
287
288   _VerifyFiles = cmdlib.LUClusterVerifyGroup._VerifyFiles
289
290   def test(self):
291     errors = []
292     master_name = "master.example.com"
293     nodeinfo = [
294       objects.Node(name=master_name, offline=False, vm_capable=True),
295       objects.Node(name="node2.example.com", offline=False, vm_capable=True),
296       objects.Node(name="node3.example.com", master_candidate=True,
297                    vm_capable=False),
298       objects.Node(name="node4.example.com", offline=False, vm_capable=True),
299       objects.Node(name="nodata.example.com", offline=False, vm_capable=True),
300       objects.Node(name="offline.example.com", offline=True),
301       ]
302     cluster = objects.Cluster(modify_etc_hosts=True,
303                               enabled_hypervisors=[constants.HT_XEN_HVM])
304     files_all = set([
305       constants.CLUSTER_DOMAIN_SECRET_FILE,
306       constants.RAPI_CERT_FILE,
307       constants.RAPI_USERS_FILE,
308       ])
309     files_opt = set([
310       constants.RAPI_USERS_FILE,
311       hv_xen.XL_CONFIG_FILE,
312       constants.VNC_PASSWORD_FILE,
313       ])
314     files_mc = set([
315       constants.CLUSTER_CONF_FILE,
316       ])
317     files_vm = set([
318       hv_xen.XEND_CONFIG_FILE,
319       hv_xen.XL_CONFIG_FILE,
320       constants.VNC_PASSWORD_FILE,
321       ])
322     nvinfo = {
323       master_name: rpc.RpcResult(data=(True, {
324         constants.NV_FILELIST: {
325           constants.CLUSTER_CONF_FILE: "82314f897f38b35f9dab2f7c6b1593e0",
326           constants.RAPI_CERT_FILE: "babbce8f387bc082228e544a2146fee4",
327           constants.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
328           hv_xen.XEND_CONFIG_FILE: "b4a8a824ab3cac3d88839a9adeadf310",
329           hv_xen.XL_CONFIG_FILE: "77935cee92afd26d162f9e525e3d49b9"
330         }})),
331       "node2.example.com": rpc.RpcResult(data=(True, {
332         constants.NV_FILELIST: {
333           constants.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
334           hv_xen.XEND_CONFIG_FILE: "b4a8a824ab3cac3d88839a9adeadf310",
335           }
336         })),
337       "node3.example.com": rpc.RpcResult(data=(True, {
338         constants.NV_FILELIST: {
339           constants.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
340           constants.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
341           }
342         })),
343       "node4.example.com": rpc.RpcResult(data=(True, {
344         constants.NV_FILELIST: {
345           constants.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
346           constants.CLUSTER_CONF_FILE: "conf-a6d4b13e407867f7a7b4f0f232a8f527",
347           constants.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
348           constants.RAPI_USERS_FILE: "rapiusers-ea3271e8d810ef3",
349           hv_xen.XL_CONFIG_FILE: "77935cee92afd26d162f9e525e3d49b9"
350           }
351         })),
352       "nodata.example.com": rpc.RpcResult(data=(True, {})),
353       "offline.example.com": rpc.RpcResult(offline=True),
354       }
355     assert set(nvinfo.keys()) == set(map(operator.attrgetter("name"), nodeinfo))
356
357     self._VerifyFiles(compat.partial(self._FakeErrorIf, errors), nodeinfo,
358                       master_name, nvinfo,
359                       (files_all, files_opt, files_mc, files_vm))
360     self.assertEqual(sorted(errors), sorted([
361       (None, ("File %s found with 2 different checksums (variant 1 on"
362               " node2.example.com, node3.example.com, node4.example.com;"
363               " variant 2 on master.example.com)" % constants.RAPI_CERT_FILE)),
364       (None, ("File %s is missing from node(s) node2.example.com" %
365               constants.CLUSTER_DOMAIN_SECRET_FILE)),
366       (None, ("File %s should not exist on node(s) node4.example.com" %
367               constants.CLUSTER_CONF_FILE)),
368       (None, ("File %s is missing from node(s) node4.example.com" %
369               hv_xen.XEND_CONFIG_FILE)),
370       (None, ("File %s is missing from node(s) node3.example.com" %
371               constants.CLUSTER_CONF_FILE)),
372       (None, ("File %s found with 2 different checksums (variant 1 on"
373               " master.example.com; variant 2 on node4.example.com)" %
374               constants.CLUSTER_CONF_FILE)),
375       (None, ("File %s is optional, but it must exist on all or no nodes (not"
376               " found on master.example.com, node2.example.com,"
377               " node3.example.com)" % constants.RAPI_USERS_FILE)),
378       (None, ("File %s is optional, but it must exist on all or no nodes (not"
379               " found on node2.example.com)" % hv_xen.XL_CONFIG_FILE)),
380       ("nodata.example.com", "Node did not return file checksum data"),
381       ]))
382
383
384 class _FakeLU:
385   def __init__(self, cfg=NotImplemented, proc=NotImplemented):
386     self.warning_log = []
387     self.info_log = []
388     self.cfg = cfg
389     self.proc = proc
390
391   def LogWarning(self, text, *args):
392     self.warning_log.append((text, args))
393
394   def LogInfo(self, text, *args):
395     self.info_log.append((text, args))
396
397
398 class TestLoadNodeEvacResult(unittest.TestCase):
399   def testSuccess(self):
400     for moved in [[], [
401       ("inst20153.example.com", "grp2", ["nodeA4509", "nodeB2912"]),
402       ]]:
403       for early_release in [False, True]:
404         for use_nodes in [False, True]:
405           jobs = [
406             [opcodes.OpInstanceReplaceDisks().__getstate__()],
407             [opcodes.OpInstanceMigrate().__getstate__()],
408             ]
409
410           alloc_result = (moved, [], jobs)
411           assert cmdlib.IAllocator._NEVAC_RESULT(alloc_result)
412
413           lu = _FakeLU()
414           result = cmdlib._LoadNodeEvacResult(lu, alloc_result,
415                                               early_release, use_nodes)
416
417           if moved:
418             (_, (info_args, )) = lu.info_log.pop(0)
419             for (instname, instgroup, instnodes) in moved:
420               self.assertTrue(instname in info_args)
421               if use_nodes:
422                 for i in instnodes:
423                   self.assertTrue(i in info_args)
424               else:
425                 self.assertTrue(instgroup in info_args)
426
427           self.assertFalse(lu.info_log)
428           self.assertFalse(lu.warning_log)
429
430           for op in itertools.chain(*result):
431             if hasattr(op.__class__, "early_release"):
432               self.assertEqual(op.early_release, early_release)
433             else:
434               self.assertFalse(hasattr(op, "early_release"))
435
436   def testFailed(self):
437     alloc_result = ([], [
438       ("inst5191.example.com", "errormsg21178"),
439       ], [])
440     assert cmdlib.IAllocator._NEVAC_RESULT(alloc_result)
441
442     lu = _FakeLU()
443     self.assertRaises(errors.OpExecError, cmdlib._LoadNodeEvacResult,
444                       lu, alloc_result, False, False)
445     self.assertFalse(lu.info_log)
446     (_, (args, )) = lu.warning_log.pop(0)
447     self.assertTrue("inst5191.example.com" in args)
448     self.assertTrue("errormsg21178" in args)
449     self.assertFalse(lu.warning_log)
450
451
452 class TestUpdateAndVerifySubDict(unittest.TestCase):
453   def setUp(self):
454     self.type_check = {
455         "a": constants.VTYPE_INT,
456         "b": constants.VTYPE_STRING,
457         "c": constants.VTYPE_BOOL,
458         "d": constants.VTYPE_STRING,
459         }
460
461   def test(self):
462     old_test = {
463       "foo": {
464         "d": "blubb",
465         "a": 321,
466         },
467       "baz": {
468         "a": 678,
469         "b": "678",
470         "c": True,
471         },
472       }
473     test = {
474       "foo": {
475         "a": 123,
476         "b": "123",
477         "c": True,
478         },
479       "bar": {
480         "a": 321,
481         "b": "321",
482         "c": False,
483         },
484       }
485
486     mv = {
487       "foo": {
488         "a": 123,
489         "b": "123",
490         "c": True,
491         "d": "blubb"
492         },
493       "bar": {
494         "a": 321,
495         "b": "321",
496         "c": False,
497         },
498       "baz": {
499         "a": 678,
500         "b": "678",
501         "c": True,
502         },
503       }
504
505     verified = cmdlib._UpdateAndVerifySubDict(old_test, test, self.type_check)
506     self.assertEqual(verified, mv)
507
508   def testWrong(self):
509     test = {
510       "foo": {
511         "a": "blubb",
512         "b": "123",
513         "c": True,
514         },
515       "bar": {
516         "a": 321,
517         "b": "321",
518         "c": False,
519         },
520       }
521
522     self.assertRaises(errors.TypeEnforcementError,
523                       cmdlib._UpdateAndVerifySubDict, {}, test, self.type_check)
524
525
526 class TestHvStateHelper(unittest.TestCase):
527   def testWithoutOpData(self):
528     self.assertEqual(cmdlib._MergeAndVerifyHvState(None, NotImplemented), None)
529
530   def testWithoutOldData(self):
531     new = {
532       constants.HT_XEN_PVM: {
533         constants.HVST_MEMORY_TOTAL: 4096,
534         },
535       }
536     self.assertEqual(cmdlib._MergeAndVerifyHvState(new, None), new)
537
538   def testWithWrongHv(self):
539     new = {
540       "i-dont-exist": {
541         constants.HVST_MEMORY_TOTAL: 4096,
542         },
543       }
544     self.assertRaises(errors.OpPrereqError, cmdlib._MergeAndVerifyHvState, new,
545                       None)
546
547 class TestDiskStateHelper(unittest.TestCase):
548   def testWithoutOpData(self):
549     self.assertEqual(cmdlib._MergeAndVerifyDiskState(None, NotImplemented),
550                      None)
551
552   def testWithoutOldData(self):
553     new = {
554       constants.LD_LV: {
555         "xenvg": {
556           constants.DS_DISK_RESERVED: 1024,
557           },
558         },
559       }
560     self.assertEqual(cmdlib._MergeAndVerifyDiskState(new, None), new)
561
562   def testWithWrongStorageType(self):
563     new = {
564       "i-dont-exist": {
565         "xenvg": {
566           constants.DS_DISK_RESERVED: 1024,
567           },
568         },
569       }
570     self.assertRaises(errors.OpPrereqError, cmdlib._MergeAndVerifyDiskState,
571                       new, None)
572
573
574 class TestComputeMinMaxSpec(unittest.TestCase):
575   def setUp(self):
576     self.ipolicy = {
577       constants.ISPECS_MAX: {
578         constants.ISPEC_MEM_SIZE: 512,
579         constants.ISPEC_DISK_SIZE: 1024,
580         },
581       constants.ISPECS_MIN: {
582         constants.ISPEC_MEM_SIZE: 128,
583         constants.ISPEC_DISK_COUNT: 1,
584         },
585       }
586
587   def testNoneValue(self):
588     self.assertTrue(cmdlib._ComputeMinMaxSpec(constants.ISPEC_MEM_SIZE,
589                                               self.ipolicy, None) is None)
590
591   def testAutoValue(self):
592     self.assertTrue(cmdlib._ComputeMinMaxSpec(constants.ISPEC_MEM_SIZE,
593                                               self.ipolicy,
594                                               constants.VALUE_AUTO) is None)
595
596   def testNotDefined(self):
597     self.assertTrue(cmdlib._ComputeMinMaxSpec(constants.ISPEC_NIC_COUNT,
598                                               self.ipolicy, 3) is None)
599
600   def testNoMinDefined(self):
601     self.assertTrue(cmdlib._ComputeMinMaxSpec(constants.ISPEC_DISK_SIZE,
602                                               self.ipolicy, 128) is None)
603
604   def testNoMaxDefined(self):
605     self.assertTrue(cmdlib._ComputeMinMaxSpec(constants.ISPEC_DISK_COUNT,
606                                                 self.ipolicy, 16) is None)
607
608   def testOutOfRange(self):
609     for (name, val) in ((constants.ISPEC_MEM_SIZE, 64),
610                         (constants.ISPEC_MEM_SIZE, 768),
611                         (constants.ISPEC_DISK_SIZE, 4096),
612                         (constants.ISPEC_DISK_COUNT, 0)):
613       min_v = self.ipolicy[constants.ISPECS_MIN].get(name, val)
614       max_v = self.ipolicy[constants.ISPECS_MAX].get(name, val)
615       self.assertEqual(cmdlib._ComputeMinMaxSpec(name, self.ipolicy, val),
616                        "%s value %s is not in range [%s, %s]" %
617                        (name, val,min_v, max_v))
618
619   def test(self):
620     for (name, val) in ((constants.ISPEC_MEM_SIZE, 256),
621                         (constants.ISPEC_MEM_SIZE, 128),
622                         (constants.ISPEC_MEM_SIZE, 512),
623                         (constants.ISPEC_DISK_SIZE, 1024),
624                         (constants.ISPEC_DISK_SIZE, 0),
625                         (constants.ISPEC_DISK_COUNT, 1),
626                         (constants.ISPEC_DISK_COUNT, 5)):
627       self.assertTrue(cmdlib._ComputeMinMaxSpec(name, self.ipolicy, val)
628                       is None)
629
630
631 def _ValidateComputeMinMaxSpec(name, *_):
632   assert name in constants.ISPECS_PARAMETERS
633   return None
634
635
636 class _SpecWrapper:
637   def __init__(self, spec):
638     self.spec = spec
639
640   def ComputeMinMaxSpec(self, *args):
641     return self.spec.pop(0)
642
643
644 class TestComputeIPolicySpecViolation(unittest.TestCase):
645   def test(self):
646     compute_fn = _ValidateComputeMinMaxSpec
647     ret = cmdlib._ComputeIPolicySpecViolation(NotImplemented, 1024, 1, 1, 1,
648                                               [1024], _compute_fn=compute_fn)
649     self.assertEqual(ret, [])
650
651   def testInvalidArguments(self):
652     self.assertRaises(AssertionError, cmdlib._ComputeIPolicySpecViolation,
653                       NotImplemented, 1024, 1, 1, 1, [])
654
655   def testInvalidSpec(self):
656     spec = _SpecWrapper([None, False, "foo", None, "bar"])
657     compute_fn = spec.ComputeMinMaxSpec
658     ret = cmdlib._ComputeIPolicySpecViolation(NotImplemented, 1024, 1, 1, 1,
659                                               [1024], _compute_fn=compute_fn)
660     self.assertEqual(ret, ["foo", "bar"])
661     self.assertFalse(spec.spec)
662
663
664 class _StubComputeIPolicySpecViolation:
665   def __init__(self, mem_size, cpu_count, disk_count, nic_count, disk_sizes):
666     self.mem_size = mem_size
667     self.cpu_count = cpu_count
668     self.disk_count = disk_count
669     self.nic_count = nic_count
670     self.disk_sizes = disk_sizes
671
672   def __call__(self, _, mem_size, cpu_count, disk_count, nic_count, disk_sizes):
673     assert self.mem_size == mem_size
674     assert self.cpu_count == cpu_count
675     assert self.disk_count == disk_count
676     assert self.nic_count == nic_count
677     assert self.disk_sizes == disk_sizes
678
679     return []
680
681
682 class TestComputeIPolicyInstanceViolation(unittest.TestCase):
683   def test(self):
684     beparams = {
685       constants.BE_MAXMEM: 2048,
686       constants.BE_VCPUS: 2,
687       }
688     disks = [objects.Disk(size=512)]
689     instance = objects.Instance(beparams=beparams, disks=disks, nics=[])
690     stub = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512])
691     ret = cmdlib._ComputeIPolicyInstanceViolation(NotImplemented, instance,
692                                                   _compute_fn=stub)
693     self.assertEqual(ret, [])
694
695
696 class TestComputeIPolicyInstanceSpecViolation(unittest.TestCase):
697   def test(self):
698     ispec = {
699       constants.ISPEC_MEM_SIZE: 2048,
700       constants.ISPEC_CPU_COUNT: 2,
701       constants.ISPEC_DISK_COUNT: 1,
702       constants.ISPEC_DISK_SIZE: [512],
703       constants.ISPEC_NIC_COUNT: 0,
704       }
705     stub = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512])
706     ret = cmdlib._ComputeIPolicyInstanceSpecViolation(NotImplemented, ispec,
707                                                       _compute_fn=stub)
708     self.assertEqual(ret, [])
709
710
711 class _CallRecorder:
712   def __init__(self, return_value=None):
713     self.called = False
714     self.return_value = return_value
715
716   def __call__(self, *args):
717     self.called = True
718     return self.return_value
719
720
721 class TestComputeIPolicyNodeViolation(unittest.TestCase):
722   def setUp(self):
723     self.recorder = _CallRecorder(return_value=[])
724
725   def testSameGroup(self):
726     ret = cmdlib._ComputeIPolicyNodeViolation(NotImplemented, NotImplemented,
727                                               "foo", "foo",
728                                               _compute_fn=self.recorder)
729     self.assertFalse(self.recorder.called)
730     self.assertEqual(ret, [])
731
732   def testDifferentGroup(self):
733     ret = cmdlib._ComputeIPolicyNodeViolation(NotImplemented, NotImplemented,
734                                               "foo", "bar",
735                                               _compute_fn=self.recorder)
736     self.assertTrue(self.recorder.called)
737     self.assertEqual(ret, [])
738
739
740 class _FakeConfigForTargetNodeIPolicy:
741   def __init__(self, node_info=NotImplemented):
742     self._node_info = node_info
743
744   def GetNodeInfo(self, _):
745     return self._node_info
746
747
748 class TestCheckTargetNodeIPolicy(unittest.TestCase):
749   def setUp(self):
750     self.instance = objects.Instance(primary_node="blubb")
751     self.target_node = objects.Node(group="bar")
752     node_info = objects.Node(group="foo")
753     fake_cfg = _FakeConfigForTargetNodeIPolicy(node_info=node_info)
754     self.lu = _FakeLU(cfg=fake_cfg)
755
756   def testNoViolation(self):
757     compute_recoder = _CallRecorder(return_value=[])
758     cmdlib._CheckTargetNodeIPolicy(self.lu, NotImplemented, self.instance,
759                                    self.target_node,
760                                    _compute_fn=compute_recoder)
761     self.assertTrue(compute_recoder.called)
762     self.assertEqual(self.lu.warning_log, [])
763
764   def testNoIgnore(self):
765     compute_recoder = _CallRecorder(return_value=["mem_size not in range"])
766     self.assertRaises(errors.OpPrereqError, cmdlib._CheckTargetNodeIPolicy,
767                       self.lu, NotImplemented, self.instance, self.target_node,
768                       _compute_fn=compute_recoder)
769     self.assertTrue(compute_recoder.called)
770     self.assertEqual(self.lu.warning_log, [])
771
772   def testIgnoreViolation(self):
773     compute_recoder = _CallRecorder(return_value=["mem_size not in range"])
774     cmdlib._CheckTargetNodeIPolicy(self.lu, NotImplemented, self.instance,
775                                    self.target_node, ignore=True,
776                                    _compute_fn=compute_recoder)
777     self.assertTrue(compute_recoder.called)
778     msg = ("Instance does not meet target node group's (bar) instance policy:"
779            " mem_size not in range")
780     self.assertEqual(self.lu.warning_log, [(msg, ())])
781
782
783 class TestApplyContainerMods(unittest.TestCase):
784   def testEmptyContainer(self):
785     container = []
786     chgdesc = []
787     cmdlib.ApplyContainerMods("test", container, chgdesc, [], None, None, None)
788     self.assertEqual(container, [])
789     self.assertEqual(chgdesc, [])
790
791   def testAdd(self):
792     container = []
793     chgdesc = []
794     mods = cmdlib.PrepareContainerMods([
795       (constants.DDM_ADD, -1, "Hello"),
796       (constants.DDM_ADD, -1, "World"),
797       (constants.DDM_ADD, 0, "Start"),
798       (constants.DDM_ADD, -1, "End"),
799       ], None)
800     cmdlib.ApplyContainerMods("test", container, chgdesc, mods,
801                               None, None, None)
802     self.assertEqual(container, ["Start", "Hello", "World", "End"])
803     self.assertEqual(chgdesc, [])
804
805   def testRemoveError(self):
806     for idx in [0, 1, 2, 100, -1, -4]:
807       mods = cmdlib.PrepareContainerMods([
808         (constants.DDM_REMOVE, idx, None),
809         ], None)
810       self.assertRaises(IndexError, cmdlib.ApplyContainerMods,
811                         "test", [], None, mods, None, None, None)
812
813     mods = cmdlib.PrepareContainerMods([
814       (constants.DDM_REMOVE, 0, object()),
815       ], None)
816     self.assertRaises(AssertionError, cmdlib.ApplyContainerMods,
817                       "test", [""], None, mods, None, None, None)
818
819   def testAddError(self):
820     for idx in range(-100, -1):
821       mods = cmdlib.PrepareContainerMods([
822         (constants.DDM_ADD, idx, None),
823         ], None)
824       self.assertRaises(IndexError, cmdlib.ApplyContainerMods,
825                         "test", [], None, mods, None, None, None)
826
827   def testRemove(self):
828     container = ["item 1", "item 2"]
829     mods = cmdlib.PrepareContainerMods([
830       (constants.DDM_ADD, -1, "aaa"),
831       (constants.DDM_REMOVE, -1, None),
832       (constants.DDM_ADD, -1, "bbb"),
833       ], None)
834     chgdesc = []
835     cmdlib.ApplyContainerMods("test", container, chgdesc, mods,
836                               None, None, None)
837     self.assertEqual(container, ["item 1", "item 2", "bbb"])
838     self.assertEqual(chgdesc, [
839       ("test/2", "remove"),
840       ])
841
842   class _PrivateData:
843     def __init__(self):
844       self.data = None
845
846   @staticmethod
847   def _CreateTestFn(idx, params, private):
848     private.data = ("add", idx, params)
849     return ((100 * idx, params), [
850       ("test/%s" % idx, hex(idx)),
851       ])
852
853   @staticmethod
854   def _ModifyTestFn(idx, item, params, private):
855     private.data = ("modify", idx, params)
856     return [
857       ("test/%s" % idx, "modify %s" % params),
858       ]
859
860   @staticmethod
861   def _RemoveTestFn(idx, item, private):
862     private.data = ("remove", idx, item)
863
864   def testAddWithCreateFunction(self):
865     container = []
866     chgdesc = []
867     mods = cmdlib.PrepareContainerMods([
868       (constants.DDM_ADD, -1, "Hello"),
869       (constants.DDM_ADD, -1, "World"),
870       (constants.DDM_ADD, 0, "Start"),
871       (constants.DDM_ADD, -1, "End"),
872       (constants.DDM_REMOVE, 2, None),
873       (constants.DDM_MODIFY, -1, "foobar"),
874       (constants.DDM_REMOVE, 2, None),
875       (constants.DDM_ADD, 1, "More"),
876       ], self._PrivateData)
877     cmdlib.ApplyContainerMods("test", container, chgdesc, mods,
878       self._CreateTestFn, self._ModifyTestFn, self._RemoveTestFn)
879     self.assertEqual(container, [
880       (100, "Start"),
881       (200, "More"),
882       (0, "Hello"),
883       ])
884     self.assertEqual(chgdesc, [
885       ("test/0", "0x0"),
886       ("test/1", "0x1"),
887       ("test/1", "0x1"),
888       ("test/3", "0x3"),
889       ("test/2", "remove"),
890       ("test/2", "modify foobar"),
891       ("test/2", "remove"),
892       ("test/2", "0x2")
893       ])
894     self.assertTrue(compat.all(op == private.data[0]
895                                for (op, _, _, private) in mods))
896     self.assertEqual([private.data for (op, _, _, private) in mods], [
897       ("add", 0, "Hello"),
898       ("add", 1, "World"),
899       ("add", 1, "Start"),
900       ("add", 3, "End"),
901       ("remove", 2, (100, "World")),
902       ("modify", 2, "foobar"),
903       ("remove", 2, (300, "End")),
904       ("add", 2, "More"),
905       ])
906
907
908 class _FakeConfigForGenDiskTemplate:
909   def __init__(self):
910     self._unique_id = itertools.count()
911     self._drbd_minor = itertools.count(20)
912     self._port = itertools.count(constants.FIRST_DRBD_PORT)
913     self._secret = itertools.count()
914
915   def GetVGName(self):
916     return "testvg"
917
918   def GenerateUniqueID(self, ec_id):
919     return "ec%s-uq%s" % (ec_id, self._unique_id.next())
920
921   def AllocateDRBDMinor(self, nodes, instance):
922     return [self._drbd_minor.next()
923             for _ in nodes]
924
925   def AllocatePort(self):
926     return self._port.next()
927
928   def GenerateDRBDSecret(self, ec_id):
929     return "ec%s-secret%s" % (ec_id, self._secret.next())
930
931
932 class _FakeProcForGenDiskTemplate:
933   def GetECId(self):
934     return 0
935
936
937 class TestGenerateDiskTemplate(unittest.TestCase):
938   def setUp(self):
939     nodegroup = objects.NodeGroup(name="ng")
940     nodegroup.UpgradeConfig()
941
942     cfg = _FakeConfigForGenDiskTemplate()
943     proc = _FakeProcForGenDiskTemplate()
944
945     self.lu = _FakeLU(cfg=cfg, proc=proc)
946     self.nodegroup = nodegroup
947
948   def testWrongDiskTemplate(self):
949     gdt = cmdlib._GenerateDiskTemplate
950     disk_template = "##unknown##"
951
952     assert disk_template not in constants.DISK_TEMPLATES
953
954     self.assertRaises(errors.ProgrammerError, gdt, self.lu, disk_template,
955                       "inst26831.example.com", "node30113.example.com", [], [],
956                       NotImplemented, NotImplemented, 0, self.lu.LogInfo,
957                       self.nodegroup.diskparams)
958
959   def testDiskless(self):
960     gdt = cmdlib._GenerateDiskTemplate
961
962     result = gdt(self.lu, constants.DT_DISKLESS, "inst27734.example.com",
963                  "node30113.example.com", [], [],
964                  NotImplemented, NotImplemented, 0, self.lu.LogInfo,
965                  self.nodegroup.diskparams)
966     self.assertEqual(result, [])
967
968   def _TestTrivialDisk(self, template, disk_info, base_index, exp_dev_type,
969                        file_storage_dir=NotImplemented,
970                        file_driver=NotImplemented,
971                        req_file_storage=NotImplemented,
972                        req_shr_file_storage=NotImplemented):
973     gdt = cmdlib._GenerateDiskTemplate
974
975     map(lambda params: utils.ForceDictType(params,
976                                            constants.IDISK_PARAMS_TYPES),
977         disk_info)
978
979     # Check if non-empty list of secondaries is rejected
980     self.assertRaises(errors.ProgrammerError, gdt, self.lu,
981                       template, "inst25088.example.com",
982                       "node185.example.com", ["node323.example.com"], [],
983                       NotImplemented, NotImplemented, base_index,
984                       self.lu.LogInfo, self.nodegroup.diskparams,
985                       _req_file_storage=req_file_storage,
986                       _req_shr_file_storage=req_shr_file_storage)
987
988     result = gdt(self.lu, template, "inst21662.example.com",
989                  "node21741.example.com", [],
990                  disk_info, file_storage_dir, file_driver, base_index,
991                  self.lu.LogInfo, self.nodegroup.diskparams,
992                  _req_file_storage=req_file_storage,
993                  _req_shr_file_storage=req_shr_file_storage)
994
995     for (idx, disk) in enumerate(result):
996       self.assertTrue(isinstance(disk, objects.Disk))
997       self.assertEqual(disk.dev_type, exp_dev_type)
998       self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
999       self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
1000       self.assertTrue(disk.children is None)
1001
1002     self.assertEqual(map(operator.attrgetter("iv_name"), result),
1003       ["disk/%s" % i for i in range(base_index, base_index + len(disk_info))])
1004
1005     return result
1006
1007   def testPlain(self):
1008     disk_info = [{
1009       constants.IDISK_SIZE: 1024,
1010       constants.IDISK_MODE: constants.DISK_RDWR,
1011       }, {
1012       constants.IDISK_SIZE: 4096,
1013       constants.IDISK_VG: "othervg",
1014       constants.IDISK_MODE: constants.DISK_RDWR,
1015       }]
1016
1017     result = self._TestTrivialDisk(constants.DT_PLAIN, disk_info, 3,
1018                                    constants.LD_LV)
1019
1020     self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1021       ("testvg", "ec0-uq0.disk3"),
1022       ("othervg", "ec0-uq1.disk4"),
1023       ])
1024
1025   @staticmethod
1026   def _AllowFileStorage():
1027     pass
1028
1029   @staticmethod
1030   def _ForbidFileStorage():
1031     raise errors.OpPrereqError("Disallowed in test")
1032
1033   def testFile(self):
1034     self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
1035                       constants.DT_FILE, [], 0, NotImplemented,
1036                       req_file_storage=self._ForbidFileStorage)
1037     self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
1038                       constants.DT_SHARED_FILE, [], 0, NotImplemented,
1039                       req_shr_file_storage=self._ForbidFileStorage)
1040
1041     for disk_template in [constants.DT_FILE, constants.DT_SHARED_FILE]:
1042       disk_info = [{
1043         constants.IDISK_SIZE: 80 * 1024,
1044         constants.IDISK_MODE: constants.DISK_RDONLY,
1045         }, {
1046         constants.IDISK_SIZE: 4096,
1047         constants.IDISK_MODE: constants.DISK_RDWR,
1048         }, {
1049         constants.IDISK_SIZE: 6 * 1024,
1050         constants.IDISK_MODE: constants.DISK_RDWR,
1051         }]
1052
1053       result = self._TestTrivialDisk(disk_template, disk_info, 2,
1054         constants.LD_FILE, file_storage_dir="/tmp",
1055         file_driver=constants.FD_BLKTAP,
1056         req_file_storage=self._AllowFileStorage,
1057         req_shr_file_storage=self._AllowFileStorage)
1058
1059       self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1060         (constants.FD_BLKTAP, "/tmp/disk2"),
1061         (constants.FD_BLKTAP, "/tmp/disk3"),
1062         (constants.FD_BLKTAP, "/tmp/disk4"),
1063         ])
1064
1065   def testBlock(self):
1066     disk_info = [{
1067       constants.IDISK_SIZE: 8 * 1024,
1068       constants.IDISK_MODE: constants.DISK_RDWR,
1069       constants.IDISK_ADOPT: "/tmp/some/block/dev",
1070       }]
1071
1072     result = self._TestTrivialDisk(constants.DT_BLOCK, disk_info, 10,
1073                                    constants.LD_BLOCKDEV)
1074
1075     self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1076       (constants.BLOCKDEV_DRIVER_MANUAL, "/tmp/some/block/dev"),
1077       ])
1078
1079   def testRbd(self):
1080     disk_info = [{
1081       constants.IDISK_SIZE: 8 * 1024,
1082       constants.IDISK_MODE: constants.DISK_RDONLY,
1083       }, {
1084       constants.IDISK_SIZE: 100 * 1024,
1085       constants.IDISK_MODE: constants.DISK_RDWR,
1086       }]
1087
1088     result = self._TestTrivialDisk(constants.DT_RBD, disk_info, 0,
1089                                    constants.LD_RBD)
1090
1091     self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1092       ("rbd", "ec0-uq0.rbd.disk0"),
1093       ("rbd", "ec0-uq1.rbd.disk1"),
1094       ])
1095
1096   def testDrbd8(self):
1097     gdt = cmdlib._GenerateDiskTemplate
1098     drbd8_defaults = constants.DISK_LD_DEFAULTS[constants.LD_DRBD8]
1099     drbd8_default_metavg = drbd8_defaults[constants.LDP_DEFAULT_METAVG]
1100
1101     disk_info = [{
1102       constants.IDISK_SIZE: 1024,
1103       constants.IDISK_MODE: constants.DISK_RDWR,
1104       }, {
1105       constants.IDISK_SIZE: 100 * 1024,
1106       constants.IDISK_MODE: constants.DISK_RDONLY,
1107       constants.IDISK_METAVG: "metavg",
1108       }, {
1109       constants.IDISK_SIZE: 4096,
1110       constants.IDISK_MODE: constants.DISK_RDWR,
1111       constants.IDISK_VG: "vgxyz",
1112       },
1113       ]
1114
1115     exp_logical_ids = [[
1116       (self.lu.cfg.GetVGName(), "ec0-uq0.disk0_data"),
1117       (drbd8_default_metavg, "ec0-uq0.disk0_meta"),
1118       ], [
1119       (self.lu.cfg.GetVGName(), "ec0-uq1.disk1_data"),
1120       ("metavg", "ec0-uq1.disk1_meta"),
1121       ], [
1122       ("vgxyz", "ec0-uq2.disk2_data"),
1123       (drbd8_default_metavg, "ec0-uq2.disk2_meta"),
1124       ]]
1125
1126     assert len(exp_logical_ids) == len(disk_info)
1127
1128     map(lambda params: utils.ForceDictType(params,
1129                                            constants.IDISK_PARAMS_TYPES),
1130         disk_info)
1131
1132     # Check if empty list of secondaries is rejected
1133     self.assertRaises(errors.ProgrammerError, gdt, self.lu, constants.DT_DRBD8,
1134                       "inst827.example.com", "node1334.example.com", [],
1135                       disk_info, NotImplemented, NotImplemented, 0,
1136                       self.lu.LogInfo, self.nodegroup.diskparams)
1137
1138     result = gdt(self.lu, constants.DT_DRBD8, "inst827.example.com",
1139                  "node1334.example.com", ["node12272.example.com"],
1140                  disk_info, NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1141                  self.nodegroup.diskparams)
1142
1143     for (idx, disk) in enumerate(result):
1144       self.assertTrue(isinstance(disk, objects.Disk))
1145       self.assertEqual(disk.dev_type, constants.LD_DRBD8)
1146       self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
1147       self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
1148
1149       for child in disk.children:
1150         self.assertTrue(isinstance(disk, objects.Disk))
1151         self.assertEqual(child.dev_type, constants.LD_LV)
1152         self.assertTrue(child.children is None)
1153
1154       self.assertEqual(map(operator.attrgetter("logical_id"), disk.children),
1155                        exp_logical_ids[idx])
1156
1157       self.assertEqual(len(disk.children), 2)
1158       self.assertEqual(disk.children[0].size, disk.size)
1159       self.assertEqual(disk.children[1].size, cmdlib.DRBD_META_SIZE)
1160
1161     self.assertEqual(map(operator.attrgetter("iv_name"), result),
1162                      ["disk/0", "disk/1", "disk/2"])
1163
1164     self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1165       ("node1334.example.com", "node12272.example.com",
1166        constants.FIRST_DRBD_PORT, 20, 21, "ec0-secret0"),
1167       ("node1334.example.com", "node12272.example.com",
1168        constants.FIRST_DRBD_PORT + 1, 22, 23, "ec0-secret1"),
1169       ("node1334.example.com", "node12272.example.com",
1170        constants.FIRST_DRBD_PORT + 2, 24, 25, "ec0-secret2"),
1171       ])
1172
1173
1174 if __name__ == "__main__":
1175   testutils.GanetiTestProgram()