RPC: Add a new client type for DNS only
[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], 1, _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, [], 1)
654
655   def testInvalidSpec(self):
656     spec = _SpecWrapper([None, False, "foo", None, "bar", None])
657     compute_fn = spec.ComputeMinMaxSpec
658     ret = cmdlib._ComputeIPolicySpecViolation(NotImplemented, 1024, 1, 1, 1,
659                                               [1024], 1, _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                spindle_use):
667     self.mem_size = mem_size
668     self.cpu_count = cpu_count
669     self.disk_count = disk_count
670     self.nic_count = nic_count
671     self.disk_sizes = disk_sizes
672     self.spindle_use = spindle_use
673
674   def __call__(self, _, mem_size, cpu_count, disk_count, nic_count, disk_sizes,
675                spindle_use):
676     assert self.mem_size == mem_size
677     assert self.cpu_count == cpu_count
678     assert self.disk_count == disk_count
679     assert self.nic_count == nic_count
680     assert self.disk_sizes == disk_sizes
681     assert self.spindle_use == spindle_use
682
683     return []
684
685
686 class TestComputeIPolicyInstanceViolation(unittest.TestCase):
687   def test(self):
688     beparams = {
689       constants.BE_MAXMEM: 2048,
690       constants.BE_VCPUS: 2,
691       constants.BE_SPINDLE_USE: 4,
692       }
693     disks = [objects.Disk(size=512)]
694     instance = objects.Instance(beparams=beparams, disks=disks, nics=[])
695     stub = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512], 4)
696     ret = cmdlib._ComputeIPolicyInstanceViolation(NotImplemented, instance,
697                                                   _compute_fn=stub)
698     self.assertEqual(ret, [])
699
700
701 class TestComputeIPolicyInstanceSpecViolation(unittest.TestCase):
702   def test(self):
703     ispec = {
704       constants.ISPEC_MEM_SIZE: 2048,
705       constants.ISPEC_CPU_COUNT: 2,
706       constants.ISPEC_DISK_COUNT: 1,
707       constants.ISPEC_DISK_SIZE: [512],
708       constants.ISPEC_NIC_COUNT: 0,
709       constants.ISPEC_SPINDLE_USE: 1,
710       }
711     stub = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512], 1)
712     ret = cmdlib._ComputeIPolicyInstanceSpecViolation(NotImplemented, ispec,
713                                                       _compute_fn=stub)
714     self.assertEqual(ret, [])
715
716
717 class _CallRecorder:
718   def __init__(self, return_value=None):
719     self.called = False
720     self.return_value = return_value
721
722   def __call__(self, *args):
723     self.called = True
724     return self.return_value
725
726
727 class TestComputeIPolicyNodeViolation(unittest.TestCase):
728   def setUp(self):
729     self.recorder = _CallRecorder(return_value=[])
730
731   def testSameGroup(self):
732     ret = cmdlib._ComputeIPolicyNodeViolation(NotImplemented, NotImplemented,
733                                               "foo", "foo",
734                                               _compute_fn=self.recorder)
735     self.assertFalse(self.recorder.called)
736     self.assertEqual(ret, [])
737
738   def testDifferentGroup(self):
739     ret = cmdlib._ComputeIPolicyNodeViolation(NotImplemented, NotImplemented,
740                                               "foo", "bar",
741                                               _compute_fn=self.recorder)
742     self.assertTrue(self.recorder.called)
743     self.assertEqual(ret, [])
744
745
746 class _FakeConfigForTargetNodeIPolicy:
747   def __init__(self, node_info=NotImplemented):
748     self._node_info = node_info
749
750   def GetNodeInfo(self, _):
751     return self._node_info
752
753
754 class TestCheckTargetNodeIPolicy(unittest.TestCase):
755   def setUp(self):
756     self.instance = objects.Instance(primary_node="blubb")
757     self.target_node = objects.Node(group="bar")
758     node_info = objects.Node(group="foo")
759     fake_cfg = _FakeConfigForTargetNodeIPolicy(node_info=node_info)
760     self.lu = _FakeLU(cfg=fake_cfg)
761
762   def testNoViolation(self):
763     compute_recoder = _CallRecorder(return_value=[])
764     cmdlib._CheckTargetNodeIPolicy(self.lu, NotImplemented, self.instance,
765                                    self.target_node,
766                                    _compute_fn=compute_recoder)
767     self.assertTrue(compute_recoder.called)
768     self.assertEqual(self.lu.warning_log, [])
769
770   def testNoIgnore(self):
771     compute_recoder = _CallRecorder(return_value=["mem_size not in range"])
772     self.assertRaises(errors.OpPrereqError, cmdlib._CheckTargetNodeIPolicy,
773                       self.lu, NotImplemented, self.instance, self.target_node,
774                       _compute_fn=compute_recoder)
775     self.assertTrue(compute_recoder.called)
776     self.assertEqual(self.lu.warning_log, [])
777
778   def testIgnoreViolation(self):
779     compute_recoder = _CallRecorder(return_value=["mem_size not in range"])
780     cmdlib._CheckTargetNodeIPolicy(self.lu, NotImplemented, self.instance,
781                                    self.target_node, ignore=True,
782                                    _compute_fn=compute_recoder)
783     self.assertTrue(compute_recoder.called)
784     msg = ("Instance does not meet target node group's (bar) instance policy:"
785            " mem_size not in range")
786     self.assertEqual(self.lu.warning_log, [(msg, ())])
787
788
789 class TestApplyContainerMods(unittest.TestCase):
790   def testEmptyContainer(self):
791     container = []
792     chgdesc = []
793     cmdlib.ApplyContainerMods("test", container, chgdesc, [], None, None, None)
794     self.assertEqual(container, [])
795     self.assertEqual(chgdesc, [])
796
797   def testAdd(self):
798     container = []
799     chgdesc = []
800     mods = cmdlib.PrepareContainerMods([
801       (constants.DDM_ADD, -1, "Hello"),
802       (constants.DDM_ADD, -1, "World"),
803       (constants.DDM_ADD, 0, "Start"),
804       (constants.DDM_ADD, -1, "End"),
805       ], None)
806     cmdlib.ApplyContainerMods("test", container, chgdesc, mods,
807                               None, None, None)
808     self.assertEqual(container, ["Start", "Hello", "World", "End"])
809     self.assertEqual(chgdesc, [])
810
811     mods = cmdlib.PrepareContainerMods([
812       (constants.DDM_ADD, 0, "zero"),
813       (constants.DDM_ADD, 3, "Added"),
814       (constants.DDM_ADD, 5, "four"),
815       (constants.DDM_ADD, 7, "xyz"),
816       ], None)
817     cmdlib.ApplyContainerMods("test", container, chgdesc, mods,
818                               None, None, None)
819     self.assertEqual(container,
820                      ["zero", "Start", "Hello", "Added", "World", "four",
821                       "End", "xyz"])
822     self.assertEqual(chgdesc, [])
823
824     for idx in [-2, len(container) + 1]:
825       mods = cmdlib.PrepareContainerMods([
826         (constants.DDM_ADD, idx, "error"),
827         ], None)
828       self.assertRaises(IndexError, cmdlib.ApplyContainerMods,
829                         "test", container, None, mods, None, None, None)
830
831   def testRemoveError(self):
832     for idx in [0, 1, 2, 100, -1, -4]:
833       mods = cmdlib.PrepareContainerMods([
834         (constants.DDM_REMOVE, idx, None),
835         ], None)
836       self.assertRaises(IndexError, cmdlib.ApplyContainerMods,
837                         "test", [], None, mods, None, None, None)
838
839     mods = cmdlib.PrepareContainerMods([
840       (constants.DDM_REMOVE, 0, object()),
841       ], None)
842     self.assertRaises(AssertionError, cmdlib.ApplyContainerMods,
843                       "test", [""], None, mods, None, None, None)
844
845   def testAddError(self):
846     for idx in range(-100, -1) + [100]:
847       mods = cmdlib.PrepareContainerMods([
848         (constants.DDM_ADD, idx, None),
849         ], None)
850       self.assertRaises(IndexError, cmdlib.ApplyContainerMods,
851                         "test", [], None, mods, None, None, None)
852
853   def testRemove(self):
854     container = ["item 1", "item 2"]
855     mods = cmdlib.PrepareContainerMods([
856       (constants.DDM_ADD, -1, "aaa"),
857       (constants.DDM_REMOVE, -1, None),
858       (constants.DDM_ADD, -1, "bbb"),
859       ], None)
860     chgdesc = []
861     cmdlib.ApplyContainerMods("test", container, chgdesc, mods,
862                               None, None, None)
863     self.assertEqual(container, ["item 1", "item 2", "bbb"])
864     self.assertEqual(chgdesc, [
865       ("test/2", "remove"),
866       ])
867
868   def testModify(self):
869     container = ["item 1", "item 2"]
870     mods = cmdlib.PrepareContainerMods([
871       (constants.DDM_MODIFY, -1, "a"),
872       (constants.DDM_MODIFY, 0, "b"),
873       (constants.DDM_MODIFY, 1, "c"),
874       ], None)
875     chgdesc = []
876     cmdlib.ApplyContainerMods("test", container, chgdesc, mods,
877                               None, None, None)
878     self.assertEqual(container, ["item 1", "item 2"])
879     self.assertEqual(chgdesc, [])
880
881     for idx in [-2, len(container) + 1]:
882       mods = cmdlib.PrepareContainerMods([
883         (constants.DDM_MODIFY, idx, "error"),
884         ], None)
885       self.assertRaises(IndexError, cmdlib.ApplyContainerMods,
886                         "test", container, None, mods, None, None, None)
887
888   class _PrivateData:
889     def __init__(self):
890       self.data = None
891
892   @staticmethod
893   def _CreateTestFn(idx, params, private):
894     private.data = ("add", idx, params)
895     return ((100 * idx, params), [
896       ("test/%s" % idx, hex(idx)),
897       ])
898
899   @staticmethod
900   def _ModifyTestFn(idx, item, params, private):
901     private.data = ("modify", idx, params)
902     return [
903       ("test/%s" % idx, "modify %s" % params),
904       ]
905
906   @staticmethod
907   def _RemoveTestFn(idx, item, private):
908     private.data = ("remove", idx, item)
909
910   def testAddWithCreateFunction(self):
911     container = []
912     chgdesc = []
913     mods = cmdlib.PrepareContainerMods([
914       (constants.DDM_ADD, -1, "Hello"),
915       (constants.DDM_ADD, -1, "World"),
916       (constants.DDM_ADD, 0, "Start"),
917       (constants.DDM_ADD, -1, "End"),
918       (constants.DDM_REMOVE, 2, None),
919       (constants.DDM_MODIFY, -1, "foobar"),
920       (constants.DDM_REMOVE, 2, None),
921       (constants.DDM_ADD, 1, "More"),
922       ], self._PrivateData)
923     cmdlib.ApplyContainerMods("test", container, chgdesc, mods,
924       self._CreateTestFn, self._ModifyTestFn, self._RemoveTestFn)
925     self.assertEqual(container, [
926       (000, "Start"),
927       (100, "More"),
928       (000, "Hello"),
929       ])
930     self.assertEqual(chgdesc, [
931       ("test/0", "0x0"),
932       ("test/1", "0x1"),
933       ("test/0", "0x0"),
934       ("test/3", "0x3"),
935       ("test/2", "remove"),
936       ("test/2", "modify foobar"),
937       ("test/2", "remove"),
938       ("test/1", "0x1")
939       ])
940     self.assertTrue(compat.all(op == private.data[0]
941                                for (op, _, _, private) in mods))
942     self.assertEqual([private.data for (op, _, _, private) in mods], [
943       ("add", 0, "Hello"),
944       ("add", 1, "World"),
945       ("add", 0, "Start"),
946       ("add", 3, "End"),
947       ("remove", 2, (100, "World")),
948       ("modify", 2, "foobar"),
949       ("remove", 2, (300, "End")),
950       ("add", 1, "More"),
951       ])
952
953
954 class _FakeConfigForGenDiskTemplate:
955   def __init__(self):
956     self._unique_id = itertools.count()
957     self._drbd_minor = itertools.count(20)
958     self._port = itertools.count(constants.FIRST_DRBD_PORT)
959     self._secret = itertools.count()
960
961   def GetVGName(self):
962     return "testvg"
963
964   def GenerateUniqueID(self, ec_id):
965     return "ec%s-uq%s" % (ec_id, self._unique_id.next())
966
967   def AllocateDRBDMinor(self, nodes, instance):
968     return [self._drbd_minor.next()
969             for _ in nodes]
970
971   def AllocatePort(self):
972     return self._port.next()
973
974   def GenerateDRBDSecret(self, ec_id):
975     return "ec%s-secret%s" % (ec_id, self._secret.next())
976
977
978 class _FakeProcForGenDiskTemplate:
979   def GetECId(self):
980     return 0
981
982
983 class TestGenerateDiskTemplate(unittest.TestCase):
984   def setUp(self):
985     nodegroup = objects.NodeGroup(name="ng")
986     nodegroup.UpgradeConfig()
987
988     cfg = _FakeConfigForGenDiskTemplate()
989     proc = _FakeProcForGenDiskTemplate()
990
991     self.lu = _FakeLU(cfg=cfg, proc=proc)
992     self.nodegroup = nodegroup
993
994   def testWrongDiskTemplate(self):
995     gdt = cmdlib._GenerateDiskTemplate
996     disk_template = "##unknown##"
997
998     assert disk_template not in constants.DISK_TEMPLATES
999
1000     self.assertRaises(errors.ProgrammerError, gdt, self.lu, disk_template,
1001                       "inst26831.example.com", "node30113.example.com", [], [],
1002                       NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1003                       self.nodegroup.diskparams)
1004
1005   def testDiskless(self):
1006     gdt = cmdlib._GenerateDiskTemplate
1007
1008     result = gdt(self.lu, constants.DT_DISKLESS, "inst27734.example.com",
1009                  "node30113.example.com", [], [],
1010                  NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1011                  self.nodegroup.diskparams)
1012     self.assertEqual(result, [])
1013
1014   def _TestTrivialDisk(self, template, disk_info, base_index, exp_dev_type,
1015                        file_storage_dir=NotImplemented,
1016                        file_driver=NotImplemented,
1017                        req_file_storage=NotImplemented,
1018                        req_shr_file_storage=NotImplemented):
1019     gdt = cmdlib._GenerateDiskTemplate
1020
1021     map(lambda params: utils.ForceDictType(params,
1022                                            constants.IDISK_PARAMS_TYPES),
1023         disk_info)
1024
1025     # Check if non-empty list of secondaries is rejected
1026     self.assertRaises(errors.ProgrammerError, gdt, self.lu,
1027                       template, "inst25088.example.com",
1028                       "node185.example.com", ["node323.example.com"], [],
1029                       NotImplemented, NotImplemented, base_index,
1030                       self.lu.LogInfo, self.nodegroup.diskparams,
1031                       _req_file_storage=req_file_storage,
1032                       _req_shr_file_storage=req_shr_file_storage)
1033
1034     result = gdt(self.lu, template, "inst21662.example.com",
1035                  "node21741.example.com", [],
1036                  disk_info, file_storage_dir, file_driver, base_index,
1037                  self.lu.LogInfo, self.nodegroup.diskparams,
1038                  _req_file_storage=req_file_storage,
1039                  _req_shr_file_storage=req_shr_file_storage)
1040
1041     for (idx, disk) in enumerate(result):
1042       self.assertTrue(isinstance(disk, objects.Disk))
1043       self.assertEqual(disk.dev_type, exp_dev_type)
1044       self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
1045       self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
1046       self.assertTrue(disk.children is None)
1047
1048     self._CheckIvNames(result, base_index, base_index + len(disk_info))
1049     cmdlib._UpdateIvNames(base_index, result)
1050     self._CheckIvNames(result, base_index, base_index + len(disk_info))
1051
1052     return result
1053
1054   def _CheckIvNames(self, disks, base_index, end_index):
1055     self.assertEqual(map(operator.attrgetter("iv_name"), disks),
1056                      ["disk/%s" % i for i in range(base_index, end_index)])
1057
1058   def testPlain(self):
1059     disk_info = [{
1060       constants.IDISK_SIZE: 1024,
1061       constants.IDISK_MODE: constants.DISK_RDWR,
1062       }, {
1063       constants.IDISK_SIZE: 4096,
1064       constants.IDISK_VG: "othervg",
1065       constants.IDISK_MODE: constants.DISK_RDWR,
1066       }]
1067
1068     result = self._TestTrivialDisk(constants.DT_PLAIN, disk_info, 3,
1069                                    constants.LD_LV)
1070
1071     self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1072       ("testvg", "ec0-uq0.disk3"),
1073       ("othervg", "ec0-uq1.disk4"),
1074       ])
1075
1076   @staticmethod
1077   def _AllowFileStorage():
1078     pass
1079
1080   @staticmethod
1081   def _ForbidFileStorage():
1082     raise errors.OpPrereqError("Disallowed in test")
1083
1084   def testFile(self):
1085     self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
1086                       constants.DT_FILE, [], 0, NotImplemented,
1087                       req_file_storage=self._ForbidFileStorage)
1088     self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
1089                       constants.DT_SHARED_FILE, [], 0, NotImplemented,
1090                       req_shr_file_storage=self._ForbidFileStorage)
1091
1092     for disk_template in [constants.DT_FILE, constants.DT_SHARED_FILE]:
1093       disk_info = [{
1094         constants.IDISK_SIZE: 80 * 1024,
1095         constants.IDISK_MODE: constants.DISK_RDONLY,
1096         }, {
1097         constants.IDISK_SIZE: 4096,
1098         constants.IDISK_MODE: constants.DISK_RDWR,
1099         }, {
1100         constants.IDISK_SIZE: 6 * 1024,
1101         constants.IDISK_MODE: constants.DISK_RDWR,
1102         }]
1103
1104       result = self._TestTrivialDisk(disk_template, disk_info, 2,
1105         constants.LD_FILE, file_storage_dir="/tmp",
1106         file_driver=constants.FD_BLKTAP,
1107         req_file_storage=self._AllowFileStorage,
1108         req_shr_file_storage=self._AllowFileStorage)
1109
1110       self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1111         (constants.FD_BLKTAP, "/tmp/disk2"),
1112         (constants.FD_BLKTAP, "/tmp/disk3"),
1113         (constants.FD_BLKTAP, "/tmp/disk4"),
1114         ])
1115
1116   def testBlock(self):
1117     disk_info = [{
1118       constants.IDISK_SIZE: 8 * 1024,
1119       constants.IDISK_MODE: constants.DISK_RDWR,
1120       constants.IDISK_ADOPT: "/tmp/some/block/dev",
1121       }]
1122
1123     result = self._TestTrivialDisk(constants.DT_BLOCK, disk_info, 10,
1124                                    constants.LD_BLOCKDEV)
1125
1126     self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1127       (constants.BLOCKDEV_DRIVER_MANUAL, "/tmp/some/block/dev"),
1128       ])
1129
1130   def testRbd(self):
1131     disk_info = [{
1132       constants.IDISK_SIZE: 8 * 1024,
1133       constants.IDISK_MODE: constants.DISK_RDONLY,
1134       }, {
1135       constants.IDISK_SIZE: 100 * 1024,
1136       constants.IDISK_MODE: constants.DISK_RDWR,
1137       }]
1138
1139     result = self._TestTrivialDisk(constants.DT_RBD, disk_info, 0,
1140                                    constants.LD_RBD)
1141
1142     self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1143       ("rbd", "ec0-uq0.rbd.disk0"),
1144       ("rbd", "ec0-uq1.rbd.disk1"),
1145       ])
1146
1147   def testDrbd8(self):
1148     gdt = cmdlib._GenerateDiskTemplate
1149     drbd8_defaults = constants.DISK_LD_DEFAULTS[constants.LD_DRBD8]
1150     drbd8_default_metavg = drbd8_defaults[constants.LDP_DEFAULT_METAVG]
1151
1152     disk_info = [{
1153       constants.IDISK_SIZE: 1024,
1154       constants.IDISK_MODE: constants.DISK_RDWR,
1155       }, {
1156       constants.IDISK_SIZE: 100 * 1024,
1157       constants.IDISK_MODE: constants.DISK_RDONLY,
1158       constants.IDISK_METAVG: "metavg",
1159       }, {
1160       constants.IDISK_SIZE: 4096,
1161       constants.IDISK_MODE: constants.DISK_RDWR,
1162       constants.IDISK_VG: "vgxyz",
1163       },
1164       ]
1165
1166     exp_logical_ids = [[
1167       (self.lu.cfg.GetVGName(), "ec0-uq0.disk0_data"),
1168       (drbd8_default_metavg, "ec0-uq0.disk0_meta"),
1169       ], [
1170       (self.lu.cfg.GetVGName(), "ec0-uq1.disk1_data"),
1171       ("metavg", "ec0-uq1.disk1_meta"),
1172       ], [
1173       ("vgxyz", "ec0-uq2.disk2_data"),
1174       (drbd8_default_metavg, "ec0-uq2.disk2_meta"),
1175       ]]
1176
1177     assert len(exp_logical_ids) == len(disk_info)
1178
1179     map(lambda params: utils.ForceDictType(params,
1180                                            constants.IDISK_PARAMS_TYPES),
1181         disk_info)
1182
1183     # Check if empty list of secondaries is rejected
1184     self.assertRaises(errors.ProgrammerError, gdt, self.lu, constants.DT_DRBD8,
1185                       "inst827.example.com", "node1334.example.com", [],
1186                       disk_info, NotImplemented, NotImplemented, 0,
1187                       self.lu.LogInfo, self.nodegroup.diskparams)
1188
1189     result = gdt(self.lu, constants.DT_DRBD8, "inst827.example.com",
1190                  "node1334.example.com", ["node12272.example.com"],
1191                  disk_info, NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1192                  self.nodegroup.diskparams)
1193
1194     for (idx, disk) in enumerate(result):
1195       self.assertTrue(isinstance(disk, objects.Disk))
1196       self.assertEqual(disk.dev_type, constants.LD_DRBD8)
1197       self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
1198       self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
1199
1200       for child in disk.children:
1201         self.assertTrue(isinstance(disk, objects.Disk))
1202         self.assertEqual(child.dev_type, constants.LD_LV)
1203         self.assertTrue(child.children is None)
1204
1205       self.assertEqual(map(operator.attrgetter("logical_id"), disk.children),
1206                        exp_logical_ids[idx])
1207
1208       self.assertEqual(len(disk.children), 2)
1209       self.assertEqual(disk.children[0].size, disk.size)
1210       self.assertEqual(disk.children[1].size, cmdlib.DRBD_META_SIZE)
1211
1212     self._CheckIvNames(result, 0, len(disk_info))
1213     cmdlib._UpdateIvNames(0, result)
1214     self._CheckIvNames(result, 0, len(disk_info))
1215
1216     self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1217       ("node1334.example.com", "node12272.example.com",
1218        constants.FIRST_DRBD_PORT, 20, 21, "ec0-secret0"),
1219       ("node1334.example.com", "node12272.example.com",
1220        constants.FIRST_DRBD_PORT + 1, 22, 23, "ec0-secret1"),
1221       ("node1334.example.com", "node12272.example.com",
1222        constants.FIRST_DRBD_PORT + 2, 24, 25, "ec0-secret2"),
1223       ])
1224
1225
1226 if __name__ == "__main__":
1227   testutils.GanetiTestProgram()