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