Assign unique filenames to filebased disks
[ganeti-local] / test / py / ganeti.cmdlib_unittest.py
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2008, 2011, 2012, 2013 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 re
27 import unittest
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.cmdlib import cluster
38 from ganeti.cmdlib import group
39 from ganeti.cmdlib import instance
40 from ganeti.cmdlib import instance_storage
41 from ganeti.cmdlib import instance_utils
42 from ganeti.cmdlib import common
43 from ganeti.cmdlib import query
44 from ganeti import opcodes
45 from ganeti import errors
46 from ganeti import utils
47 from ganeti import luxi
48 from ganeti import ht
49 from ganeti import objects
50 from ganeti import compat
51 from ganeti import rpc
52 from ganeti import locking
53 from ganeti import pathutils
54 from ganeti.masterd import iallocator
55 from ganeti.hypervisor import hv_xen
56
57 import testutils
58 import mocks
59
60
61 class TestCertVerification(testutils.GanetiTestCase):
62   def setUp(self):
63     testutils.GanetiTestCase.setUp(self)
64
65     self.tmpdir = tempfile.mkdtemp()
66
67   def tearDown(self):
68     shutil.rmtree(self.tmpdir)
69
70   def testVerifyCertificate(self):
71     cluster._VerifyCertificate(testutils.TestDataFilename("cert1.pem"))
72
73     nonexist_filename = os.path.join(self.tmpdir, "does-not-exist")
74
75     (errcode, msg) = cluster._VerifyCertificate(nonexist_filename)
76     self.assertEqual(errcode, cluster.LUClusterVerifyConfig.ETYPE_ERROR)
77
78     # Try to load non-certificate file
79     invalid_cert = testutils.TestDataFilename("bdev-net.txt")
80     (errcode, msg) = cluster._VerifyCertificate(invalid_cert)
81     self.assertEqual(errcode, cluster.LUClusterVerifyConfig.ETYPE_ERROR)
82
83
84 class TestOpcodeParams(testutils.GanetiTestCase):
85   def testParamsStructures(self):
86     for op in sorted(mcpu.Processor.DISPATCH_TABLE):
87       lu = mcpu.Processor.DISPATCH_TABLE[op]
88       lu_name = lu.__name__
89       self.failIf(hasattr(lu, "_OP_REQP"),
90                   msg=("LU '%s' has old-style _OP_REQP" % lu_name))
91       self.failIf(hasattr(lu, "_OP_DEFS"),
92                   msg=("LU '%s' has old-style _OP_DEFS" % lu_name))
93       self.failIf(hasattr(lu, "_OP_PARAMS"),
94                   msg=("LU '%s' has old-style _OP_PARAMS" % lu_name))
95
96
97 class TestIAllocatorChecks(testutils.GanetiTestCase):
98   def testFunction(self):
99     class TestLU(object):
100       def __init__(self, opcode):
101         self.cfg = mocks.FakeConfig()
102         self.op = opcode
103
104     class OpTest(opcodes.OpCode):
105        OP_PARAMS = [
106         ("iallocator", None, ht.NoType, None),
107         ("node", None, ht.NoType, None),
108         ]
109
110     default_iallocator = mocks.FakeConfig().GetDefaultIAllocator()
111     other_iallocator = default_iallocator + "_not"
112
113     op = OpTest()
114     lu = TestLU(op)
115
116     c_i = lambda: common.CheckIAllocatorOrNode(lu, "iallocator", "node")
117
118     # Neither node nor iallocator given
119     for n in (None, []):
120       op.iallocator = None
121       op.node = n
122       c_i()
123       self.assertEqual(lu.op.iallocator, default_iallocator)
124       self.assertEqual(lu.op.node, n)
125
126     # Both, iallocator and node given
127     for a in ("test", constants.DEFAULT_IALLOCATOR_SHORTCUT):
128       op.iallocator = a
129       op.node = "test"
130       self.assertRaises(errors.OpPrereqError, c_i)
131
132     # Only iallocator given
133     for n in (None, []):
134       op.iallocator = other_iallocator
135       op.node = n
136       c_i()
137       self.assertEqual(lu.op.iallocator, other_iallocator)
138       self.assertEqual(lu.op.node, n)
139
140     # Only node given
141     op.iallocator = None
142     op.node = "node"
143     c_i()
144     self.assertEqual(lu.op.iallocator, None)
145     self.assertEqual(lu.op.node, "node")
146
147     # Asked for default iallocator, no node given
148     op.iallocator = constants.DEFAULT_IALLOCATOR_SHORTCUT
149     op.node = None
150     c_i()
151     self.assertEqual(lu.op.iallocator, default_iallocator)
152     self.assertEqual(lu.op.node, None)
153
154     # No node, iallocator or default iallocator
155     op.iallocator = None
156     op.node = None
157     lu.cfg.GetDefaultIAllocator = lambda: None
158     self.assertRaises(errors.OpPrereqError, c_i)
159
160
161 class TestLUTestJqueue(unittest.TestCase):
162   def test(self):
163     self.assert_(cmdlib.LUTestJqueue._CLIENT_CONNECT_TIMEOUT <
164                  (luxi.WFJC_TIMEOUT * 0.75),
165                  msg=("Client timeout too high, might not notice bugs"
166                       " in WaitForJobChange"))
167
168
169 class TestLUQuery(unittest.TestCase):
170   def test(self):
171     self.assertEqual(sorted(query._QUERY_IMPL.keys()),
172                      sorted(constants.QR_VIA_OP))
173
174     assert constants.QR_NODE in constants.QR_VIA_OP
175     assert constants.QR_INSTANCE in constants.QR_VIA_OP
176
177     for i in constants.QR_VIA_OP:
178       self.assert_(query._GetQueryImplementation(i))
179
180     self.assertRaises(errors.OpPrereqError, query._GetQueryImplementation,
181                       "")
182     self.assertRaises(errors.OpPrereqError, query._GetQueryImplementation,
183                       "xyz")
184
185
186 class TestLUGroupAssignNodes(unittest.TestCase):
187
188   def testCheckAssignmentForSplitInstances(self):
189     node_data = dict((n, objects.Node(name=n, group=g))
190                      for (n, g) in [("n1a", "g1"), ("n1b", "g1"),
191                                     ("n2a", "g2"), ("n2b", "g2"),
192                                     ("n3a", "g3"), ("n3b", "g3"),
193                                     ("n3c", "g3"),
194                                     ])
195
196     def Instance(uuid, pnode, snode):
197       if snode is None:
198         disks = []
199         disk_template = constants.DT_DISKLESS
200       else:
201         disks = [objects.Disk(dev_type=constants.DT_DRBD8,
202                               logical_id=[pnode, snode, 1, 17, 17])]
203         disk_template = constants.DT_DRBD8
204
205       return objects.Instance(name="%s-name" % uuid, uuid="%s" % uuid,
206                               primary_node=pnode, disks=disks,
207                               disk_template=disk_template)
208
209     instance_data = dict((uuid, Instance(uuid, pnode, snode))
210                          for uuid, pnode, snode in [("inst1a", "n1a", "n1b"),
211                                                     ("inst1b", "n1b", "n1a"),
212                                                     ("inst2a", "n2a", "n2b"),
213                                                     ("inst3a", "n3a", None),
214                                                     ("inst3b", "n3b", "n1b"),
215                                                     ("inst3c", "n3b", "n2b"),
216                                                     ])
217
218     # Test first with the existing state.
219     (new, prev) = \
220       group.LUGroupAssignNodes.CheckAssignmentForSplitInstances([],
221                                                                 node_data,
222                                                                 instance_data)
223
224     self.assertEqual([], new)
225     self.assertEqual(set(["inst3b", "inst3c"]), set(prev))
226
227     # And now some changes.
228     (new, prev) = \
229       group.LUGroupAssignNodes.CheckAssignmentForSplitInstances([("n1b",
230                                                                   "g3")],
231                                                                 node_data,
232                                                                 instance_data)
233
234     self.assertEqual(set(["inst1a", "inst1b"]), set(new))
235     self.assertEqual(set(["inst3c"]), set(prev))
236
237
238 class TestClusterVerifySsh(unittest.TestCase):
239   def testMultipleGroups(self):
240     fn = cluster.LUClusterVerifyGroup._SelectSshCheckNodes
241     mygroupnodes = [
242       objects.Node(name="node20", group="my", offline=False),
243       objects.Node(name="node21", group="my", offline=False),
244       objects.Node(name="node22", group="my", offline=False),
245       objects.Node(name="node23", group="my", offline=False),
246       objects.Node(name="node24", group="my", offline=False),
247       objects.Node(name="node25", group="my", offline=False),
248       objects.Node(name="node26", group="my", offline=True),
249       ]
250     nodes = [
251       objects.Node(name="node1", group="g1", offline=True),
252       objects.Node(name="node2", group="g1", offline=False),
253       objects.Node(name="node3", group="g1", offline=False),
254       objects.Node(name="node4", group="g1", offline=True),
255       objects.Node(name="node5", group="g1", offline=False),
256       objects.Node(name="node10", group="xyz", offline=False),
257       objects.Node(name="node11", group="xyz", offline=False),
258       objects.Node(name="node40", group="alloff", offline=True),
259       objects.Node(name="node41", group="alloff", offline=True),
260       objects.Node(name="node50", group="aaa", offline=False),
261       ] + mygroupnodes
262     assert not utils.FindDuplicates(map(operator.attrgetter("name"), nodes))
263
264     (online, perhost) = fn(mygroupnodes, "my", nodes)
265     self.assertEqual(online, ["node%s" % i for i in range(20, 26)])
266     self.assertEqual(set(perhost.keys()), set(online))
267
268     self.assertEqual(perhost, {
269       "node20": ["node10", "node2", "node50"],
270       "node21": ["node11", "node3", "node50"],
271       "node22": ["node10", "node5", "node50"],
272       "node23": ["node11", "node2", "node50"],
273       "node24": ["node10", "node3", "node50"],
274       "node25": ["node11", "node5", "node50"],
275       })
276
277   def testSingleGroup(self):
278     fn = cluster.LUClusterVerifyGroup._SelectSshCheckNodes
279     nodes = [
280       objects.Node(name="node1", group="default", offline=True),
281       objects.Node(name="node2", group="default", offline=False),
282       objects.Node(name="node3", group="default", offline=False),
283       objects.Node(name="node4", group="default", offline=True),
284       ]
285     assert not utils.FindDuplicates(map(operator.attrgetter("name"), nodes))
286
287     (online, perhost) = fn(nodes, "default", nodes)
288     self.assertEqual(online, ["node2", "node3"])
289     self.assertEqual(set(perhost.keys()), set(online))
290
291     self.assertEqual(perhost, {
292       "node2": [],
293       "node3": [],
294       })
295
296
297 class TestClusterVerifyFiles(unittest.TestCase):
298   @staticmethod
299   def _FakeErrorIf(errors, cond, ecode, item, msg, *args, **kwargs):
300     assert ((ecode == constants.CV_ENODEFILECHECK and
301              ht.TNonEmptyString(item)) or
302             (ecode == constants.CV_ECLUSTERFILECHECK and
303              item is None))
304
305     if args:
306       msg = msg % args
307
308     if cond:
309       errors.append((item, msg))
310
311   def test(self):
312     errors = []
313     nodeinfo = [
314       objects.Node(name="master.example.com",
315                    uuid="master-uuid",
316                    offline=False,
317                    vm_capable=True),
318       objects.Node(name="node2.example.com",
319                    uuid="node2-uuid",
320                    offline=False,
321                    vm_capable=True),
322       objects.Node(name="node3.example.com",
323                    uuid="node3-uuid",
324                    master_candidate=True,
325                    vm_capable=False),
326       objects.Node(name="node4.example.com",
327                    uuid="node4-uuid",
328                    offline=False,
329                    vm_capable=True),
330       objects.Node(name="nodata.example.com",
331                    uuid="nodata-uuid",
332                    offline=False,
333                    vm_capable=True),
334       objects.Node(name="offline.example.com",
335                    uuid="offline-uuid",
336                    offline=True),
337       ]
338     files_all = set([
339       pathutils.CLUSTER_DOMAIN_SECRET_FILE,
340       pathutils.RAPI_CERT_FILE,
341       pathutils.RAPI_USERS_FILE,
342       ])
343     files_opt = set([
344       pathutils.RAPI_USERS_FILE,
345       hv_xen.XL_CONFIG_FILE,
346       pathutils.VNC_PASSWORD_FILE,
347       ])
348     files_mc = set([
349       pathutils.CLUSTER_CONF_FILE,
350       ])
351     files_vm = set([
352       hv_xen.XEND_CONFIG_FILE,
353       hv_xen.XL_CONFIG_FILE,
354       pathutils.VNC_PASSWORD_FILE,
355       ])
356     nvinfo = {
357       "master-uuid": rpc.RpcResult(data=(True, {
358         constants.NV_FILELIST: {
359           pathutils.CLUSTER_CONF_FILE: "82314f897f38b35f9dab2f7c6b1593e0",
360           pathutils.RAPI_CERT_FILE: "babbce8f387bc082228e544a2146fee4",
361           pathutils.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
362           hv_xen.XEND_CONFIG_FILE: "b4a8a824ab3cac3d88839a9adeadf310",
363           hv_xen.XL_CONFIG_FILE: "77935cee92afd26d162f9e525e3d49b9"
364         }})),
365       "node2-uuid": rpc.RpcResult(data=(True, {
366         constants.NV_FILELIST: {
367           pathutils.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
368           hv_xen.XEND_CONFIG_FILE: "b4a8a824ab3cac3d88839a9adeadf310",
369           }
370         })),
371       "node3-uuid": rpc.RpcResult(data=(True, {
372         constants.NV_FILELIST: {
373           pathutils.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
374           pathutils.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
375           }
376         })),
377       "node4-uuid": rpc.RpcResult(data=(True, {
378         constants.NV_FILELIST: {
379           pathutils.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
380           pathutils.CLUSTER_CONF_FILE: "conf-a6d4b13e407867f7a7b4f0f232a8f527",
381           pathutils.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
382           pathutils.RAPI_USERS_FILE: "rapiusers-ea3271e8d810ef3",
383           hv_xen.XL_CONFIG_FILE: "77935cee92afd26d162f9e525e3d49b9"
384           }
385         })),
386       "nodata-uuid": rpc.RpcResult(data=(True, {})),
387       "offline-uuid": rpc.RpcResult(offline=True),
388       }
389     assert set(nvinfo.keys()) == set(map(operator.attrgetter("uuid"), nodeinfo))
390
391     verify_lu = cluster.LUClusterVerifyGroup(mocks.FakeProc(),
392                                              opcodes.OpClusterVerify(),
393                                              mocks.FakeContext(),
394                                              None)
395
396     verify_lu._ErrorIf = compat.partial(self._FakeErrorIf, errors)
397
398     # TODO: That's a bit hackish to mock only this single method. We should
399     # build a better FakeConfig which provides such a feature already.
400     def GetNodeName(node_uuid):
401       for node in nodeinfo:
402         if node.uuid == node_uuid:
403           return node.name
404       return None
405
406     verify_lu.cfg.GetNodeName = GetNodeName
407
408     verify_lu._VerifyFiles(nodeinfo, "master-uuid", nvinfo,
409                            (files_all, files_opt, files_mc, files_vm))
410     self.assertEqual(sorted(errors), sorted([
411       (None, ("File %s found with 2 different checksums (variant 1 on"
412               " node2.example.com, node3.example.com, node4.example.com;"
413               " variant 2 on master.example.com)" % pathutils.RAPI_CERT_FILE)),
414       (None, ("File %s is missing from node(s) node2.example.com" %
415               pathutils.CLUSTER_DOMAIN_SECRET_FILE)),
416       (None, ("File %s should not exist on node(s) node4.example.com" %
417               pathutils.CLUSTER_CONF_FILE)),
418       (None, ("File %s is missing from node(s) node4.example.com" %
419               hv_xen.XEND_CONFIG_FILE)),
420       (None, ("File %s is missing from node(s) node3.example.com" %
421               pathutils.CLUSTER_CONF_FILE)),
422       (None, ("File %s found with 2 different checksums (variant 1 on"
423               " master.example.com; variant 2 on node4.example.com)" %
424               pathutils.CLUSTER_CONF_FILE)),
425       (None, ("File %s is optional, but it must exist on all or no nodes (not"
426               " found on master.example.com, node2.example.com,"
427               " node3.example.com)" % pathutils.RAPI_USERS_FILE)),
428       (None, ("File %s is optional, but it must exist on all or no nodes (not"
429               " found on node2.example.com)" % hv_xen.XL_CONFIG_FILE)),
430       ("nodata.example.com", "Node did not return file checksum data"),
431       ]))
432
433
434 class _FakeLU:
435   def __init__(self, cfg=NotImplemented, proc=NotImplemented,
436                rpc=NotImplemented):
437     self.warning_log = []
438     self.info_log = []
439     self.cfg = cfg
440     self.proc = proc
441     self.rpc = rpc
442
443   def LogWarning(self, text, *args):
444     self.warning_log.append((text, args))
445
446   def LogInfo(self, text, *args):
447     self.info_log.append((text, args))
448
449
450 class TestLoadNodeEvacResult(unittest.TestCase):
451   def testSuccess(self):
452     for moved in [[], [
453       ("inst20153.example.com", "grp2", ["nodeA4509", "nodeB2912"]),
454       ]]:
455       for early_release in [False, True]:
456         for use_nodes in [False, True]:
457           jobs = [
458             [opcodes.OpInstanceReplaceDisks().__getstate__()],
459             [opcodes.OpInstanceMigrate().__getstate__()],
460             ]
461
462           alloc_result = (moved, [], jobs)
463           assert iallocator._NEVAC_RESULT(alloc_result)
464
465           lu = _FakeLU()
466           result = common.LoadNodeEvacResult(lu, alloc_result,
467                                              early_release, use_nodes)
468
469           if moved:
470             (_, (info_args, )) = lu.info_log.pop(0)
471             for (instname, instgroup, instnodes) in moved:
472               self.assertTrue(instname in info_args)
473               if use_nodes:
474                 for i in instnodes:
475                   self.assertTrue(i in info_args)
476               else:
477                 self.assertTrue(instgroup in info_args)
478
479           self.assertFalse(lu.info_log)
480           self.assertFalse(lu.warning_log)
481
482           for op in itertools.chain(*result):
483             if hasattr(op.__class__, "early_release"):
484               self.assertEqual(op.early_release, early_release)
485             else:
486               self.assertFalse(hasattr(op, "early_release"))
487
488   def testFailed(self):
489     alloc_result = ([], [
490       ("inst5191.example.com", "errormsg21178"),
491       ], [])
492     assert iallocator._NEVAC_RESULT(alloc_result)
493
494     lu = _FakeLU()
495     self.assertRaises(errors.OpExecError, common.LoadNodeEvacResult,
496                       lu, alloc_result, False, False)
497     self.assertFalse(lu.info_log)
498     (_, (args, )) = lu.warning_log.pop(0)
499     self.assertTrue("inst5191.example.com" in args)
500     self.assertTrue("errormsg21178" in args)
501     self.assertFalse(lu.warning_log)
502
503
504 class TestUpdateAndVerifySubDict(unittest.TestCase):
505   def setUp(self):
506     self.type_check = {
507         "a": constants.VTYPE_INT,
508         "b": constants.VTYPE_STRING,
509         "c": constants.VTYPE_BOOL,
510         "d": constants.VTYPE_STRING,
511         }
512
513   def test(self):
514     old_test = {
515       "foo": {
516         "d": "blubb",
517         "a": 321,
518         },
519       "baz": {
520         "a": 678,
521         "b": "678",
522         "c": True,
523         },
524       }
525     test = {
526       "foo": {
527         "a": 123,
528         "b": "123",
529         "c": True,
530         },
531       "bar": {
532         "a": 321,
533         "b": "321",
534         "c": False,
535         },
536       }
537
538     mv = {
539       "foo": {
540         "a": 123,
541         "b": "123",
542         "c": True,
543         "d": "blubb"
544         },
545       "bar": {
546         "a": 321,
547         "b": "321",
548         "c": False,
549         },
550       "baz": {
551         "a": 678,
552         "b": "678",
553         "c": True,
554         },
555       }
556
557     verified = common._UpdateAndVerifySubDict(old_test, test, self.type_check)
558     self.assertEqual(verified, mv)
559
560   def testWrong(self):
561     test = {
562       "foo": {
563         "a": "blubb",
564         "b": "123",
565         "c": True,
566         },
567       "bar": {
568         "a": 321,
569         "b": "321",
570         "c": False,
571         },
572       }
573
574     self.assertRaises(errors.TypeEnforcementError,
575                       common._UpdateAndVerifySubDict, {}, test,
576                       self.type_check)
577
578
579 class TestHvStateHelper(unittest.TestCase):
580   def testWithoutOpData(self):
581     self.assertEqual(common.MergeAndVerifyHvState(None, NotImplemented),
582                      None)
583
584   def testWithoutOldData(self):
585     new = {
586       constants.HT_XEN_PVM: {
587         constants.HVST_MEMORY_TOTAL: 4096,
588         },
589       }
590     self.assertEqual(common.MergeAndVerifyHvState(new, None), new)
591
592   def testWithWrongHv(self):
593     new = {
594       "i-dont-exist": {
595         constants.HVST_MEMORY_TOTAL: 4096,
596         },
597       }
598     self.assertRaises(errors.OpPrereqError, common.MergeAndVerifyHvState,
599                       new, None)
600
601 class TestDiskStateHelper(unittest.TestCase):
602   def testWithoutOpData(self):
603     self.assertEqual(common.MergeAndVerifyDiskState(None, NotImplemented),
604                      None)
605
606   def testWithoutOldData(self):
607     new = {
608       constants.DT_PLAIN: {
609         "xenvg": {
610           constants.DS_DISK_RESERVED: 1024,
611           },
612         },
613       }
614     self.assertEqual(common.MergeAndVerifyDiskState(new, None), new)
615
616   def testWithWrongStorageType(self):
617     new = {
618       "i-dont-exist": {
619         "xenvg": {
620           constants.DS_DISK_RESERVED: 1024,
621           },
622         },
623       }
624     self.assertRaises(errors.OpPrereqError, common.MergeAndVerifyDiskState,
625                       new, None)
626
627
628 class TestComputeMinMaxSpec(unittest.TestCase):
629   def setUp(self):
630     self.ispecs = {
631       constants.ISPECS_MAX: {
632         constants.ISPEC_MEM_SIZE: 512,
633         constants.ISPEC_DISK_SIZE: 1024,
634         },
635       constants.ISPECS_MIN: {
636         constants.ISPEC_MEM_SIZE: 128,
637         constants.ISPEC_DISK_COUNT: 1,
638         },
639       }
640
641   def testNoneValue(self):
642     self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_MEM_SIZE, None,
643                                               self.ispecs, None) is None)
644
645   def testAutoValue(self):
646     self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_MEM_SIZE, None,
647                                               self.ispecs,
648                                               constants.VALUE_AUTO) is None)
649
650   def testNotDefined(self):
651     self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_NIC_COUNT, None,
652                                               self.ispecs, 3) is None)
653
654   def testNoMinDefined(self):
655     self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_DISK_SIZE, None,
656                                               self.ispecs, 128) is None)
657
658   def testNoMaxDefined(self):
659     self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_DISK_COUNT,
660                                               None, self.ispecs, 16) is None)
661
662   def testOutOfRange(self):
663     for (name, val) in ((constants.ISPEC_MEM_SIZE, 64),
664                         (constants.ISPEC_MEM_SIZE, 768),
665                         (constants.ISPEC_DISK_SIZE, 4096),
666                         (constants.ISPEC_DISK_COUNT, 0)):
667       min_v = self.ispecs[constants.ISPECS_MIN].get(name, val)
668       max_v = self.ispecs[constants.ISPECS_MAX].get(name, val)
669       self.assertEqual(common._ComputeMinMaxSpec(name, None,
670                                                  self.ispecs, val),
671                        "%s value %s is not in range [%s, %s]" %
672                        (name, val,min_v, max_v))
673       self.assertEqual(common._ComputeMinMaxSpec(name, "1",
674                                                  self.ispecs, val),
675                        "%s/1 value %s is not in range [%s, %s]" %
676                        (name, val,min_v, max_v))
677
678   def test(self):
679     for (name, val) in ((constants.ISPEC_MEM_SIZE, 256),
680                         (constants.ISPEC_MEM_SIZE, 128),
681                         (constants.ISPEC_MEM_SIZE, 512),
682                         (constants.ISPEC_DISK_SIZE, 1024),
683                         (constants.ISPEC_DISK_SIZE, 0),
684                         (constants.ISPEC_DISK_COUNT, 1),
685                         (constants.ISPEC_DISK_COUNT, 5)):
686       self.assertTrue(common._ComputeMinMaxSpec(name, None, self.ispecs, val)
687                       is None)
688
689
690 def _ValidateComputeMinMaxSpec(name, *_):
691   assert name in constants.ISPECS_PARAMETERS
692   return None
693
694
695 def _NoDiskComputeMinMaxSpec(name, *_):
696   if name == constants.ISPEC_DISK_COUNT:
697     return name
698   else:
699     return None
700
701
702 class _SpecWrapper:
703   def __init__(self, spec):
704     self.spec = spec
705
706   def ComputeMinMaxSpec(self, *args):
707     return self.spec.pop(0)
708
709
710 class TestComputeIPolicySpecViolation(unittest.TestCase):
711   # Minimal policy accepted by _ComputeIPolicySpecViolation()
712   _MICRO_IPOL = {
713     constants.IPOLICY_DTS: [constants.DT_PLAIN, constants.DT_DISKLESS],
714     constants.ISPECS_MINMAX: [NotImplemented],
715     }
716
717   def test(self):
718     compute_fn = _ValidateComputeMinMaxSpec
719     ret = common.ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1,
720                                              [1024], 1, constants.DT_PLAIN,
721                                              _compute_fn=compute_fn)
722     self.assertEqual(ret, [])
723
724   def testDiskFull(self):
725     compute_fn = _NoDiskComputeMinMaxSpec
726     ret = common.ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1,
727                                              [1024], 1, constants.DT_PLAIN,
728                                              _compute_fn=compute_fn)
729     self.assertEqual(ret, [constants.ISPEC_DISK_COUNT])
730
731   def testDiskLess(self):
732     compute_fn = _NoDiskComputeMinMaxSpec
733     ret = common.ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1,
734                                              [1024], 1, constants.DT_DISKLESS,
735                                              _compute_fn=compute_fn)
736     self.assertEqual(ret, [])
737
738   def testWrongTemplates(self):
739     compute_fn = _ValidateComputeMinMaxSpec
740     ret = common.ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1,
741                                              [1024], 1, constants.DT_DRBD8,
742                                              _compute_fn=compute_fn)
743     self.assertEqual(len(ret), 1)
744     self.assertTrue("Disk template" in ret[0])
745
746   def testInvalidArguments(self):
747     self.assertRaises(AssertionError, common.ComputeIPolicySpecViolation,
748                       self._MICRO_IPOL, 1024, 1, 1, 1, [], 1,
749                       constants.DT_PLAIN,)
750
751   def testInvalidSpec(self):
752     spec = _SpecWrapper([None, False, "foo", None, "bar", None])
753     compute_fn = spec.ComputeMinMaxSpec
754     ret = common.ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1,
755                                              [1024], 1, constants.DT_PLAIN,
756                                              _compute_fn=compute_fn)
757     self.assertEqual(ret, ["foo", "bar"])
758     self.assertFalse(spec.spec)
759
760   def testWithIPolicy(self):
761     mem_size = 2048
762     cpu_count = 2
763     disk_count = 1
764     disk_sizes = [512]
765     nic_count = 1
766     spindle_use = 4
767     disk_template = "mytemplate"
768     ispec = {
769       constants.ISPEC_MEM_SIZE: mem_size,
770       constants.ISPEC_CPU_COUNT: cpu_count,
771       constants.ISPEC_DISK_COUNT: disk_count,
772       constants.ISPEC_DISK_SIZE: disk_sizes[0],
773       constants.ISPEC_NIC_COUNT: nic_count,
774       constants.ISPEC_SPINDLE_USE: spindle_use,
775       }
776     ipolicy1 = {
777       constants.ISPECS_MINMAX: [{
778         constants.ISPECS_MIN: ispec,
779         constants.ISPECS_MAX: ispec,
780         }],
781       constants.IPOLICY_DTS: [disk_template],
782       }
783     ispec_copy = copy.deepcopy(ispec)
784     ipolicy2 = {
785       constants.ISPECS_MINMAX: [
786         {
787           constants.ISPECS_MIN: ispec_copy,
788           constants.ISPECS_MAX: ispec_copy,
789           },
790         {
791           constants.ISPECS_MIN: ispec,
792           constants.ISPECS_MAX: ispec,
793           },
794         ],
795       constants.IPOLICY_DTS: [disk_template],
796       }
797     ipolicy3 = {
798       constants.ISPECS_MINMAX: [
799         {
800           constants.ISPECS_MIN: ispec,
801           constants.ISPECS_MAX: ispec,
802           },
803         {
804           constants.ISPECS_MIN: ispec_copy,
805           constants.ISPECS_MAX: ispec_copy,
806           },
807         ],
808       constants.IPOLICY_DTS: [disk_template],
809       }
810     def AssertComputeViolation(ipolicy, violations):
811       ret = common.ComputeIPolicySpecViolation(ipolicy, mem_size, cpu_count,
812                                                disk_count, nic_count,
813                                                disk_sizes, spindle_use,
814                                                disk_template)
815       self.assertEqual(len(ret), violations)
816
817     AssertComputeViolation(ipolicy1, 0)
818     AssertComputeViolation(ipolicy2, 0)
819     AssertComputeViolation(ipolicy3, 0)
820     for par in constants.ISPECS_PARAMETERS:
821       ispec[par] += 1
822       AssertComputeViolation(ipolicy1, 1)
823       AssertComputeViolation(ipolicy2, 0)
824       AssertComputeViolation(ipolicy3, 0)
825       ispec[par] -= 2
826       AssertComputeViolation(ipolicy1, 1)
827       AssertComputeViolation(ipolicy2, 0)
828       AssertComputeViolation(ipolicy3, 0)
829       ispec[par] += 1 # Restore
830     ipolicy1[constants.IPOLICY_DTS] = ["another_template"]
831     AssertComputeViolation(ipolicy1, 1)
832
833
834 class _StubComputeIPolicySpecViolation:
835   def __init__(self, mem_size, cpu_count, disk_count, nic_count, disk_sizes,
836                spindle_use, disk_template):
837     self.mem_size = mem_size
838     self.cpu_count = cpu_count
839     self.disk_count = disk_count
840     self.nic_count = nic_count
841     self.disk_sizes = disk_sizes
842     self.spindle_use = spindle_use
843     self.disk_template = disk_template
844
845   def __call__(self, _, mem_size, cpu_count, disk_count, nic_count, disk_sizes,
846                spindle_use, disk_template):
847     assert self.mem_size == mem_size
848     assert self.cpu_count == cpu_count
849     assert self.disk_count == disk_count
850     assert self.nic_count == nic_count
851     assert self.disk_sizes == disk_sizes
852     assert self.spindle_use == spindle_use
853     assert self.disk_template == disk_template
854
855     return []
856
857
858 class _FakeConfigForComputeIPolicyInstanceViolation:
859   def __init__(self, be, excl_stor):
860     self.cluster = objects.Cluster(beparams={"default": be})
861     self.excl_stor = excl_stor
862
863   def GetClusterInfo(self):
864     return self.cluster
865
866   def GetNodeInfo(self, _):
867     return {}
868
869   def GetNdParams(self, _):
870     return {
871       constants.ND_EXCLUSIVE_STORAGE: self.excl_stor,
872       }
873
874
875 class TestComputeIPolicyInstanceViolation(unittest.TestCase):
876   def test(self):
877     beparams = {
878       constants.BE_MAXMEM: 2048,
879       constants.BE_VCPUS: 2,
880       constants.BE_SPINDLE_USE: 4,
881       }
882     disks = [objects.Disk(size=512, spindles=13)]
883     cfg = _FakeConfigForComputeIPolicyInstanceViolation(beparams, False)
884     instance = objects.Instance(beparams=beparams, disks=disks, nics=[],
885                                 disk_template=constants.DT_PLAIN)
886     stub = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512], 4,
887                                             constants.DT_PLAIN)
888     ret = common.ComputeIPolicyInstanceViolation(NotImplemented, instance,
889                                                  cfg, _compute_fn=stub)
890     self.assertEqual(ret, [])
891     instance2 = objects.Instance(beparams={}, disks=disks, nics=[],
892                                  disk_template=constants.DT_PLAIN)
893     ret = common.ComputeIPolicyInstanceViolation(NotImplemented, instance2,
894                                                  cfg, _compute_fn=stub)
895     self.assertEqual(ret, [])
896     cfg_es = _FakeConfigForComputeIPolicyInstanceViolation(beparams, True)
897     stub_es = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512], 13,
898                                                constants.DT_PLAIN)
899     ret = common.ComputeIPolicyInstanceViolation(NotImplemented, instance,
900                                                  cfg_es, _compute_fn=stub_es)
901     self.assertEqual(ret, [])
902     ret = common.ComputeIPolicyInstanceViolation(NotImplemented, instance2,
903                                                  cfg_es, _compute_fn=stub_es)
904     self.assertEqual(ret, [])
905
906
907 class TestComputeIPolicyInstanceSpecViolation(unittest.TestCase):
908   def test(self):
909     ispec = {
910       constants.ISPEC_MEM_SIZE: 2048,
911       constants.ISPEC_CPU_COUNT: 2,
912       constants.ISPEC_DISK_COUNT: 1,
913       constants.ISPEC_DISK_SIZE: [512],
914       constants.ISPEC_NIC_COUNT: 0,
915       constants.ISPEC_SPINDLE_USE: 1,
916       }
917     stub = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512], 1,
918                                             constants.DT_PLAIN)
919     ret = instance._ComputeIPolicyInstanceSpecViolation(NotImplemented, ispec,
920                                                         constants.DT_PLAIN,
921                                                         _compute_fn=stub)
922     self.assertEqual(ret, [])
923
924
925 class _CallRecorder:
926   def __init__(self, return_value=None):
927     self.called = False
928     self.return_value = return_value
929
930   def __call__(self, *args):
931     self.called = True
932     return self.return_value
933
934
935 class TestComputeIPolicyNodeViolation(unittest.TestCase):
936   def setUp(self):
937     self.recorder = _CallRecorder(return_value=[])
938
939   def testSameGroup(self):
940     ret = instance_utils._ComputeIPolicyNodeViolation(
941       NotImplemented,
942       NotImplemented,
943       "foo", "foo", NotImplemented,
944       _compute_fn=self.recorder)
945     self.assertFalse(self.recorder.called)
946     self.assertEqual(ret, [])
947
948   def testDifferentGroup(self):
949     ret = instance_utils._ComputeIPolicyNodeViolation(
950       NotImplemented,
951       NotImplemented,
952       "foo", "bar", NotImplemented,
953       _compute_fn=self.recorder)
954     self.assertTrue(self.recorder.called)
955     self.assertEqual(ret, [])
956
957
958 class _FakeConfigForTargetNodeIPolicy:
959   def __init__(self, node_info=NotImplemented):
960     self._node_info = node_info
961
962   def GetNodeInfo(self, _):
963     return self._node_info
964
965
966 class TestCheckTargetNodeIPolicy(unittest.TestCase):
967   def setUp(self):
968     self.instance = objects.Instance(primary_node="blubb")
969     self.target_node = objects.Node(group="bar")
970     node_info = objects.Node(group="foo")
971     fake_cfg = _FakeConfigForTargetNodeIPolicy(node_info=node_info)
972     self.lu = _FakeLU(cfg=fake_cfg)
973
974   def testNoViolation(self):
975     compute_recoder = _CallRecorder(return_value=[])
976     instance.CheckTargetNodeIPolicy(self.lu, NotImplemented, self.instance,
977                                     self.target_node, NotImplemented,
978                                     _compute_fn=compute_recoder)
979     self.assertTrue(compute_recoder.called)
980     self.assertEqual(self.lu.warning_log, [])
981
982   def testNoIgnore(self):
983     compute_recoder = _CallRecorder(return_value=["mem_size not in range"])
984     self.assertRaises(errors.OpPrereqError, instance.CheckTargetNodeIPolicy,
985                       self.lu, NotImplemented, self.instance,
986                       self.target_node, NotImplemented,
987                       _compute_fn=compute_recoder)
988     self.assertTrue(compute_recoder.called)
989     self.assertEqual(self.lu.warning_log, [])
990
991   def testIgnoreViolation(self):
992     compute_recoder = _CallRecorder(return_value=["mem_size not in range"])
993     instance.CheckTargetNodeIPolicy(self.lu, NotImplemented, self.instance,
994                                      self.target_node, NotImplemented,
995                                      ignore=True, _compute_fn=compute_recoder)
996     self.assertTrue(compute_recoder.called)
997     msg = ("Instance does not meet target node group's (bar) instance policy:"
998            " mem_size not in range")
999     self.assertEqual(self.lu.warning_log, [(msg, ())])
1000
1001
1002 class TestApplyContainerMods(unittest.TestCase):
1003   def testEmptyContainer(self):
1004     container = []
1005     chgdesc = []
1006     instance._ApplyContainerMods("test", container, chgdesc, [], None, None,
1007                                 None)
1008     self.assertEqual(container, [])
1009     self.assertEqual(chgdesc, [])
1010
1011   def testAdd(self):
1012     container = []
1013     chgdesc = []
1014     mods = instance._PrepareContainerMods([
1015       (constants.DDM_ADD, -1, "Hello"),
1016       (constants.DDM_ADD, -1, "World"),
1017       (constants.DDM_ADD, 0, "Start"),
1018       (constants.DDM_ADD, -1, "End"),
1019       ], None)
1020     instance._ApplyContainerMods("test", container, chgdesc, mods,
1021                                 None, None, None)
1022     self.assertEqual(container, ["Start", "Hello", "World", "End"])
1023     self.assertEqual(chgdesc, [])
1024
1025     mods = instance._PrepareContainerMods([
1026       (constants.DDM_ADD, 0, "zero"),
1027       (constants.DDM_ADD, 3, "Added"),
1028       (constants.DDM_ADD, 5, "four"),
1029       (constants.DDM_ADD, 7, "xyz"),
1030       ], None)
1031     instance._ApplyContainerMods("test", container, chgdesc, mods,
1032                                 None, None, None)
1033     self.assertEqual(container,
1034                      ["zero", "Start", "Hello", "Added", "World", "four",
1035                       "End", "xyz"])
1036     self.assertEqual(chgdesc, [])
1037
1038     for idx in [-2, len(container) + 1]:
1039       mods = instance._PrepareContainerMods([
1040         (constants.DDM_ADD, idx, "error"),
1041         ], None)
1042       self.assertRaises(IndexError, instance._ApplyContainerMods,
1043                         "test", container, None, mods, None, None, None)
1044
1045   def testRemoveError(self):
1046     for idx in [0, 1, 2, 100, -1, -4]:
1047       mods = instance._PrepareContainerMods([
1048         (constants.DDM_REMOVE, idx, None),
1049         ], None)
1050       self.assertRaises(IndexError, instance._ApplyContainerMods,
1051                         "test", [], None, mods, None, None, None)
1052
1053     mods = instance._PrepareContainerMods([
1054       (constants.DDM_REMOVE, 0, object()),
1055       ], None)
1056     self.assertRaises(AssertionError, instance._ApplyContainerMods,
1057                       "test", [""], None, mods, None, None, None)
1058
1059   def testAddError(self):
1060     for idx in range(-100, -1) + [100]:
1061       mods = instance._PrepareContainerMods([
1062         (constants.DDM_ADD, idx, None),
1063         ], None)
1064       self.assertRaises(IndexError, instance._ApplyContainerMods,
1065                         "test", [], None, mods, None, None, None)
1066
1067   def testRemove(self):
1068     container = ["item 1", "item 2"]
1069     mods = instance._PrepareContainerMods([
1070       (constants.DDM_ADD, -1, "aaa"),
1071       (constants.DDM_REMOVE, -1, None),
1072       (constants.DDM_ADD, -1, "bbb"),
1073       ], None)
1074     chgdesc = []
1075     instance._ApplyContainerMods("test", container, chgdesc, mods,
1076                                 None, None, None)
1077     self.assertEqual(container, ["item 1", "item 2", "bbb"])
1078     self.assertEqual(chgdesc, [
1079       ("test/2", "remove"),
1080       ])
1081
1082   def testModify(self):
1083     container = ["item 1", "item 2"]
1084     mods = instance._PrepareContainerMods([
1085       (constants.DDM_MODIFY, -1, "a"),
1086       (constants.DDM_MODIFY, 0, "b"),
1087       (constants.DDM_MODIFY, 1, "c"),
1088       ], None)
1089     chgdesc = []
1090     instance._ApplyContainerMods("test", container, chgdesc, mods,
1091                                 None, None, None)
1092     self.assertEqual(container, ["item 1", "item 2"])
1093     self.assertEqual(chgdesc, [])
1094
1095     for idx in [-2, len(container) + 1]:
1096       mods = instance._PrepareContainerMods([
1097         (constants.DDM_MODIFY, idx, "error"),
1098         ], None)
1099       self.assertRaises(IndexError, instance._ApplyContainerMods,
1100                         "test", container, None, mods, None, None, None)
1101
1102   class _PrivateData:
1103     def __init__(self):
1104       self.data = None
1105
1106   @staticmethod
1107   def _CreateTestFn(idx, params, private):
1108     private.data = ("add", idx, params)
1109     return ((100 * idx, params), [
1110       ("test/%s" % idx, hex(idx)),
1111       ])
1112
1113   @staticmethod
1114   def _ModifyTestFn(idx, item, params, private):
1115     private.data = ("modify", idx, params)
1116     return [
1117       ("test/%s" % idx, "modify %s" % params),
1118       ]
1119
1120   @staticmethod
1121   def _RemoveTestFn(idx, item, private):
1122     private.data = ("remove", idx, item)
1123
1124   def testAddWithCreateFunction(self):
1125     container = []
1126     chgdesc = []
1127     mods = instance._PrepareContainerMods([
1128       (constants.DDM_ADD, -1, "Hello"),
1129       (constants.DDM_ADD, -1, "World"),
1130       (constants.DDM_ADD, 0, "Start"),
1131       (constants.DDM_ADD, -1, "End"),
1132       (constants.DDM_REMOVE, 2, None),
1133       (constants.DDM_MODIFY, -1, "foobar"),
1134       (constants.DDM_REMOVE, 2, None),
1135       (constants.DDM_ADD, 1, "More"),
1136       ], self._PrivateData)
1137     instance._ApplyContainerMods("test", container, chgdesc, mods,
1138                                 self._CreateTestFn, self._ModifyTestFn,
1139                                 self._RemoveTestFn)
1140     self.assertEqual(container, [
1141       (000, "Start"),
1142       (100, "More"),
1143       (000, "Hello"),
1144       ])
1145     self.assertEqual(chgdesc, [
1146       ("test/0", "0x0"),
1147       ("test/1", "0x1"),
1148       ("test/0", "0x0"),
1149       ("test/3", "0x3"),
1150       ("test/2", "remove"),
1151       ("test/2", "modify foobar"),
1152       ("test/2", "remove"),
1153       ("test/1", "0x1")
1154       ])
1155     self.assertTrue(compat.all(op == private.data[0]
1156                                for (op, _, _, private) in mods))
1157     self.assertEqual([private.data for (op, _, _, private) in mods], [
1158       ("add", 0, "Hello"),
1159       ("add", 1, "World"),
1160       ("add", 0, "Start"),
1161       ("add", 3, "End"),
1162       ("remove", 2, (100, "World")),
1163       ("modify", 2, "foobar"),
1164       ("remove", 2, (300, "End")),
1165       ("add", 1, "More"),
1166       ])
1167
1168
1169 class _FakeConfigForGenDiskTemplate:
1170   def __init__(self, enabled_disk_templates):
1171     self._unique_id = itertools.count()
1172     self._drbd_minor = itertools.count(20)
1173     self._port = itertools.count(constants.FIRST_DRBD_PORT)
1174     self._secret = itertools.count()
1175     self._enabled_disk_templates = enabled_disk_templates
1176
1177   def GetVGName(self):
1178     return "testvg"
1179
1180   def GenerateUniqueID(self, ec_id):
1181     return "ec%s-uq%s" % (ec_id, self._unique_id.next())
1182
1183   def AllocateDRBDMinor(self, nodes, instance):
1184     return [self._drbd_minor.next()
1185             for _ in nodes]
1186
1187   def AllocatePort(self):
1188     return self._port.next()
1189
1190   def GenerateDRBDSecret(self, ec_id):
1191     return "ec%s-secret%s" % (ec_id, self._secret.next())
1192
1193   def GetInstanceInfo(self, _):
1194     return "foobar"
1195
1196   def GetClusterInfo(self):
1197     cluster = objects.Cluster()
1198     cluster.enabled_disk_templates = self._enabled_disk_templates
1199     return cluster
1200
1201
1202 class _FakeProcForGenDiskTemplate:
1203   def GetECId(self):
1204     return 0
1205
1206
1207 class TestGenerateDiskTemplate(unittest.TestCase):
1208
1209   def _SetUpLUWithTemplates(self, enabled_disk_templates):
1210     self._enabled_disk_templates = enabled_disk_templates
1211     cfg = _FakeConfigForGenDiskTemplate(self._enabled_disk_templates)
1212     proc = _FakeProcForGenDiskTemplate()
1213
1214     self.lu = _FakeLU(cfg=cfg, proc=proc)
1215
1216   def setUp(self):
1217     nodegroup = objects.NodeGroup(name="ng")
1218     nodegroup.UpgradeConfig()
1219
1220     self._enabled_disk_templates = list(constants.DISK_TEMPLATES)
1221     self._SetUpLUWithTemplates(self._enabled_disk_templates)
1222     self.nodegroup = nodegroup
1223
1224   @staticmethod
1225   def GetDiskParams():
1226     return copy.deepcopy(constants.DISK_DT_DEFAULTS)
1227
1228   def testWrongDiskTemplate(self):
1229     gdt = instance.GenerateDiskTemplate
1230     disk_template = "##unknown##"
1231
1232     assert disk_template not in constants.DISK_TEMPLATES
1233
1234     self.assertRaises(errors.OpPrereqError, gdt, self.lu, disk_template,
1235                       "inst26831.example.com", "node30113.example.com", [], [],
1236                       NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1237                       self.GetDiskParams())
1238
1239   def testDiskless(self):
1240     gdt = instance.GenerateDiskTemplate
1241
1242     result = gdt(self.lu, constants.DT_DISKLESS, "inst27734.example.com",
1243                  "node30113.example.com", [], [],
1244                  NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1245                  self.GetDiskParams())
1246     self.assertEqual(result, [])
1247
1248   def _TestTrivialDisk(self, template, disk_info, base_index, exp_dev_type,
1249                        file_storage_dir=NotImplemented,
1250                        file_driver=NotImplemented):
1251     gdt = instance.GenerateDiskTemplate
1252
1253     map(lambda params: utils.ForceDictType(params,
1254                                            constants.IDISK_PARAMS_TYPES),
1255         disk_info)
1256
1257     # Check if non-empty list of secondaries is rejected
1258     self.assertRaises(errors.ProgrammerError, gdt, self.lu,
1259                       template, "inst25088.example.com",
1260                       "node185.example.com", ["node323.example.com"], [],
1261                       NotImplemented, NotImplemented, base_index,
1262                       self.lu.LogInfo, self.GetDiskParams())
1263
1264     result = gdt(self.lu, template, "inst21662.example.com",
1265                  "node21741.example.com", [],
1266                  disk_info, file_storage_dir, file_driver, base_index,
1267                  self.lu.LogInfo, self.GetDiskParams())
1268
1269     for (idx, disk) in enumerate(result):
1270       self.assertTrue(isinstance(disk, objects.Disk))
1271       self.assertEqual(disk.dev_type, exp_dev_type)
1272       self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
1273       self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
1274       self.assertTrue(disk.children is None)
1275
1276     self._CheckIvNames(result, base_index, base_index + len(disk_info))
1277     instance._UpdateIvNames(base_index, result)
1278     self._CheckIvNames(result, base_index, base_index + len(disk_info))
1279
1280     return result
1281
1282   def _CheckIvNames(self, disks, base_index, end_index):
1283     self.assertEqual(map(operator.attrgetter("iv_name"), disks),
1284                      ["disk/%s" % i for i in range(base_index, end_index)])
1285
1286   def testPlain(self):
1287     disk_info = [{
1288       constants.IDISK_SIZE: 1024,
1289       constants.IDISK_MODE: constants.DISK_RDWR,
1290       }, {
1291       constants.IDISK_SIZE: 4096,
1292       constants.IDISK_VG: "othervg",
1293       constants.IDISK_MODE: constants.DISK_RDWR,
1294       }]
1295
1296     result = self._TestTrivialDisk(constants.DT_PLAIN, disk_info, 3,
1297                                    constants.DT_PLAIN)
1298
1299     self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1300       ("testvg", "ec0-uq0.disk3"),
1301       ("othervg", "ec0-uq1.disk4"),
1302       ])
1303
1304   def testFile(self):
1305     # anything != DT_FILE would do here
1306     self._SetUpLUWithTemplates([constants.DT_PLAIN])
1307     self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
1308                       constants.DT_FILE, [], 0, NotImplemented)
1309     self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
1310                       constants.DT_SHARED_FILE, [], 0, NotImplemented)
1311
1312     for disk_template in [constants.DT_FILE, constants.DT_SHARED_FILE]:
1313       disk_info = [{
1314         constants.IDISK_SIZE: 80 * 1024,
1315         constants.IDISK_MODE: constants.DISK_RDONLY,
1316         }, {
1317         constants.IDISK_SIZE: 4096,
1318         constants.IDISK_MODE: constants.DISK_RDWR,
1319         }, {
1320         constants.IDISK_SIZE: 6 * 1024,
1321         constants.IDISK_MODE: constants.DISK_RDWR,
1322         }]
1323
1324       self._SetUpLUWithTemplates([disk_template])
1325       result = self._TestTrivialDisk(disk_template, disk_info, 2,
1326         disk_template, file_storage_dir="/tmp",
1327         file_driver=constants.FD_BLKTAP)
1328
1329       for (idx, disk) in enumerate(result):
1330         (file_driver, file_storage_dir) = disk.logical_id
1331         dir_fmt = r"^/tmp/.*\.%s\.disk%d$" % (disk_template, idx + 2)
1332         self.assertEqual(file_driver, constants.FD_BLKTAP)
1333         # FIXME: use assertIsNotNone when py 2.7 is minimum supported version
1334         self.assertNotEqual(re.match(dir_fmt, file_storage_dir), None)
1335
1336   def testBlock(self):
1337     disk_info = [{
1338       constants.IDISK_SIZE: 8 * 1024,
1339       constants.IDISK_MODE: constants.DISK_RDWR,
1340       constants.IDISK_ADOPT: "/tmp/some/block/dev",
1341       }]
1342
1343     result = self._TestTrivialDisk(constants.DT_BLOCK, disk_info, 10,
1344                                    constants.DT_BLOCK)
1345
1346     self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1347       (constants.BLOCKDEV_DRIVER_MANUAL, "/tmp/some/block/dev"),
1348       ])
1349
1350   def testRbd(self):
1351     disk_info = [{
1352       constants.IDISK_SIZE: 8 * 1024,
1353       constants.IDISK_MODE: constants.DISK_RDONLY,
1354       }, {
1355       constants.IDISK_SIZE: 100 * 1024,
1356       constants.IDISK_MODE: constants.DISK_RDWR,
1357       }]
1358
1359     result = self._TestTrivialDisk(constants.DT_RBD, disk_info, 0,
1360                                    constants.DT_RBD)
1361
1362     self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1363       ("rbd", "ec0-uq0.rbd.disk0"),
1364       ("rbd", "ec0-uq1.rbd.disk1"),
1365       ])
1366
1367   def testDrbd8(self):
1368     gdt = instance.GenerateDiskTemplate
1369     drbd8_defaults = constants.DISK_LD_DEFAULTS[constants.DT_DRBD8]
1370     drbd8_default_metavg = drbd8_defaults[constants.LDP_DEFAULT_METAVG]
1371
1372     disk_info = [{
1373       constants.IDISK_SIZE: 1024,
1374       constants.IDISK_MODE: constants.DISK_RDWR,
1375       }, {
1376       constants.IDISK_SIZE: 100 * 1024,
1377       constants.IDISK_MODE: constants.DISK_RDONLY,
1378       constants.IDISK_METAVG: "metavg",
1379       }, {
1380       constants.IDISK_SIZE: 4096,
1381       constants.IDISK_MODE: constants.DISK_RDWR,
1382       constants.IDISK_VG: "vgxyz",
1383       },
1384       ]
1385
1386     exp_logical_ids = [[
1387       (self.lu.cfg.GetVGName(), "ec0-uq0.disk0_data"),
1388       (drbd8_default_metavg, "ec0-uq0.disk0_meta"),
1389       ], [
1390       (self.lu.cfg.GetVGName(), "ec0-uq1.disk1_data"),
1391       ("metavg", "ec0-uq1.disk1_meta"),
1392       ], [
1393       ("vgxyz", "ec0-uq2.disk2_data"),
1394       (drbd8_default_metavg, "ec0-uq2.disk2_meta"),
1395       ]]
1396
1397     assert len(exp_logical_ids) == len(disk_info)
1398
1399     map(lambda params: utils.ForceDictType(params,
1400                                            constants.IDISK_PARAMS_TYPES),
1401         disk_info)
1402
1403     # Check if empty list of secondaries is rejected
1404     self.assertRaises(errors.ProgrammerError, gdt, self.lu, constants.DT_DRBD8,
1405                       "inst827.example.com", "node1334.example.com", [],
1406                       disk_info, NotImplemented, NotImplemented, 0,
1407                       self.lu.LogInfo, self.GetDiskParams())
1408
1409     result = gdt(self.lu, constants.DT_DRBD8, "inst827.example.com",
1410                  "node1334.example.com", ["node12272.example.com"],
1411                  disk_info, NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1412                  self.GetDiskParams())
1413
1414     for (idx, disk) in enumerate(result):
1415       self.assertTrue(isinstance(disk, objects.Disk))
1416       self.assertEqual(disk.dev_type, constants.DT_DRBD8)
1417       self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
1418       self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
1419
1420       for child in disk.children:
1421         self.assertTrue(isinstance(disk, objects.Disk))
1422         self.assertEqual(child.dev_type, constants.DT_PLAIN)
1423         self.assertTrue(child.children is None)
1424
1425       self.assertEqual(map(operator.attrgetter("logical_id"), disk.children),
1426                        exp_logical_ids[idx])
1427
1428       self.assertEqual(len(disk.children), 2)
1429       self.assertEqual(disk.children[0].size, disk.size)
1430       self.assertEqual(disk.children[1].size, constants.DRBD_META_SIZE)
1431
1432     self._CheckIvNames(result, 0, len(disk_info))
1433     instance._UpdateIvNames(0, result)
1434     self._CheckIvNames(result, 0, len(disk_info))
1435
1436     self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1437       ("node1334.example.com", "node12272.example.com",
1438        constants.FIRST_DRBD_PORT, 20, 21, "ec0-secret0"),
1439       ("node1334.example.com", "node12272.example.com",
1440        constants.FIRST_DRBD_PORT + 1, 22, 23, "ec0-secret1"),
1441       ("node1334.example.com", "node12272.example.com",
1442        constants.FIRST_DRBD_PORT + 2, 24, 25, "ec0-secret2"),
1443       ])
1444
1445
1446 class _ConfigForDiskWipe:
1447   def __init__(self, exp_node_uuid):
1448     self._exp_node_uuid = exp_node_uuid
1449
1450   def SetDiskID(self, device, node_uuid):
1451     assert isinstance(device, objects.Disk)
1452     assert node_uuid == self._exp_node_uuid
1453
1454   def GetNodeName(self, node_uuid):
1455     assert node_uuid == self._exp_node_uuid
1456     return "name.of.expected.node"
1457
1458
1459 class _RpcForDiskWipe:
1460   def __init__(self, exp_node, pause_cb, wipe_cb):
1461     self._exp_node = exp_node
1462     self._pause_cb = pause_cb
1463     self._wipe_cb = wipe_cb
1464
1465   def call_blockdev_pause_resume_sync(self, node, disks, pause):
1466     assert node == self._exp_node
1467     return rpc.RpcResult(data=self._pause_cb(disks, pause))
1468
1469   def call_blockdev_wipe(self, node, bdev, offset, size):
1470     assert node == self._exp_node
1471     return rpc.RpcResult(data=self._wipe_cb(bdev, offset, size))
1472
1473
1474 class _DiskPauseTracker:
1475   def __init__(self):
1476     self.history = []
1477
1478   def __call__(self, (disks, instance), pause):
1479     assert not (set(disks) - set(instance.disks))
1480
1481     self.history.extend((i.logical_id, i.size, pause)
1482                         for i in disks)
1483
1484     return (True, [True] * len(disks))
1485
1486
1487 class _DiskWipeProgressTracker:
1488   def __init__(self, start_offset):
1489     self._start_offset = start_offset
1490     self.progress = {}
1491
1492   def __call__(self, (disk, _), offset, size):
1493     assert isinstance(offset, (long, int))
1494     assert isinstance(size, (long, int))
1495
1496     max_chunk_size = (disk.size / 100.0 * constants.MIN_WIPE_CHUNK_PERCENT)
1497
1498     assert offset >= self._start_offset
1499     assert (offset + size) <= disk.size
1500
1501     assert size > 0
1502     assert size <= constants.MAX_WIPE_CHUNK
1503     assert size <= max_chunk_size
1504
1505     assert offset == self._start_offset or disk.logical_id in self.progress
1506
1507     # Keep track of progress
1508     cur_progress = self.progress.setdefault(disk.logical_id, self._start_offset)
1509
1510     assert cur_progress == offset
1511
1512     # Record progress
1513     self.progress[disk.logical_id] += size
1514
1515     return (True, None)
1516
1517
1518 class TestWipeDisks(unittest.TestCase):
1519   def _FailingPauseCb(self, (disks, _), pause):
1520     self.assertEqual(len(disks), 3)
1521     self.assertTrue(pause)
1522     # Simulate an RPC error
1523     return (False, "error")
1524
1525   def testPauseFailure(self):
1526     node_name = "node1372.example.com"
1527
1528     lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, self._FailingPauseCb,
1529                                      NotImplemented),
1530                  cfg=_ConfigForDiskWipe(node_name))
1531
1532     disks = [
1533       objects.Disk(dev_type=constants.DT_PLAIN),
1534       objects.Disk(dev_type=constants.DT_PLAIN),
1535       objects.Disk(dev_type=constants.DT_PLAIN),
1536       ]
1537
1538     inst = objects.Instance(name="inst21201",
1539                             primary_node=node_name,
1540                             disk_template=constants.DT_PLAIN,
1541                             disks=disks)
1542
1543     self.assertRaises(errors.OpExecError, instance.WipeDisks, lu, inst)
1544
1545   def _FailingWipeCb(self, (disk, _), offset, size):
1546     # This should only ever be called for the first disk
1547     self.assertEqual(disk.logical_id, "disk0")
1548     return (False, None)
1549
1550   def testFailingWipe(self):
1551     node_uuid = "node13445-uuid"
1552     pt = _DiskPauseTracker()
1553
1554     lu = _FakeLU(rpc=_RpcForDiskWipe(node_uuid, pt, self._FailingWipeCb),
1555                  cfg=_ConfigForDiskWipe(node_uuid))
1556
1557     disks = [
1558       objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk0",
1559                    size=100 * 1024),
1560       objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk1",
1561                    size=500 * 1024),
1562       objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk2", size=256),
1563       ]
1564
1565     inst = objects.Instance(name="inst562",
1566                             primary_node=node_uuid,
1567                             disk_template=constants.DT_PLAIN,
1568                             disks=disks)
1569
1570     try:
1571       instance.WipeDisks(lu, inst)
1572     except errors.OpExecError, err:
1573       self.assertTrue(str(err), "Could not wipe disk 0 at offset 0 ")
1574     else:
1575       self.fail("Did not raise exception")
1576
1577     # Check if all disks were paused and resumed
1578     self.assertEqual(pt.history, [
1579       ("disk0", 100 * 1024, True),
1580       ("disk1", 500 * 1024, True),
1581       ("disk2", 256, True),
1582       ("disk0", 100 * 1024, False),
1583       ("disk1", 500 * 1024, False),
1584       ("disk2", 256, False),
1585       ])
1586
1587   def _PrepareWipeTest(self, start_offset, disks):
1588     node_name = "node-with-offset%s.example.com" % start_offset
1589     pauset = _DiskPauseTracker()
1590     progresst = _DiskWipeProgressTracker(start_offset)
1591
1592     lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, pauset, progresst),
1593                  cfg=_ConfigForDiskWipe(node_name))
1594
1595     instance = objects.Instance(name="inst3560",
1596                                 primary_node=node_name,
1597                                 disk_template=constants.DT_PLAIN,
1598                                 disks=disks)
1599
1600     return (lu, instance, pauset, progresst)
1601
1602   def testNormalWipe(self):
1603     disks = [
1604       objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk0", size=1024),
1605       objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk1",
1606                    size=500 * 1024),
1607       objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk2", size=128),
1608       objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk3",
1609                    size=constants.MAX_WIPE_CHUNK),
1610       ]
1611
1612     (lu, inst, pauset, progresst) = self._PrepareWipeTest(0, disks)
1613
1614     instance.WipeDisks(lu, inst)
1615
1616     self.assertEqual(pauset.history, [
1617       ("disk0", 1024, True),
1618       ("disk1", 500 * 1024, True),
1619       ("disk2", 128, True),
1620       ("disk3", constants.MAX_WIPE_CHUNK, True),
1621       ("disk0", 1024, False),
1622       ("disk1", 500 * 1024, False),
1623       ("disk2", 128, False),
1624       ("disk3", constants.MAX_WIPE_CHUNK, False),
1625       ])
1626
1627     # Ensure the complete disk has been wiped
1628     self.assertEqual(progresst.progress,
1629                      dict((i.logical_id, i.size) for i in disks))
1630
1631   def testWipeWithStartOffset(self):
1632     for start_offset in [0, 280, 8895, 1563204]:
1633       disks = [
1634         objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk0",
1635                      size=128),
1636         objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk1",
1637                      size=start_offset + (100 * 1024)),
1638         ]
1639
1640       (lu, inst, pauset, progresst) = \
1641         self._PrepareWipeTest(start_offset, disks)
1642
1643       # Test start offset with only one disk
1644       instance.WipeDisks(lu, inst,
1645                          disks=[(1, disks[1], start_offset)])
1646
1647       # Only the second disk may have been paused and wiped
1648       self.assertEqual(pauset.history, [
1649         ("disk1", start_offset + (100 * 1024), True),
1650         ("disk1", start_offset + (100 * 1024), False),
1651         ])
1652       self.assertEqual(progresst.progress, {
1653         "disk1": disks[1].size,
1654         })
1655
1656
1657 class TestDiskSizeInBytesToMebibytes(unittest.TestCase):
1658   def testLessThanOneMebibyte(self):
1659     for i in [1, 2, 7, 512, 1000, 1023]:
1660       lu = _FakeLU()
1661       result = instance_storage._DiskSizeInBytesToMebibytes(lu, i)
1662       self.assertEqual(result, 1)
1663       self.assertEqual(len(lu.warning_log), 1)
1664       self.assertEqual(len(lu.warning_log[0]), 2)
1665       (_, (warnsize, )) = lu.warning_log[0]
1666       self.assertEqual(warnsize, (1024 * 1024) - i)
1667
1668   def testEven(self):
1669     for i in [1, 2, 7, 512, 1000, 1023]:
1670       lu = _FakeLU()
1671       result = instance_storage._DiskSizeInBytesToMebibytes(lu,
1672                                                             i * 1024 * 1024)
1673       self.assertEqual(result, i)
1674       self.assertFalse(lu.warning_log)
1675
1676   def testLargeNumber(self):
1677     for i in [1, 2, 7, 512, 1000, 1023, 2724, 12420]:
1678       for j in [1, 2, 486, 326, 986, 1023]:
1679         lu = _FakeLU()
1680         size = (1024 * 1024 * i) + j
1681         result = instance_storage._DiskSizeInBytesToMebibytes(lu, size)
1682         self.assertEqual(result, i + 1, msg="Amount was not rounded up")
1683         self.assertEqual(len(lu.warning_log), 1)
1684         self.assertEqual(len(lu.warning_log[0]), 2)
1685         (_, (warnsize, )) = lu.warning_log[0]
1686         self.assertEqual(warnsize, (1024 * 1024) - j)
1687
1688
1689 class TestCopyLockList(unittest.TestCase):
1690   def test(self):
1691     self.assertEqual(instance.CopyLockList([]), [])
1692     self.assertEqual(instance.CopyLockList(None), None)
1693     self.assertEqual(instance.CopyLockList(locking.ALL_SET), locking.ALL_SET)
1694
1695     names = ["foo", "bar"]
1696     output = instance.CopyLockList(names)
1697     self.assertEqual(names, output)
1698     self.assertNotEqual(id(names), id(output), msg="List was not copied")
1699
1700
1701 class TestCheckOpportunisticLocking(unittest.TestCase):
1702   class OpTest(opcodes.OpCode):
1703     OP_PARAMS = [
1704       opcodes._POpportunisticLocking,
1705       opcodes._PIAllocFromDesc(""),
1706       ]
1707
1708   @classmethod
1709   def _MakeOp(cls, **kwargs):
1710     op = cls.OpTest(**kwargs)
1711     op.Validate(True)
1712     return op
1713
1714   def testMissingAttributes(self):
1715     self.assertRaises(AttributeError, instance._CheckOpportunisticLocking,
1716                       object())
1717
1718   def testDefaults(self):
1719     op = self._MakeOp()
1720     instance._CheckOpportunisticLocking(op)
1721
1722   def test(self):
1723     for iallocator in [None, "something", "other"]:
1724       for opplock in [False, True]:
1725         op = self._MakeOp(iallocator=iallocator,
1726                           opportunistic_locking=opplock)
1727         if opplock and not iallocator:
1728           self.assertRaises(errors.OpPrereqError,
1729                             instance._CheckOpportunisticLocking, op)
1730         else:
1731           instance._CheckOpportunisticLocking(op)
1732
1733
1734 class _OpTestVerifyErrors(opcodes.OpCode):
1735   OP_PARAMS = [
1736     opcodes._PDebugSimulateErrors,
1737     opcodes._PErrorCodes,
1738     opcodes._PIgnoreErrors,
1739     ]
1740
1741
1742 class _LuTestVerifyErrors(cluster._VerifyErrors):
1743   def __init__(self, **kwargs):
1744     cluster._VerifyErrors.__init__(self)
1745     self.op = _OpTestVerifyErrors(**kwargs)
1746     self.op.Validate(True)
1747     self.msglist = []
1748     self._feedback_fn = self.msglist.append
1749     self.bad = False
1750
1751   def DispatchCallError(self, which, *args, **kwargs):
1752     if which:
1753       self._Error(*args, **kwargs)
1754     else:
1755       self._ErrorIf(True, *args, **kwargs)
1756
1757   def CallErrorIf(self, c, *args, **kwargs):
1758     self._ErrorIf(c, *args, **kwargs)
1759
1760
1761 class TestVerifyErrors(unittest.TestCase):
1762   # Fake cluster-verify error code structures; we use two arbitary real error
1763   # codes to pass validation of ignore_errors
1764   (_, _ERR1ID, _) = constants.CV_ECLUSTERCFG
1765   _NODESTR = "node"
1766   _NODENAME = "mynode"
1767   _ERR1CODE = (_NODESTR, _ERR1ID, "Error one")
1768   (_, _ERR2ID, _) = constants.CV_ECLUSTERCERT
1769   _INSTSTR = "instance"
1770   _INSTNAME = "myinstance"
1771   _ERR2CODE = (_INSTSTR, _ERR2ID, "Error two")
1772   # Arguments used to call _Error() or _ErrorIf()
1773   _ERR1ARGS = (_ERR1CODE, _NODENAME, "Error1 is %s", "an error")
1774   _ERR2ARGS = (_ERR2CODE, _INSTNAME, "Error2 has no argument")
1775   # Expected error messages
1776   _ERR1MSG = _ERR1ARGS[2] % _ERR1ARGS[3]
1777   _ERR2MSG = _ERR2ARGS[2]
1778
1779   def testNoError(self):
1780     lu = _LuTestVerifyErrors()
1781     lu.CallErrorIf(False, self._ERR1CODE, *self._ERR1ARGS)
1782     self.assertFalse(lu.bad)
1783     self.assertFalse(lu.msglist)
1784
1785   def _InitTest(self, **kwargs):
1786     self.lu1 = _LuTestVerifyErrors(**kwargs)
1787     self.lu2 = _LuTestVerifyErrors(**kwargs)
1788
1789   def _CallError(self, *args, **kwargs):
1790     # Check that _Error() and _ErrorIf() produce the same results
1791     self.lu1.DispatchCallError(True, *args, **kwargs)
1792     self.lu2.DispatchCallError(False, *args, **kwargs)
1793     self.assertEqual(self.lu1.bad, self.lu2.bad)
1794     self.assertEqual(self.lu1.msglist, self.lu2.msglist)
1795     # Test-specific checks are made on one LU
1796     return self.lu1
1797
1798   def _checkMsgCommon(self, logstr, errmsg, itype, item, warning):
1799     self.assertTrue(errmsg in logstr)
1800     if warning:
1801       self.assertTrue("WARNING" in logstr)
1802     else:
1803       self.assertTrue("ERROR" in logstr)
1804     self.assertTrue(itype in logstr)
1805     self.assertTrue(item in logstr)
1806
1807   def _checkMsg1(self, logstr, warning=False):
1808     self._checkMsgCommon(logstr, self._ERR1MSG, self._NODESTR,
1809                          self._NODENAME, warning)
1810
1811   def _checkMsg2(self, logstr, warning=False):
1812     self._checkMsgCommon(logstr, self._ERR2MSG, self._INSTSTR,
1813                          self._INSTNAME, warning)
1814
1815   def testPlain(self):
1816     self._InitTest()
1817     lu = self._CallError(*self._ERR1ARGS)
1818     self.assertTrue(lu.bad)
1819     self.assertEqual(len(lu.msglist), 1)
1820     self._checkMsg1(lu.msglist[0])
1821
1822   def testMultiple(self):
1823     self._InitTest()
1824     self._CallError(*self._ERR1ARGS)
1825     lu = self._CallError(*self._ERR2ARGS)
1826     self.assertTrue(lu.bad)
1827     self.assertEqual(len(lu.msglist), 2)
1828     self._checkMsg1(lu.msglist[0])
1829     self._checkMsg2(lu.msglist[1])
1830
1831   def testIgnore(self):
1832     self._InitTest(ignore_errors=[self._ERR1ID])
1833     lu = self._CallError(*self._ERR1ARGS)
1834     self.assertFalse(lu.bad)
1835     self.assertEqual(len(lu.msglist), 1)
1836     self._checkMsg1(lu.msglist[0], warning=True)
1837
1838   def testWarning(self):
1839     self._InitTest()
1840     lu = self._CallError(*self._ERR1ARGS,
1841                          code=_LuTestVerifyErrors.ETYPE_WARNING)
1842     self.assertFalse(lu.bad)
1843     self.assertEqual(len(lu.msglist), 1)
1844     self._checkMsg1(lu.msglist[0], warning=True)
1845
1846   def testWarning2(self):
1847     self._InitTest()
1848     self._CallError(*self._ERR1ARGS)
1849     lu = self._CallError(*self._ERR2ARGS,
1850                          code=_LuTestVerifyErrors.ETYPE_WARNING)
1851     self.assertTrue(lu.bad)
1852     self.assertEqual(len(lu.msglist), 2)
1853     self._checkMsg1(lu.msglist[0])
1854     self._checkMsg2(lu.msglist[1], warning=True)
1855
1856   def testDebugSimulate(self):
1857     lu = _LuTestVerifyErrors(debug_simulate_errors=True)
1858     lu.CallErrorIf(False, *self._ERR1ARGS)
1859     self.assertTrue(lu.bad)
1860     self.assertEqual(len(lu.msglist), 1)
1861     self._checkMsg1(lu.msglist[0])
1862
1863   def testErrCodes(self):
1864     self._InitTest(error_codes=True)
1865     lu = self._CallError(*self._ERR1ARGS)
1866     self.assertTrue(lu.bad)
1867     self.assertEqual(len(lu.msglist), 1)
1868     self._checkMsg1(lu.msglist[0])
1869     self.assertTrue(self._ERR1ID in lu.msglist[0])
1870
1871
1872 class TestGetUpdatedIPolicy(unittest.TestCase):
1873   """Tests for cmdlib._GetUpdatedIPolicy()"""
1874   _OLD_CLUSTER_POLICY = {
1875     constants.IPOLICY_VCPU_RATIO: 1.5,
1876     constants.ISPECS_MINMAX: [
1877       {
1878         constants.ISPECS_MIN: {
1879           constants.ISPEC_MEM_SIZE: 32768,
1880           constants.ISPEC_CPU_COUNT: 8,
1881           constants.ISPEC_DISK_COUNT: 1,
1882           constants.ISPEC_DISK_SIZE: 1024,
1883           constants.ISPEC_NIC_COUNT: 1,
1884           constants.ISPEC_SPINDLE_USE: 1,
1885           },
1886         constants.ISPECS_MAX: {
1887           constants.ISPEC_MEM_SIZE: 65536,
1888           constants.ISPEC_CPU_COUNT: 10,
1889           constants.ISPEC_DISK_COUNT: 5,
1890           constants.ISPEC_DISK_SIZE: 1024 * 1024,
1891           constants.ISPEC_NIC_COUNT: 3,
1892           constants.ISPEC_SPINDLE_USE: 12,
1893           },
1894         },
1895       constants.ISPECS_MINMAX_DEFAULTS,
1896       ],
1897     constants.ISPECS_STD: constants.IPOLICY_DEFAULTS[constants.ISPECS_STD],
1898     }
1899   _OLD_GROUP_POLICY = {
1900     constants.IPOLICY_SPINDLE_RATIO: 2.5,
1901     constants.ISPECS_MINMAX: [{
1902       constants.ISPECS_MIN: {
1903         constants.ISPEC_MEM_SIZE: 128,
1904         constants.ISPEC_CPU_COUNT: 1,
1905         constants.ISPEC_DISK_COUNT: 1,
1906         constants.ISPEC_DISK_SIZE: 1024,
1907         constants.ISPEC_NIC_COUNT: 1,
1908         constants.ISPEC_SPINDLE_USE: 1,
1909         },
1910       constants.ISPECS_MAX: {
1911         constants.ISPEC_MEM_SIZE: 32768,
1912         constants.ISPEC_CPU_COUNT: 8,
1913         constants.ISPEC_DISK_COUNT: 5,
1914         constants.ISPEC_DISK_SIZE: 1024 * 1024,
1915         constants.ISPEC_NIC_COUNT: 3,
1916         constants.ISPEC_SPINDLE_USE: 12,
1917         },
1918       }],
1919     }
1920
1921   def _TestSetSpecs(self, old_policy, isgroup):
1922     diff_minmax = [{
1923       constants.ISPECS_MIN: {
1924         constants.ISPEC_MEM_SIZE: 64,
1925         constants.ISPEC_CPU_COUNT: 1,
1926         constants.ISPEC_DISK_COUNT: 2,
1927         constants.ISPEC_DISK_SIZE: 64,
1928         constants.ISPEC_NIC_COUNT: 1,
1929         constants.ISPEC_SPINDLE_USE: 1,
1930         },
1931       constants.ISPECS_MAX: {
1932         constants.ISPEC_MEM_SIZE: 16384,
1933         constants.ISPEC_CPU_COUNT: 10,
1934         constants.ISPEC_DISK_COUNT: 12,
1935         constants.ISPEC_DISK_SIZE: 1024,
1936         constants.ISPEC_NIC_COUNT: 9,
1937         constants.ISPEC_SPINDLE_USE: 18,
1938         },
1939       }]
1940     diff_std = {
1941         constants.ISPEC_DISK_COUNT: 10,
1942         constants.ISPEC_DISK_SIZE: 512,
1943         }
1944     diff_policy = {
1945       constants.ISPECS_MINMAX: diff_minmax
1946       }
1947     if not isgroup:
1948       diff_policy[constants.ISPECS_STD] = diff_std
1949     new_policy = common.GetUpdatedIPolicy(old_policy, diff_policy,
1950                                           group_policy=isgroup)
1951
1952     self.assertTrue(constants.ISPECS_MINMAX in new_policy)
1953     self.assertEqual(new_policy[constants.ISPECS_MINMAX], diff_minmax)
1954     for key in old_policy:
1955       if not key in diff_policy:
1956         self.assertTrue(key in new_policy)
1957         self.assertEqual(new_policy[key], old_policy[key])
1958
1959     if not isgroup:
1960       new_std = new_policy[constants.ISPECS_STD]
1961       for key in diff_std:
1962         self.assertTrue(key in new_std)
1963         self.assertEqual(new_std[key], diff_std[key])
1964       old_std = old_policy.get(constants.ISPECS_STD, {})
1965       for key in old_std:
1966         self.assertTrue(key in new_std)
1967         if key not in diff_std:
1968           self.assertEqual(new_std[key], old_std[key])
1969
1970   def _TestSet(self, old_policy, diff_policy, isgroup):
1971     new_policy = common.GetUpdatedIPolicy(old_policy, diff_policy,
1972                                            group_policy=isgroup)
1973     for key in diff_policy:
1974       self.assertTrue(key in new_policy)
1975       self.assertEqual(new_policy[key], diff_policy[key])
1976     for key in old_policy:
1977       if not key in diff_policy:
1978         self.assertTrue(key in new_policy)
1979         self.assertEqual(new_policy[key], old_policy[key])
1980
1981   def testSet(self):
1982     diff_policy = {
1983       constants.IPOLICY_VCPU_RATIO: 3,
1984       constants.IPOLICY_DTS: [constants.DT_FILE],
1985       }
1986     self._TestSet(self._OLD_GROUP_POLICY, diff_policy, True)
1987     self._TestSetSpecs(self._OLD_GROUP_POLICY, True)
1988     self._TestSet({}, diff_policy, True)
1989     self._TestSetSpecs({}, True)
1990     self._TestSet(self._OLD_CLUSTER_POLICY, diff_policy, False)
1991     self._TestSetSpecs(self._OLD_CLUSTER_POLICY, False)
1992
1993   def testUnset(self):
1994     old_policy = self._OLD_GROUP_POLICY
1995     diff_policy = {
1996       constants.IPOLICY_SPINDLE_RATIO: constants.VALUE_DEFAULT,
1997       }
1998     new_policy = common.GetUpdatedIPolicy(old_policy, diff_policy,
1999                                           group_policy=True)
2000     for key in diff_policy:
2001       self.assertFalse(key in new_policy)
2002     for key in old_policy:
2003       if not key in diff_policy:
2004         self.assertTrue(key in new_policy)
2005         self.assertEqual(new_policy[key], old_policy[key])
2006
2007     self.assertRaises(errors.OpPrereqError, common.GetUpdatedIPolicy,
2008                       old_policy, diff_policy, group_policy=False)
2009
2010   def testUnsetEmpty(self):
2011     old_policy = {}
2012     for key in constants.IPOLICY_ALL_KEYS:
2013       diff_policy = {
2014         key: constants.VALUE_DEFAULT,
2015         }
2016     new_policy = common.GetUpdatedIPolicy(old_policy, diff_policy,
2017                                           group_policy=True)
2018     self.assertEqual(new_policy, old_policy)
2019
2020   def _TestInvalidKeys(self, old_policy, isgroup):
2021     INVALID_KEY = "this_key_shouldnt_be_allowed"
2022     INVALID_DICT = {
2023       INVALID_KEY: 3,
2024       }
2025     invalid_policy = INVALID_DICT
2026     self.assertRaises(errors.OpPrereqError, common.GetUpdatedIPolicy,
2027                       old_policy, invalid_policy, group_policy=isgroup)
2028     invalid_ispecs = {
2029       constants.ISPECS_MINMAX: [INVALID_DICT],
2030       }
2031     self.assertRaises(errors.TypeEnforcementError, common.GetUpdatedIPolicy,
2032                       old_policy, invalid_ispecs, group_policy=isgroup)
2033     if isgroup:
2034       invalid_for_group = {
2035         constants.ISPECS_STD: constants.IPOLICY_DEFAULTS[constants.ISPECS_STD],
2036         }
2037       self.assertRaises(errors.OpPrereqError, common.GetUpdatedIPolicy,
2038                         old_policy, invalid_for_group, group_policy=isgroup)
2039     good_ispecs = self._OLD_CLUSTER_POLICY[constants.ISPECS_MINMAX]
2040     invalid_ispecs = copy.deepcopy(good_ispecs)
2041     invalid_policy = {
2042       constants.ISPECS_MINMAX: invalid_ispecs,
2043       }
2044     for minmax in invalid_ispecs:
2045       for key in constants.ISPECS_MINMAX_KEYS:
2046         ispec = minmax[key]
2047         ispec[INVALID_KEY] = None
2048         self.assertRaises(errors.TypeEnforcementError,
2049                           common.GetUpdatedIPolicy, old_policy,
2050                           invalid_policy, group_policy=isgroup)
2051         del ispec[INVALID_KEY]
2052         for par in constants.ISPECS_PARAMETERS:
2053           oldv = ispec[par]
2054           ispec[par] = "this_is_not_good"
2055           self.assertRaises(errors.TypeEnforcementError,
2056                             common.GetUpdatedIPolicy,
2057                             old_policy, invalid_policy, group_policy=isgroup)
2058           ispec[par] = oldv
2059     # This is to make sure that no two errors were present during the tests
2060     common.GetUpdatedIPolicy(old_policy, invalid_policy,
2061                              group_policy=isgroup)
2062
2063   def testInvalidKeys(self):
2064     self._TestInvalidKeys(self._OLD_GROUP_POLICY, True)
2065     self._TestInvalidKeys(self._OLD_CLUSTER_POLICY, False)
2066
2067   def testInvalidValues(self):
2068     for par in (constants.IPOLICY_PARAMETERS |
2069                 frozenset([constants.IPOLICY_DTS])):
2070       bad_policy = {
2071         par: "invalid_value",
2072         }
2073       self.assertRaises(errors.OpPrereqError, common.GetUpdatedIPolicy, {},
2074                         bad_policy, group_policy=True)
2075
2076 if __name__ == "__main__":
2077   testutils.GanetiTestProgram()