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