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