Statistics
| Branch: | Tag: | Revision:

root / test / py / ganeti.cmdlib_unittest.py @ 6839584c

History | View | Annotate | Download (69.3 kB)

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(name, pnode, snode):
197
      if snode is None:
198
        disks = []
199
        disk_template = constants.DT_DISKLESS
200
      else:
201
        disks = [objects.Disk(dev_type=constants.LD_DRBD8,
202
                              logical_id=[pnode, snode, 1, 17, 17])]
203
        disk_template = constants.DT_DRBD8
204

    
205
      return objects.Instance(name=name, primary_node=pnode, disks=disks,
206
                              disk_template=disk_template)
207

    
208
    instance_data = dict((name, Instance(name, pnode, snode))
209
                         for name, pnode, snode in [("inst1a", "n1a", "n1b"),
210
                                                    ("inst1b", "n1b", "n1a"),
211
                                                    ("inst2a", "n2a", "n2b"),
212
                                                    ("inst3a", "n3a", None),
213
                                                    ("inst3b", "n3b", "n1b"),
214
                                                    ("inst3c", "n3b", "n2b"),
215
                                                    ])
216

    
217
    # Test first with the existing state.
218
    (new, prev) = \
219
      group.LUGroupAssignNodes.CheckAssignmentForSplitInstances([],
220
                                                                node_data,
221
                                                                instance_data)
222

    
223
    self.assertEqual([], new)
224
    self.assertEqual(set(["inst3b", "inst3c"]), set(prev))
225

    
226
    # And now some changes.
227
    (new, prev) = \
228
      group.LUGroupAssignNodes.CheckAssignmentForSplitInstances([("n1b",
229
                                                                  "g3")],
230
                                                                node_data,
231
                                                                instance_data)
232

    
233
    self.assertEqual(set(["inst1a", "inst1b"]), set(new))
234
    self.assertEqual(set(["inst3c"]), set(prev))
235

    
236

    
237
class TestClusterVerifySsh(unittest.TestCase):
238
  def testMultipleGroups(self):
239
    fn = cluster.LUClusterVerifyGroup._SelectSshCheckNodes
240
    mygroupnodes = [
241
      objects.Node(name="node20", group="my", offline=False),
242
      objects.Node(name="node21", group="my", offline=False),
243
      objects.Node(name="node22", group="my", offline=False),
244
      objects.Node(name="node23", group="my", offline=False),
245
      objects.Node(name="node24", group="my", offline=False),
246
      objects.Node(name="node25", group="my", offline=False),
247
      objects.Node(name="node26", group="my", offline=True),
248
      ]
249
    nodes = [
250
      objects.Node(name="node1", group="g1", offline=True),
251
      objects.Node(name="node2", group="g1", offline=False),
252
      objects.Node(name="node3", group="g1", offline=False),
253
      objects.Node(name="node4", group="g1", offline=True),
254
      objects.Node(name="node5", group="g1", offline=False),
255
      objects.Node(name="node10", group="xyz", offline=False),
256
      objects.Node(name="node11", group="xyz", offline=False),
257
      objects.Node(name="node40", group="alloff", offline=True),
258
      objects.Node(name="node41", group="alloff", offline=True),
259
      objects.Node(name="node50", group="aaa", offline=False),
260
      ] + mygroupnodes
261
    assert not utils.FindDuplicates(map(operator.attrgetter("name"), nodes))
262

    
263
    (online, perhost) = fn(mygroupnodes, "my", nodes)
264
    self.assertEqual(online, ["node%s" % i for i in range(20, 26)])
265
    self.assertEqual(set(perhost.keys()), set(online))
266

    
267
    self.assertEqual(perhost, {
268
      "node20": ["node10", "node2", "node50"],
269
      "node21": ["node11", "node3", "node50"],
270
      "node22": ["node10", "node5", "node50"],
271
      "node23": ["node11", "node2", "node50"],
272
      "node24": ["node10", "node3", "node50"],
273
      "node25": ["node11", "node5", "node50"],
274
      })
275

    
276
  def testSingleGroup(self):
277
    fn = cluster.LUClusterVerifyGroup._SelectSshCheckNodes
278
    nodes = [
279
      objects.Node(name="node1", group="default", offline=True),
280
      objects.Node(name="node2", group="default", offline=False),
281
      objects.Node(name="node3", group="default", offline=False),
282
      objects.Node(name="node4", group="default", offline=True),
283
      ]
284
    assert not utils.FindDuplicates(map(operator.attrgetter("name"), nodes))
285

    
286
    (online, perhost) = fn(nodes, "default", nodes)
287
    self.assertEqual(online, ["node2", "node3"])
288
    self.assertEqual(set(perhost.keys()), set(online))
289

    
290
    self.assertEqual(perhost, {
291
      "node2": [],
292
      "node3": [],
293
      })
294

    
295

    
296
class TestClusterVerifyFiles(unittest.TestCase):
297
  @staticmethod
298
  def _FakeErrorIf(errors, cond, ecode, item, msg, *args, **kwargs):
299
    assert ((ecode == constants.CV_ENODEFILECHECK and
300
             ht.TNonEmptyString(item)) or
301
            (ecode == constants.CV_ECLUSTERFILECHECK and
302
             item is None))
303

    
304
    if args:
305
      msg = msg % args
306

    
307
    if cond:
308
      errors.append((item, msg))
309

    
310
  _VerifyFiles = cluster.LUClusterVerifyGroup._VerifyFiles
311

    
312
  def test(self):
313
    errors = []
314
    master_name = "master.example.com"
315
    nodeinfo = [
316
      objects.Node(name=master_name, offline=False, vm_capable=True),
317
      objects.Node(name="node2.example.com", offline=False, vm_capable=True),
318
      objects.Node(name="node3.example.com", master_candidate=True,
319
                   vm_capable=False),
320
      objects.Node(name="node4.example.com", offline=False, vm_capable=True),
321
      objects.Node(name="nodata.example.com", offline=False, vm_capable=True),
322
      objects.Node(name="offline.example.com", offline=True),
323
      ]
324
    cluster = objects.Cluster(modify_etc_hosts=True,
325
                              enabled_hypervisors=[constants.HT_XEN_HVM])
326
    files_all = set([
327
      pathutils.CLUSTER_DOMAIN_SECRET_FILE,
328
      pathutils.RAPI_CERT_FILE,
329
      pathutils.RAPI_USERS_FILE,
330
      ])
331
    files_opt = set([
332
      pathutils.RAPI_USERS_FILE,
333
      hv_xen.XL_CONFIG_FILE,
334
      pathutils.VNC_PASSWORD_FILE,
335
      ])
336
    files_mc = set([
337
      pathutils.CLUSTER_CONF_FILE,
338
      ])
339
    files_vm = set([
340
      hv_xen.XEND_CONFIG_FILE,
341
      hv_xen.XL_CONFIG_FILE,
342
      pathutils.VNC_PASSWORD_FILE,
343
      ])
344
    nvinfo = {
345
      master_name: rpc.RpcResult(data=(True, {
346
        constants.NV_FILELIST: {
347
          pathutils.CLUSTER_CONF_FILE: "82314f897f38b35f9dab2f7c6b1593e0",
348
          pathutils.RAPI_CERT_FILE: "babbce8f387bc082228e544a2146fee4",
349
          pathutils.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
350
          hv_xen.XEND_CONFIG_FILE: "b4a8a824ab3cac3d88839a9adeadf310",
351
          hv_xen.XL_CONFIG_FILE: "77935cee92afd26d162f9e525e3d49b9"
352
        }})),
353
      "node2.example.com": rpc.RpcResult(data=(True, {
354
        constants.NV_FILELIST: {
355
          pathutils.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
356
          hv_xen.XEND_CONFIG_FILE: "b4a8a824ab3cac3d88839a9adeadf310",
357
          }
358
        })),
359
      "node3.example.com": rpc.RpcResult(data=(True, {
360
        constants.NV_FILELIST: {
361
          pathutils.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
362
          pathutils.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
363
          }
364
        })),
365
      "node4.example.com": rpc.RpcResult(data=(True, {
366
        constants.NV_FILELIST: {
367
          pathutils.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
368
          pathutils.CLUSTER_CONF_FILE: "conf-a6d4b13e407867f7a7b4f0f232a8f527",
369
          pathutils.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
370
          pathutils.RAPI_USERS_FILE: "rapiusers-ea3271e8d810ef3",
371
          hv_xen.XL_CONFIG_FILE: "77935cee92afd26d162f9e525e3d49b9"
372
          }
373
        })),
374
      "nodata.example.com": rpc.RpcResult(data=(True, {})),
375
      "offline.example.com": rpc.RpcResult(offline=True),
376
      }
377
    assert set(nvinfo.keys()) == set(map(operator.attrgetter("name"), nodeinfo))
378

    
379
    self._VerifyFiles(compat.partial(self._FakeErrorIf, errors), nodeinfo,
380
                      master_name, nvinfo,
381
                      (files_all, files_opt, files_mc, files_vm))
382
    self.assertEqual(sorted(errors), sorted([
383
      (None, ("File %s found with 2 different checksums (variant 1 on"
384
              " node2.example.com, node3.example.com, node4.example.com;"
385
              " variant 2 on master.example.com)" % pathutils.RAPI_CERT_FILE)),
386
      (None, ("File %s is missing from node(s) node2.example.com" %
387
              pathutils.CLUSTER_DOMAIN_SECRET_FILE)),
388
      (None, ("File %s should not exist on node(s) node4.example.com" %
389
              pathutils.CLUSTER_CONF_FILE)),
390
      (None, ("File %s is missing from node(s) node4.example.com" %
391
              hv_xen.XEND_CONFIG_FILE)),
392
      (None, ("File %s is missing from node(s) node3.example.com" %
393
              pathutils.CLUSTER_CONF_FILE)),
394
      (None, ("File %s found with 2 different checksums (variant 1 on"
395
              " master.example.com; variant 2 on node4.example.com)" %
396
              pathutils.CLUSTER_CONF_FILE)),
397
      (None, ("File %s is optional, but it must exist on all or no nodes (not"
398
              " found on master.example.com, node2.example.com,"
399
              " node3.example.com)" % pathutils.RAPI_USERS_FILE)),
400
      (None, ("File %s is optional, but it must exist on all or no nodes (not"
401
              " found on node2.example.com)" % hv_xen.XL_CONFIG_FILE)),
402
      ("nodata.example.com", "Node did not return file checksum data"),
403
      ]))
404

    
405

    
406
class _FakeLU:
407
  def __init__(self, cfg=NotImplemented, proc=NotImplemented,
408
               rpc=NotImplemented):
409
    self.warning_log = []
410
    self.info_log = []
411
    self.cfg = cfg
412
    self.proc = proc
413
    self.rpc = rpc
414

    
415
  def LogWarning(self, text, *args):
416
    self.warning_log.append((text, args))
417

    
418
  def LogInfo(self, text, *args):
419
    self.info_log.append((text, args))
420

    
421

    
422
class TestLoadNodeEvacResult(unittest.TestCase):
423
  def testSuccess(self):
424
    for moved in [[], [
425
      ("inst20153.example.com", "grp2", ["nodeA4509", "nodeB2912"]),
426
      ]]:
427
      for early_release in [False, True]:
428
        for use_nodes in [False, True]:
429
          jobs = [
430
            [opcodes.OpInstanceReplaceDisks().__getstate__()],
431
            [opcodes.OpInstanceMigrate().__getstate__()],
432
            ]
433

    
434
          alloc_result = (moved, [], jobs)
435
          assert iallocator._NEVAC_RESULT(alloc_result)
436

    
437
          lu = _FakeLU()
438
          result = common.LoadNodeEvacResult(lu, alloc_result,
439
                                             early_release, use_nodes)
440

    
441
          if moved:
442
            (_, (info_args, )) = lu.info_log.pop(0)
443
            for (instname, instgroup, instnodes) in moved:
444
              self.assertTrue(instname in info_args)
445
              if use_nodes:
446
                for i in instnodes:
447
                  self.assertTrue(i in info_args)
448
              else:
449
                self.assertTrue(instgroup in info_args)
450

    
451
          self.assertFalse(lu.info_log)
452
          self.assertFalse(lu.warning_log)
453

    
454
          for op in itertools.chain(*result):
455
            if hasattr(op.__class__, "early_release"):
456
              self.assertEqual(op.early_release, early_release)
457
            else:
458
              self.assertFalse(hasattr(op, "early_release"))
459

    
460
  def testFailed(self):
461
    alloc_result = ([], [
462
      ("inst5191.example.com", "errormsg21178"),
463
      ], [])
464
    assert iallocator._NEVAC_RESULT(alloc_result)
465

    
466
    lu = _FakeLU()
467
    self.assertRaises(errors.OpExecError, common.LoadNodeEvacResult,
468
                      lu, alloc_result, False, False)
469
    self.assertFalse(lu.info_log)
470
    (_, (args, )) = lu.warning_log.pop(0)
471
    self.assertTrue("inst5191.example.com" in args)
472
    self.assertTrue("errormsg21178" in args)
473
    self.assertFalse(lu.warning_log)
474

    
475

    
476
class TestUpdateAndVerifySubDict(unittest.TestCase):
477
  def setUp(self):
478
    self.type_check = {
479
        "a": constants.VTYPE_INT,
480
        "b": constants.VTYPE_STRING,
481
        "c": constants.VTYPE_BOOL,
482
        "d": constants.VTYPE_STRING,
483
        }
484

    
485
  def test(self):
486
    old_test = {
487
      "foo": {
488
        "d": "blubb",
489
        "a": 321,
490
        },
491
      "baz": {
492
        "a": 678,
493
        "b": "678",
494
        "c": True,
495
        },
496
      }
497
    test = {
498
      "foo": {
499
        "a": 123,
500
        "b": "123",
501
        "c": True,
502
        },
503
      "bar": {
504
        "a": 321,
505
        "b": "321",
506
        "c": False,
507
        },
508
      }
509

    
510
    mv = {
511
      "foo": {
512
        "a": 123,
513
        "b": "123",
514
        "c": True,
515
        "d": "blubb"
516
        },
517
      "bar": {
518
        "a": 321,
519
        "b": "321",
520
        "c": False,
521
        },
522
      "baz": {
523
        "a": 678,
524
        "b": "678",
525
        "c": True,
526
        },
527
      }
528

    
529
    verified = common._UpdateAndVerifySubDict(old_test, test, self.type_check)
530
    self.assertEqual(verified, mv)
531

    
532
  def testWrong(self):
533
    test = {
534
      "foo": {
535
        "a": "blubb",
536
        "b": "123",
537
        "c": True,
538
        },
539
      "bar": {
540
        "a": 321,
541
        "b": "321",
542
        "c": False,
543
        },
544
      }
545

    
546
    self.assertRaises(errors.TypeEnforcementError,
547
                      common._UpdateAndVerifySubDict, {}, test,
548
                      self.type_check)
549

    
550

    
551
class TestHvStateHelper(unittest.TestCase):
552
  def testWithoutOpData(self):
553
    self.assertEqual(common.MergeAndVerifyHvState(None, NotImplemented),
554
                     None)
555

    
556
  def testWithoutOldData(self):
557
    new = {
558
      constants.HT_XEN_PVM: {
559
        constants.HVST_MEMORY_TOTAL: 4096,
560
        },
561
      }
562
    self.assertEqual(common.MergeAndVerifyHvState(new, None), new)
563

    
564
  def testWithWrongHv(self):
565
    new = {
566
      "i-dont-exist": {
567
        constants.HVST_MEMORY_TOTAL: 4096,
568
        },
569
      }
570
    self.assertRaises(errors.OpPrereqError, common.MergeAndVerifyHvState,
571
                      new, None)
572

    
573
class TestDiskStateHelper(unittest.TestCase):
574
  def testWithoutOpData(self):
575
    self.assertEqual(common.MergeAndVerifyDiskState(None, NotImplemented),
576
                     None)
577

    
578
  def testWithoutOldData(self):
579
    new = {
580
      constants.LD_LV: {
581
        "xenvg": {
582
          constants.DS_DISK_RESERVED: 1024,
583
          },
584
        },
585
      }
586
    self.assertEqual(common.MergeAndVerifyDiskState(new, None), new)
587

    
588
  def testWithWrongStorageType(self):
589
    new = {
590
      "i-dont-exist": {
591
        "xenvg": {
592
          constants.DS_DISK_RESERVED: 1024,
593
          },
594
        },
595
      }
596
    self.assertRaises(errors.OpPrereqError, common.MergeAndVerifyDiskState,
597
                      new, None)
598

    
599

    
600
class TestComputeMinMaxSpec(unittest.TestCase):
601
  def setUp(self):
602
    self.ispecs = {
603
      constants.ISPECS_MAX: {
604
        constants.ISPEC_MEM_SIZE: 512,
605
        constants.ISPEC_DISK_SIZE: 1024,
606
        },
607
      constants.ISPECS_MIN: {
608
        constants.ISPEC_MEM_SIZE: 128,
609
        constants.ISPEC_DISK_COUNT: 1,
610
        },
611
      }
612

    
613
  def testNoneValue(self):
614
    self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_MEM_SIZE, None,
615
                                              self.ispecs, None) is None)
616

    
617
  def testAutoValue(self):
618
    self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_MEM_SIZE, None,
619
                                              self.ispecs,
620
                                              constants.VALUE_AUTO) is None)
621

    
622
  def testNotDefined(self):
623
    self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_NIC_COUNT, None,
624
                                              self.ispecs, 3) is None)
625

    
626
  def testNoMinDefined(self):
627
    self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_DISK_SIZE, None,
628
                                              self.ispecs, 128) is None)
629

    
630
  def testNoMaxDefined(self):
631
    self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_DISK_COUNT,
632
                                              None, self.ispecs, 16) is None)
633

    
634
  def testOutOfRange(self):
635
    for (name, val) in ((constants.ISPEC_MEM_SIZE, 64),
636
                        (constants.ISPEC_MEM_SIZE, 768),
637
                        (constants.ISPEC_DISK_SIZE, 4096),
638
                        (constants.ISPEC_DISK_COUNT, 0)):
639
      min_v = self.ispecs[constants.ISPECS_MIN].get(name, val)
640
      max_v = self.ispecs[constants.ISPECS_MAX].get(name, val)
641
      self.assertEqual(common._ComputeMinMaxSpec(name, None,
642
                                                 self.ispecs, val),
643
                       "%s value %s is not in range [%s, %s]" %
644
                       (name, val,min_v, max_v))
645
      self.assertEqual(common._ComputeMinMaxSpec(name, "1",
646
                                                 self.ispecs, val),
647
                       "%s/1 value %s is not in range [%s, %s]" %
648
                       (name, val,min_v, max_v))
649

    
650
  def test(self):
651
    for (name, val) in ((constants.ISPEC_MEM_SIZE, 256),
652
                        (constants.ISPEC_MEM_SIZE, 128),
653
                        (constants.ISPEC_MEM_SIZE, 512),
654
                        (constants.ISPEC_DISK_SIZE, 1024),
655
                        (constants.ISPEC_DISK_SIZE, 0),
656
                        (constants.ISPEC_DISK_COUNT, 1),
657
                        (constants.ISPEC_DISK_COUNT, 5)):
658
      self.assertTrue(common._ComputeMinMaxSpec(name, None, self.ispecs, val)
659
                      is None)
660

    
661

    
662
def _ValidateComputeMinMaxSpec(name, *_):
663
  assert name in constants.ISPECS_PARAMETERS
664
  return None
665

    
666

    
667
def _NoDiskComputeMinMaxSpec(name, *_):
668
  if name == constants.ISPEC_DISK_COUNT:
669
    return name
670
  else:
671
    return None
672

    
673

    
674
class _SpecWrapper:
675
  def __init__(self, spec):
676
    self.spec = spec
677

    
678
  def ComputeMinMaxSpec(self, *args):
679
    return self.spec.pop(0)
680

    
681

    
682
class TestComputeIPolicySpecViolation(unittest.TestCase):
683
  # Minimal policy accepted by _ComputeIPolicySpecViolation()
684
  _MICRO_IPOL = {
685
    constants.IPOLICY_DTS: [constants.DT_PLAIN, constants.DT_DISKLESS],
686
    constants.ISPECS_MINMAX: [NotImplemented],
687
    }
688

    
689
  def test(self):
690
    compute_fn = _ValidateComputeMinMaxSpec
691
    ret = common.ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1,
692
                                             [1024], 1, constants.DT_PLAIN,
693
                                             _compute_fn=compute_fn)
694
    self.assertEqual(ret, [])
695

    
696
  def testDiskFull(self):
697
    compute_fn = _NoDiskComputeMinMaxSpec
698
    ret = common.ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1,
699
                                             [1024], 1, constants.DT_PLAIN,
700
                                             _compute_fn=compute_fn)
701
    self.assertEqual(ret, [constants.ISPEC_DISK_COUNT])
702

    
703
  def testDiskLess(self):
704
    compute_fn = _NoDiskComputeMinMaxSpec
705
    ret = common.ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1,
706
                                             [1024], 1, constants.DT_DISKLESS,
707
                                             _compute_fn=compute_fn)
708
    self.assertEqual(ret, [])
709

    
710
  def testWrongTemplates(self):
711
    compute_fn = _ValidateComputeMinMaxSpec
712
    ret = common.ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1,
713
                                             [1024], 1, constants.DT_DRBD8,
714
                                             _compute_fn=compute_fn)
715
    self.assertEqual(len(ret), 1)
716
    self.assertTrue("Disk template" in ret[0])
717

    
718
  def testInvalidArguments(self):
719
    self.assertRaises(AssertionError, common.ComputeIPolicySpecViolation,
720
                      self._MICRO_IPOL, 1024, 1, 1, 1, [], 1,
721
                      constants.DT_PLAIN,)
722

    
723
  def testInvalidSpec(self):
724
    spec = _SpecWrapper([None, False, "foo", None, "bar", None])
725
    compute_fn = spec.ComputeMinMaxSpec
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, ["foo", "bar"])
730
    self.assertFalse(spec.spec)
731

    
732
  def testWithIPolicy(self):
733
    mem_size = 2048
734
    cpu_count = 2
735
    disk_count = 1
736
    disk_sizes = [512]
737
    nic_count = 1
738
    spindle_use = 4
739
    disk_template = "mytemplate"
740
    ispec = {
741
      constants.ISPEC_MEM_SIZE: mem_size,
742
      constants.ISPEC_CPU_COUNT: cpu_count,
743
      constants.ISPEC_DISK_COUNT: disk_count,
744
      constants.ISPEC_DISK_SIZE: disk_sizes[0],
745
      constants.ISPEC_NIC_COUNT: nic_count,
746
      constants.ISPEC_SPINDLE_USE: spindle_use,
747
      }
748
    ipolicy1 = {
749
      constants.ISPECS_MINMAX: [{
750
        constants.ISPECS_MIN: ispec,
751
        constants.ISPECS_MAX: ispec,
752
        }],
753
      constants.IPOLICY_DTS: [disk_template],
754
      }
755
    ispec_copy = copy.deepcopy(ispec)
756
    ipolicy2 = {
757
      constants.ISPECS_MINMAX: [
758
        {
759
          constants.ISPECS_MIN: ispec_copy,
760
          constants.ISPECS_MAX: ispec_copy,
761
          },
762
        {
763
          constants.ISPECS_MIN: ispec,
764
          constants.ISPECS_MAX: ispec,
765
          },
766
        ],
767
      constants.IPOLICY_DTS: [disk_template],
768
      }
769
    ipolicy3 = {
770
      constants.ISPECS_MINMAX: [
771
        {
772
          constants.ISPECS_MIN: ispec,
773
          constants.ISPECS_MAX: ispec,
774
          },
775
        {
776
          constants.ISPECS_MIN: ispec_copy,
777
          constants.ISPECS_MAX: ispec_copy,
778
          },
779
        ],
780
      constants.IPOLICY_DTS: [disk_template],
781
      }
782
    def AssertComputeViolation(ipolicy, violations):
783
      ret = common.ComputeIPolicySpecViolation(ipolicy, mem_size, cpu_count,
784
                                               disk_count, nic_count,
785
                                               disk_sizes, spindle_use,
786
                                               disk_template)
787
      self.assertEqual(len(ret), violations)
788

    
789
    AssertComputeViolation(ipolicy1, 0)
790
    AssertComputeViolation(ipolicy2, 0)
791
    AssertComputeViolation(ipolicy3, 0)
792
    for par in constants.ISPECS_PARAMETERS:
793
      ispec[par] += 1
794
      AssertComputeViolation(ipolicy1, 1)
795
      AssertComputeViolation(ipolicy2, 0)
796
      AssertComputeViolation(ipolicy3, 0)
797
      ispec[par] -= 2
798
      AssertComputeViolation(ipolicy1, 1)
799
      AssertComputeViolation(ipolicy2, 0)
800
      AssertComputeViolation(ipolicy3, 0)
801
      ispec[par] += 1 # Restore
802
    ipolicy1[constants.IPOLICY_DTS] = ["another_template"]
803
    AssertComputeViolation(ipolicy1, 1)
804

    
805

    
806
class _StubComputeIPolicySpecViolation:
807
  def __init__(self, mem_size, cpu_count, disk_count, nic_count, disk_sizes,
808
               spindle_use, disk_template):
809
    self.mem_size = mem_size
810
    self.cpu_count = cpu_count
811
    self.disk_count = disk_count
812
    self.nic_count = nic_count
813
    self.disk_sizes = disk_sizes
814
    self.spindle_use = spindle_use
815
    self.disk_template = disk_template
816

    
817
  def __call__(self, _, mem_size, cpu_count, disk_count, nic_count, disk_sizes,
818
               spindle_use, disk_template):
819
    assert self.mem_size == mem_size
820
    assert self.cpu_count == cpu_count
821
    assert self.disk_count == disk_count
822
    assert self.nic_count == nic_count
823
    assert self.disk_sizes == disk_sizes
824
    assert self.spindle_use == spindle_use
825
    assert self.disk_template == disk_template
826

    
827
    return []
828

    
829

    
830
class _FakeConfigForComputeIPolicyInstanceViolation:
831
  def __init__(self, be):
832
    self.cluster = objects.Cluster(beparams={"default": be})
833

    
834
  def GetClusterInfo(self):
835
    return self.cluster
836

    
837

    
838
class TestComputeIPolicyInstanceViolation(unittest.TestCase):
839
  def test(self):
840
    beparams = {
841
      constants.BE_MAXMEM: 2048,
842
      constants.BE_VCPUS: 2,
843
      constants.BE_SPINDLE_USE: 4,
844
      }
845
    disks = [objects.Disk(size=512)]
846
    cfg = _FakeConfigForComputeIPolicyInstanceViolation(beparams)
847
    instance = objects.Instance(beparams=beparams, disks=disks, nics=[],
848
                                disk_template=constants.DT_PLAIN)
849
    stub = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512], 4,
850
                                            constants.DT_PLAIN)
851
    ret = common.ComputeIPolicyInstanceViolation(NotImplemented, instance,
852
                                                 cfg, _compute_fn=stub)
853
    self.assertEqual(ret, [])
854
    instance2 = objects.Instance(beparams={}, disks=disks, nics=[],
855
                                 disk_template=constants.DT_PLAIN)
856
    ret = common.ComputeIPolicyInstanceViolation(NotImplemented, instance2,
857
                                                 cfg, _compute_fn=stub)
858
    self.assertEqual(ret, [])
859

    
860

    
861
class TestComputeIPolicyInstanceSpecViolation(unittest.TestCase):
862
  def test(self):
863
    ispec = {
864
      constants.ISPEC_MEM_SIZE: 2048,
865
      constants.ISPEC_CPU_COUNT: 2,
866
      constants.ISPEC_DISK_COUNT: 1,
867
      constants.ISPEC_DISK_SIZE: [512],
868
      constants.ISPEC_NIC_COUNT: 0,
869
      constants.ISPEC_SPINDLE_USE: 1,
870
      }
871
    stub = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512], 1,
872
                                            constants.DT_PLAIN)
873
    ret = instance._ComputeIPolicyInstanceSpecViolation(NotImplemented, ispec,
874
                                                        constants.DT_PLAIN,
875
                                                        _compute_fn=stub)
876
    self.assertEqual(ret, [])
877

    
878

    
879
class _CallRecorder:
880
  def __init__(self, return_value=None):
881
    self.called = False
882
    self.return_value = return_value
883

    
884
  def __call__(self, *args):
885
    self.called = True
886
    return self.return_value
887

    
888

    
889
class TestComputeIPolicyNodeViolation(unittest.TestCase):
890
  def setUp(self):
891
    self.recorder = _CallRecorder(return_value=[])
892

    
893
  def testSameGroup(self):
894
    ret = instance_utils._ComputeIPolicyNodeViolation(
895
      NotImplemented,
896
      NotImplemented,
897
      "foo", "foo", NotImplemented,
898
      _compute_fn=self.recorder)
899
    self.assertFalse(self.recorder.called)
900
    self.assertEqual(ret, [])
901

    
902
  def testDifferentGroup(self):
903
    ret = instance_utils._ComputeIPolicyNodeViolation(
904
      NotImplemented,
905
      NotImplemented,
906
      "foo", "bar", NotImplemented,
907
      _compute_fn=self.recorder)
908
    self.assertTrue(self.recorder.called)
909
    self.assertEqual(ret, [])
910

    
911

    
912
class _FakeConfigForTargetNodeIPolicy:
913
  def __init__(self, node_info=NotImplemented):
914
    self._node_info = node_info
915

    
916
  def GetNodeInfo(self, _):
917
    return self._node_info
918

    
919

    
920
class TestCheckTargetNodeIPolicy(unittest.TestCase):
921
  def setUp(self):
922
    self.instance = objects.Instance(primary_node="blubb")
923
    self.target_node = objects.Node(group="bar")
924
    node_info = objects.Node(group="foo")
925
    fake_cfg = _FakeConfigForTargetNodeIPolicy(node_info=node_info)
926
    self.lu = _FakeLU(cfg=fake_cfg)
927

    
928
  def testNoViolation(self):
929
    compute_recoder = _CallRecorder(return_value=[])
930
    instance.CheckTargetNodeIPolicy(self.lu, NotImplemented, self.instance,
931
                                    self.target_node, NotImplemented,
932
                                    _compute_fn=compute_recoder)
933
    self.assertTrue(compute_recoder.called)
934
    self.assertEqual(self.lu.warning_log, [])
935

    
936
  def testNoIgnore(self):
937
    compute_recoder = _CallRecorder(return_value=["mem_size not in range"])
938
    self.assertRaises(errors.OpPrereqError, instance.CheckTargetNodeIPolicy,
939
                      self.lu, NotImplemented, self.instance,
940
                      self.target_node, NotImplemented,
941
                      _compute_fn=compute_recoder)
942
    self.assertTrue(compute_recoder.called)
943
    self.assertEqual(self.lu.warning_log, [])
944

    
945
  def testIgnoreViolation(self):
946
    compute_recoder = _CallRecorder(return_value=["mem_size not in range"])
947
    instance.CheckTargetNodeIPolicy(self.lu, NotImplemented, self.instance,
948
                                     self.target_node, NotImplemented,
949
                                     ignore=True, _compute_fn=compute_recoder)
950
    self.assertTrue(compute_recoder.called)
951
    msg = ("Instance does not meet target node group's (bar) instance policy:"
952
           " mem_size not in range")
953
    self.assertEqual(self.lu.warning_log, [(msg, ())])
954

    
955

    
956
class TestApplyContainerMods(unittest.TestCase):
957
  def testEmptyContainer(self):
958
    container = []
959
    chgdesc = []
960
    instance._ApplyContainerMods("test", container, chgdesc, [], None, None,
961
                                None)
962
    self.assertEqual(container, [])
963
    self.assertEqual(chgdesc, [])
964

    
965
  def testAdd(self):
966
    container = []
967
    chgdesc = []
968
    mods = instance._PrepareContainerMods([
969
      (constants.DDM_ADD, -1, "Hello"),
970
      (constants.DDM_ADD, -1, "World"),
971
      (constants.DDM_ADD, 0, "Start"),
972
      (constants.DDM_ADD, -1, "End"),
973
      ], None)
974
    instance._ApplyContainerMods("test", container, chgdesc, mods,
975
                                None, None, None)
976
    self.assertEqual(container, ["Start", "Hello", "World", "End"])
977
    self.assertEqual(chgdesc, [])
978

    
979
    mods = instance._PrepareContainerMods([
980
      (constants.DDM_ADD, 0, "zero"),
981
      (constants.DDM_ADD, 3, "Added"),
982
      (constants.DDM_ADD, 5, "four"),
983
      (constants.DDM_ADD, 7, "xyz"),
984
      ], None)
985
    instance._ApplyContainerMods("test", container, chgdesc, mods,
986
                                None, None, None)
987
    self.assertEqual(container,
988
                     ["zero", "Start", "Hello", "Added", "World", "four",
989
                      "End", "xyz"])
990
    self.assertEqual(chgdesc, [])
991

    
992
    for idx in [-2, len(container) + 1]:
993
      mods = instance._PrepareContainerMods([
994
        (constants.DDM_ADD, idx, "error"),
995
        ], None)
996
      self.assertRaises(IndexError, instance._ApplyContainerMods,
997
                        "test", container, None, mods, None, None, None)
998

    
999
  def testRemoveError(self):
1000
    for idx in [0, 1, 2, 100, -1, -4]:
1001
      mods = instance._PrepareContainerMods([
1002
        (constants.DDM_REMOVE, idx, None),
1003
        ], None)
1004
      self.assertRaises(IndexError, instance._ApplyContainerMods,
1005
                        "test", [], None, mods, None, None, None)
1006

    
1007
    mods = instance._PrepareContainerMods([
1008
      (constants.DDM_REMOVE, 0, object()),
1009
      ], None)
1010
    self.assertRaises(AssertionError, instance._ApplyContainerMods,
1011
                      "test", [""], None, mods, None, None, None)
1012

    
1013
  def testAddError(self):
1014
    for idx in range(-100, -1) + [100]:
1015
      mods = instance._PrepareContainerMods([
1016
        (constants.DDM_ADD, idx, None),
1017
        ], None)
1018
      self.assertRaises(IndexError, instance._ApplyContainerMods,
1019
                        "test", [], None, mods, None, None, None)
1020

    
1021
  def testRemove(self):
1022
    container = ["item 1", "item 2"]
1023
    mods = instance._PrepareContainerMods([
1024
      (constants.DDM_ADD, -1, "aaa"),
1025
      (constants.DDM_REMOVE, -1, None),
1026
      (constants.DDM_ADD, -1, "bbb"),
1027
      ], None)
1028
    chgdesc = []
1029
    instance._ApplyContainerMods("test", container, chgdesc, mods,
1030
                                None, None, None)
1031
    self.assertEqual(container, ["item 1", "item 2", "bbb"])
1032
    self.assertEqual(chgdesc, [
1033
      ("test/2", "remove"),
1034
      ])
1035

    
1036
  def testModify(self):
1037
    container = ["item 1", "item 2"]
1038
    mods = instance._PrepareContainerMods([
1039
      (constants.DDM_MODIFY, -1, "a"),
1040
      (constants.DDM_MODIFY, 0, "b"),
1041
      (constants.DDM_MODIFY, 1, "c"),
1042
      ], None)
1043
    chgdesc = []
1044
    instance._ApplyContainerMods("test", container, chgdesc, mods,
1045
                                None, None, None)
1046
    self.assertEqual(container, ["item 1", "item 2"])
1047
    self.assertEqual(chgdesc, [])
1048

    
1049
    for idx in [-2, len(container) + 1]:
1050
      mods = instance._PrepareContainerMods([
1051
        (constants.DDM_MODIFY, idx, "error"),
1052
        ], None)
1053
      self.assertRaises(IndexError, instance._ApplyContainerMods,
1054
                        "test", container, None, mods, None, None, None)
1055

    
1056
  class _PrivateData:
1057
    def __init__(self):
1058
      self.data = None
1059

    
1060
  @staticmethod
1061
  def _CreateTestFn(idx, params, private):
1062
    private.data = ("add", idx, params)
1063
    return ((100 * idx, params), [
1064
      ("test/%s" % idx, hex(idx)),
1065
      ])
1066

    
1067
  @staticmethod
1068
  def _ModifyTestFn(idx, item, params, private):
1069
    private.data = ("modify", idx, params)
1070
    return [
1071
      ("test/%s" % idx, "modify %s" % params),
1072
      ]
1073

    
1074
  @staticmethod
1075
  def _RemoveTestFn(idx, item, private):
1076
    private.data = ("remove", idx, item)
1077

    
1078
  def testAddWithCreateFunction(self):
1079
    container = []
1080
    chgdesc = []
1081
    mods = instance._PrepareContainerMods([
1082
      (constants.DDM_ADD, -1, "Hello"),
1083
      (constants.DDM_ADD, -1, "World"),
1084
      (constants.DDM_ADD, 0, "Start"),
1085
      (constants.DDM_ADD, -1, "End"),
1086
      (constants.DDM_REMOVE, 2, None),
1087
      (constants.DDM_MODIFY, -1, "foobar"),
1088
      (constants.DDM_REMOVE, 2, None),
1089
      (constants.DDM_ADD, 1, "More"),
1090
      ], self._PrivateData)
1091
    instance._ApplyContainerMods("test", container, chgdesc, mods,
1092
                                self._CreateTestFn, self._ModifyTestFn,
1093
                                self._RemoveTestFn)
1094
    self.assertEqual(container, [
1095
      (000, "Start"),
1096
      (100, "More"),
1097
      (000, "Hello"),
1098
      ])
1099
    self.assertEqual(chgdesc, [
1100
      ("test/0", "0x0"),
1101
      ("test/1", "0x1"),
1102
      ("test/0", "0x0"),
1103
      ("test/3", "0x3"),
1104
      ("test/2", "remove"),
1105
      ("test/2", "modify foobar"),
1106
      ("test/2", "remove"),
1107
      ("test/1", "0x1")
1108
      ])
1109
    self.assertTrue(compat.all(op == private.data[0]
1110
                               for (op, _, _, private) in mods))
1111
    self.assertEqual([private.data for (op, _, _, private) in mods], [
1112
      ("add", 0, "Hello"),
1113
      ("add", 1, "World"),
1114
      ("add", 0, "Start"),
1115
      ("add", 3, "End"),
1116
      ("remove", 2, (100, "World")),
1117
      ("modify", 2, "foobar"),
1118
      ("remove", 2, (300, "End")),
1119
      ("add", 1, "More"),
1120
      ])
1121

    
1122

    
1123
class _FakeConfigForGenDiskTemplate:
1124
  def __init__(self):
1125
    self._unique_id = itertools.count()
1126
    self._drbd_minor = itertools.count(20)
1127
    self._port = itertools.count(constants.FIRST_DRBD_PORT)
1128
    self._secret = itertools.count()
1129

    
1130
  def GetVGName(self):
1131
    return "testvg"
1132

    
1133
  def GenerateUniqueID(self, ec_id):
1134
    return "ec%s-uq%s" % (ec_id, self._unique_id.next())
1135

    
1136
  def AllocateDRBDMinor(self, nodes, instance):
1137
    return [self._drbd_minor.next()
1138
            for _ in nodes]
1139

    
1140
  def AllocatePort(self):
1141
    return self._port.next()
1142

    
1143
  def GenerateDRBDSecret(self, ec_id):
1144
    return "ec%s-secret%s" % (ec_id, self._secret.next())
1145

    
1146
  def GetInstanceInfo(self, _):
1147
    return "foobar"
1148

    
1149

    
1150
class _FakeProcForGenDiskTemplate:
1151
  def GetECId(self):
1152
    return 0
1153

    
1154

    
1155
class TestGenerateDiskTemplate(unittest.TestCase):
1156
  def setUp(self):
1157
    nodegroup = objects.NodeGroup(name="ng")
1158
    nodegroup.UpgradeConfig()
1159

    
1160
    cfg = _FakeConfigForGenDiskTemplate()
1161
    proc = _FakeProcForGenDiskTemplate()
1162

    
1163
    self.lu = _FakeLU(cfg=cfg, proc=proc)
1164
    self.nodegroup = nodegroup
1165

    
1166
  @staticmethod
1167
  def GetDiskParams():
1168
    return copy.deepcopy(constants.DISK_DT_DEFAULTS)
1169

    
1170
  def testWrongDiskTemplate(self):
1171
    gdt = instance.GenerateDiskTemplate
1172
    disk_template = "##unknown##"
1173

    
1174
    assert disk_template not in constants.DISK_TEMPLATES
1175

    
1176
    self.assertRaises(errors.ProgrammerError, gdt, self.lu, disk_template,
1177
                      "inst26831.example.com", "node30113.example.com", [], [],
1178
                      NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1179
                      self.GetDiskParams())
1180

    
1181
  def testDiskless(self):
1182
    gdt = instance.GenerateDiskTemplate
1183

    
1184
    result = gdt(self.lu, constants.DT_DISKLESS, "inst27734.example.com",
1185
                 "node30113.example.com", [], [],
1186
                 NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1187
                 self.GetDiskParams())
1188
    self.assertEqual(result, [])
1189

    
1190
  def _TestTrivialDisk(self, template, disk_info, base_index, exp_dev_type,
1191
                       file_storage_dir=NotImplemented,
1192
                       file_driver=NotImplemented,
1193
                       req_file_storage=NotImplemented,
1194
                       req_shr_file_storage=NotImplemented):
1195
    gdt = instance.GenerateDiskTemplate
1196

    
1197
    map(lambda params: utils.ForceDictType(params,
1198
                                           constants.IDISK_PARAMS_TYPES),
1199
        disk_info)
1200

    
1201
    # Check if non-empty list of secondaries is rejected
1202
    self.assertRaises(errors.ProgrammerError, gdt, self.lu,
1203
                      template, "inst25088.example.com",
1204
                      "node185.example.com", ["node323.example.com"], [],
1205
                      NotImplemented, NotImplemented, base_index,
1206
                      self.lu.LogInfo, self.GetDiskParams(),
1207
                      _req_file_storage=req_file_storage,
1208
                      _req_shr_file_storage=req_shr_file_storage)
1209

    
1210
    result = gdt(self.lu, template, "inst21662.example.com",
1211
                 "node21741.example.com", [],
1212
                 disk_info, file_storage_dir, file_driver, base_index,
1213
                 self.lu.LogInfo, self.GetDiskParams(),
1214
                 _req_file_storage=req_file_storage,
1215
                 _req_shr_file_storage=req_shr_file_storage)
1216

    
1217
    for (idx, disk) in enumerate(result):
1218
      self.assertTrue(isinstance(disk, objects.Disk))
1219
      self.assertEqual(disk.dev_type, exp_dev_type)
1220
      self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
1221
      self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
1222
      self.assertTrue(disk.children is None)
1223

    
1224
    self._CheckIvNames(result, base_index, base_index + len(disk_info))
1225
    instance._UpdateIvNames(base_index, result)
1226
    self._CheckIvNames(result, base_index, base_index + len(disk_info))
1227

    
1228
    return result
1229

    
1230
  def _CheckIvNames(self, disks, base_index, end_index):
1231
    self.assertEqual(map(operator.attrgetter("iv_name"), disks),
1232
                     ["disk/%s" % i for i in range(base_index, end_index)])
1233

    
1234
  def testPlain(self):
1235
    disk_info = [{
1236
      constants.IDISK_SIZE: 1024,
1237
      constants.IDISK_MODE: constants.DISK_RDWR,
1238
      }, {
1239
      constants.IDISK_SIZE: 4096,
1240
      constants.IDISK_VG: "othervg",
1241
      constants.IDISK_MODE: constants.DISK_RDWR,
1242
      }]
1243

    
1244
    result = self._TestTrivialDisk(constants.DT_PLAIN, disk_info, 3,
1245
                                   constants.LD_LV)
1246

    
1247
    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1248
      ("testvg", "ec0-uq0.disk3"),
1249
      ("othervg", "ec0-uq1.disk4"),
1250
      ])
1251

    
1252
  @staticmethod
1253
  def _AllowFileStorage():
1254
    pass
1255

    
1256
  @staticmethod
1257
  def _ForbidFileStorage():
1258
    raise errors.OpPrereqError("Disallowed in test")
1259

    
1260
  def testFile(self):
1261
    self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
1262
                      constants.DT_FILE, [], 0, NotImplemented,
1263
                      req_file_storage=self._ForbidFileStorage)
1264
    self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
1265
                      constants.DT_SHARED_FILE, [], 0, NotImplemented,
1266
                      req_shr_file_storage=self._ForbidFileStorage)
1267

    
1268
    for disk_template in [constants.DT_FILE, constants.DT_SHARED_FILE]:
1269
      disk_info = [{
1270
        constants.IDISK_SIZE: 80 * 1024,
1271
        constants.IDISK_MODE: constants.DISK_RDONLY,
1272
        }, {
1273
        constants.IDISK_SIZE: 4096,
1274
        constants.IDISK_MODE: constants.DISK_RDWR,
1275
        }, {
1276
        constants.IDISK_SIZE: 6 * 1024,
1277
        constants.IDISK_MODE: constants.DISK_RDWR,
1278
        }]
1279

    
1280
      result = self._TestTrivialDisk(disk_template, disk_info, 2,
1281
        constants.LD_FILE, file_storage_dir="/tmp",
1282
        file_driver=constants.FD_BLKTAP,
1283
        req_file_storage=self._AllowFileStorage,
1284
        req_shr_file_storage=self._AllowFileStorage)
1285

    
1286
      for (idx, disk) in enumerate(result):
1287
        (file_driver, file_storage_dir) = disk.logical_id
1288
        dir_fmt = r"^/tmp/.*\.%s\.disk%d$" % (disk_template, idx + 2)
1289
        self.assertEqual(file_driver, constants.FD_BLKTAP)
1290
        # FIXME: use assertIsNotNone when py 2.7 is minimum supported version
1291
        self.assertNotEqual(re.match(dir_fmt, file_storage_dir), None)
1292

    
1293
  def testBlock(self):
1294
    disk_info = [{
1295
      constants.IDISK_SIZE: 8 * 1024,
1296
      constants.IDISK_MODE: constants.DISK_RDWR,
1297
      constants.IDISK_ADOPT: "/tmp/some/block/dev",
1298
      }]
1299

    
1300
    result = self._TestTrivialDisk(constants.DT_BLOCK, disk_info, 10,
1301
                                   constants.LD_BLOCKDEV)
1302

    
1303
    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1304
      (constants.BLOCKDEV_DRIVER_MANUAL, "/tmp/some/block/dev"),
1305
      ])
1306

    
1307
  def testRbd(self):
1308
    disk_info = [{
1309
      constants.IDISK_SIZE: 8 * 1024,
1310
      constants.IDISK_MODE: constants.DISK_RDONLY,
1311
      }, {
1312
      constants.IDISK_SIZE: 100 * 1024,
1313
      constants.IDISK_MODE: constants.DISK_RDWR,
1314
      }]
1315

    
1316
    result = self._TestTrivialDisk(constants.DT_RBD, disk_info, 0,
1317
                                   constants.LD_RBD)
1318

    
1319
    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1320
      ("rbd", "ec0-uq0.rbd.disk0"),
1321
      ("rbd", "ec0-uq1.rbd.disk1"),
1322
      ])
1323

    
1324
  def testDrbd8(self):
1325
    gdt = instance.GenerateDiskTemplate
1326
    drbd8_defaults = constants.DISK_LD_DEFAULTS[constants.LD_DRBD8]
1327
    drbd8_default_metavg = drbd8_defaults[constants.LDP_DEFAULT_METAVG]
1328

    
1329
    disk_info = [{
1330
      constants.IDISK_SIZE: 1024,
1331
      constants.IDISK_MODE: constants.DISK_RDWR,
1332
      }, {
1333
      constants.IDISK_SIZE: 100 * 1024,
1334
      constants.IDISK_MODE: constants.DISK_RDONLY,
1335
      constants.IDISK_METAVG: "metavg",
1336
      }, {
1337
      constants.IDISK_SIZE: 4096,
1338
      constants.IDISK_MODE: constants.DISK_RDWR,
1339
      constants.IDISK_VG: "vgxyz",
1340
      },
1341
      ]
1342

    
1343
    exp_logical_ids = [[
1344
      (self.lu.cfg.GetVGName(), "ec0-uq0.disk0_data"),
1345
      (drbd8_default_metavg, "ec0-uq0.disk0_meta"),
1346
      ], [
1347
      (self.lu.cfg.GetVGName(), "ec0-uq1.disk1_data"),
1348
      ("metavg", "ec0-uq1.disk1_meta"),
1349
      ], [
1350
      ("vgxyz", "ec0-uq2.disk2_data"),
1351
      (drbd8_default_metavg, "ec0-uq2.disk2_meta"),
1352
      ]]
1353

    
1354
    assert len(exp_logical_ids) == len(disk_info)
1355

    
1356
    map(lambda params: utils.ForceDictType(params,
1357
                                           constants.IDISK_PARAMS_TYPES),
1358
        disk_info)
1359

    
1360
    # Check if empty list of secondaries is rejected
1361
    self.assertRaises(errors.ProgrammerError, gdt, self.lu, constants.DT_DRBD8,
1362
                      "inst827.example.com", "node1334.example.com", [],
1363
                      disk_info, NotImplemented, NotImplemented, 0,
1364
                      self.lu.LogInfo, self.GetDiskParams())
1365

    
1366
    result = gdt(self.lu, constants.DT_DRBD8, "inst827.example.com",
1367
                 "node1334.example.com", ["node12272.example.com"],
1368
                 disk_info, NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1369
                 self.GetDiskParams())
1370

    
1371
    for (idx, disk) in enumerate(result):
1372
      self.assertTrue(isinstance(disk, objects.Disk))
1373
      self.assertEqual(disk.dev_type, constants.LD_DRBD8)
1374
      self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
1375
      self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
1376

    
1377
      for child in disk.children:
1378
        self.assertTrue(isinstance(disk, objects.Disk))
1379
        self.assertEqual(child.dev_type, constants.LD_LV)
1380
        self.assertTrue(child.children is None)
1381

    
1382
      self.assertEqual(map(operator.attrgetter("logical_id"), disk.children),
1383
                       exp_logical_ids[idx])
1384

    
1385
      self.assertEqual(len(disk.children), 2)
1386
      self.assertEqual(disk.children[0].size, disk.size)
1387
      self.assertEqual(disk.children[1].size, constants.DRBD_META_SIZE)
1388

    
1389
    self._CheckIvNames(result, 0, len(disk_info))
1390
    instance._UpdateIvNames(0, result)
1391
    self._CheckIvNames(result, 0, len(disk_info))
1392

    
1393
    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1394
      ("node1334.example.com", "node12272.example.com",
1395
       constants.FIRST_DRBD_PORT, 20, 21, "ec0-secret0"),
1396
      ("node1334.example.com", "node12272.example.com",
1397
       constants.FIRST_DRBD_PORT + 1, 22, 23, "ec0-secret1"),
1398
      ("node1334.example.com", "node12272.example.com",
1399
       constants.FIRST_DRBD_PORT + 2, 24, 25, "ec0-secret2"),
1400
      ])
1401

    
1402

    
1403
class _ConfigForDiskWipe:
1404
  def __init__(self, exp_node):
1405
    self._exp_node = exp_node
1406

    
1407
  def SetDiskID(self, device, node):
1408
    assert isinstance(device, objects.Disk)
1409
    assert node == self._exp_node
1410

    
1411

    
1412
class _RpcForDiskWipe:
1413
  def __init__(self, exp_node, pause_cb, wipe_cb):
1414
    self._exp_node = exp_node
1415
    self._pause_cb = pause_cb
1416
    self._wipe_cb = wipe_cb
1417

    
1418
  def call_blockdev_pause_resume_sync(self, node, disks, pause):
1419
    assert node == self._exp_node
1420
    return rpc.RpcResult(data=self._pause_cb(disks, pause))
1421

    
1422
  def call_blockdev_wipe(self, node, bdev, offset, size):
1423
    assert node == self._exp_node
1424
    return rpc.RpcResult(data=self._wipe_cb(bdev, offset, size))
1425

    
1426

    
1427
class _DiskPauseTracker:
1428
  def __init__(self):
1429
    self.history = []
1430

    
1431
  def __call__(self, (disks, instance), pause):
1432
    assert not (set(disks) - set(instance.disks))
1433

    
1434
    self.history.extend((i.logical_id, i.size, pause)
1435
                        for i in disks)
1436

    
1437
    return (True, [True] * len(disks))
1438

    
1439

    
1440
class _DiskWipeProgressTracker:
1441
  def __init__(self, start_offset):
1442
    self._start_offset = start_offset
1443
    self.progress = {}
1444

    
1445
  def __call__(self, (disk, _), offset, size):
1446
    assert isinstance(offset, (long, int))
1447
    assert isinstance(size, (long, int))
1448

    
1449
    max_chunk_size = (disk.size / 100.0 * constants.MIN_WIPE_CHUNK_PERCENT)
1450

    
1451
    assert offset >= self._start_offset
1452
    assert (offset + size) <= disk.size
1453

    
1454
    assert size > 0
1455
    assert size <= constants.MAX_WIPE_CHUNK
1456
    assert size <= max_chunk_size
1457

    
1458
    assert offset == self._start_offset or disk.logical_id in self.progress
1459

    
1460
    # Keep track of progress
1461
    cur_progress = self.progress.setdefault(disk.logical_id, self._start_offset)
1462

    
1463
    assert cur_progress == offset
1464

    
1465
    # Record progress
1466
    self.progress[disk.logical_id] += size
1467

    
1468
    return (True, None)
1469

    
1470

    
1471
class TestWipeDisks(unittest.TestCase):
1472
  def _FailingPauseCb(self, (disks, _), pause):
1473
    self.assertEqual(len(disks), 3)
1474
    self.assertTrue(pause)
1475
    # Simulate an RPC error
1476
    return (False, "error")
1477

    
1478
  def testPauseFailure(self):
1479
    node_name = "node1372.example.com"
1480

    
1481
    lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, self._FailingPauseCb,
1482
                                     NotImplemented),
1483
                 cfg=_ConfigForDiskWipe(node_name))
1484

    
1485
    disks = [
1486
      objects.Disk(dev_type=constants.LD_LV),
1487
      objects.Disk(dev_type=constants.LD_LV),
1488
      objects.Disk(dev_type=constants.LD_LV),
1489
      ]
1490

    
1491
    inst = objects.Instance(name="inst21201",
1492
                            primary_node=node_name,
1493
                            disk_template=constants.DT_PLAIN,
1494
                            disks=disks)
1495

    
1496
    self.assertRaises(errors.OpExecError, instance.WipeDisks, lu, inst)
1497

    
1498
  def _FailingWipeCb(self, (disk, _), offset, size):
1499
    # This should only ever be called for the first disk
1500
    self.assertEqual(disk.logical_id, "disk0")
1501
    return (False, None)
1502

    
1503
  def testFailingWipe(self):
1504
    node_name = "node13445.example.com"
1505
    pt = _DiskPauseTracker()
1506

    
1507
    lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, pt, self._FailingWipeCb),
1508
                 cfg=_ConfigForDiskWipe(node_name))
1509

    
1510
    disks = [
1511
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk0",
1512
                   size=100 * 1024),
1513
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk1",
1514
                   size=500 * 1024),
1515
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk2", size=256),
1516
      ]
1517

    
1518
    inst = objects.Instance(name="inst562",
1519
                            primary_node=node_name,
1520
                            disk_template=constants.DT_PLAIN,
1521
                            disks=disks)
1522

    
1523
    try:
1524
      instance.WipeDisks(lu, inst)
1525
    except errors.OpExecError, err:
1526
      self.assertTrue(str(err), "Could not wipe disk 0 at offset 0 ")
1527
    else:
1528
      self.fail("Did not raise exception")
1529

    
1530
    # Check if all disks were paused and resumed
1531
    self.assertEqual(pt.history, [
1532
      ("disk0", 100 * 1024, True),
1533
      ("disk1", 500 * 1024, True),
1534
      ("disk2", 256, True),
1535
      ("disk0", 100 * 1024, False),
1536
      ("disk1", 500 * 1024, False),
1537
      ("disk2", 256, False),
1538
      ])
1539

    
1540
  def _PrepareWipeTest(self, start_offset, disks):
1541
    node_name = "node-with-offset%s.example.com" % start_offset
1542
    pauset = _DiskPauseTracker()
1543
    progresst = _DiskWipeProgressTracker(start_offset)
1544

    
1545
    lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, pauset, progresst),
1546
                 cfg=_ConfigForDiskWipe(node_name))
1547

    
1548
    instance = objects.Instance(name="inst3560",
1549
                                primary_node=node_name,
1550
                                disk_template=constants.DT_PLAIN,
1551
                                disks=disks)
1552

    
1553
    return (lu, instance, pauset, progresst)
1554

    
1555
  def testNormalWipe(self):
1556
    disks = [
1557
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk0", size=1024),
1558
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk1",
1559
                   size=500 * 1024),
1560
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk2", size=128),
1561
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk3",
1562
                   size=constants.MAX_WIPE_CHUNK),
1563
      ]
1564

    
1565
    (lu, inst, pauset, progresst) = self._PrepareWipeTest(0, disks)
1566

    
1567
    instance.WipeDisks(lu, inst)
1568

    
1569
    self.assertEqual(pauset.history, [
1570
      ("disk0", 1024, True),
1571
      ("disk1", 500 * 1024, True),
1572
      ("disk2", 128, True),
1573
      ("disk3", constants.MAX_WIPE_CHUNK, True),
1574
      ("disk0", 1024, False),
1575
      ("disk1", 500 * 1024, False),
1576
      ("disk2", 128, False),
1577
      ("disk3", constants.MAX_WIPE_CHUNK, False),
1578
      ])
1579

    
1580
    # Ensure the complete disk has been wiped
1581
    self.assertEqual(progresst.progress,
1582
                     dict((i.logical_id, i.size) for i in disks))
1583

    
1584
  def testWipeWithStartOffset(self):
1585
    for start_offset in [0, 280, 8895, 1563204]:
1586
      disks = [
1587
        objects.Disk(dev_type=constants.LD_LV, logical_id="disk0",
1588
                     size=128),
1589
        objects.Disk(dev_type=constants.LD_LV, logical_id="disk1",
1590
                     size=start_offset + (100 * 1024)),
1591
        ]
1592

    
1593
      (lu, inst, pauset, progresst) = \
1594
        self._PrepareWipeTest(start_offset, disks)
1595

    
1596
      # Test start offset with only one disk
1597
      instance.WipeDisks(lu, inst,
1598
                         disks=[(1, disks[1], start_offset)])
1599

    
1600
      # Only the second disk may have been paused and wiped
1601
      self.assertEqual(pauset.history, [
1602
        ("disk1", start_offset + (100 * 1024), True),
1603
        ("disk1", start_offset + (100 * 1024), False),
1604
        ])
1605
      self.assertEqual(progresst.progress, {
1606
        "disk1": disks[1].size,
1607
        })
1608

    
1609

    
1610
class TestDiskSizeInBytesToMebibytes(unittest.TestCase):
1611
  def testLessThanOneMebibyte(self):
1612
    for i in [1, 2, 7, 512, 1000, 1023]:
1613
      lu = _FakeLU()
1614
      result = instance_storage._DiskSizeInBytesToMebibytes(lu, i)
1615
      self.assertEqual(result, 1)
1616
      self.assertEqual(len(lu.warning_log), 1)
1617
      self.assertEqual(len(lu.warning_log[0]), 2)
1618
      (_, (warnsize, )) = lu.warning_log[0]
1619
      self.assertEqual(warnsize, (1024 * 1024) - i)
1620

    
1621
  def testEven(self):
1622
    for i in [1, 2, 7, 512, 1000, 1023]:
1623
      lu = _FakeLU()
1624
      result = instance_storage._DiskSizeInBytesToMebibytes(lu,
1625
                                                            i * 1024 * 1024)
1626
      self.assertEqual(result, i)
1627
      self.assertFalse(lu.warning_log)
1628

    
1629
  def testLargeNumber(self):
1630
    for i in [1, 2, 7, 512, 1000, 1023, 2724, 12420]:
1631
      for j in [1, 2, 486, 326, 986, 1023]:
1632
        lu = _FakeLU()
1633
        size = (1024 * 1024 * i) + j
1634
        result = instance_storage._DiskSizeInBytesToMebibytes(lu, size)
1635
        self.assertEqual(result, i + 1, msg="Amount was not rounded up")
1636
        self.assertEqual(len(lu.warning_log), 1)
1637
        self.assertEqual(len(lu.warning_log[0]), 2)
1638
        (_, (warnsize, )) = lu.warning_log[0]
1639
        self.assertEqual(warnsize, (1024 * 1024) - j)
1640

    
1641

    
1642
class TestCopyLockList(unittest.TestCase):
1643
  def test(self):
1644
    self.assertEqual(instance.CopyLockList([]), [])
1645
    self.assertEqual(instance.CopyLockList(None), None)
1646
    self.assertEqual(instance.CopyLockList(locking.ALL_SET), locking.ALL_SET)
1647

    
1648
    names = ["foo", "bar"]
1649
    output = instance.CopyLockList(names)
1650
    self.assertEqual(names, output)
1651
    self.assertNotEqual(id(names), id(output), msg="List was not copied")
1652

    
1653

    
1654
class TestCheckOpportunisticLocking(unittest.TestCase):
1655
  class OpTest(opcodes.OpCode):
1656
    OP_PARAMS = [
1657
      opcodes._POpportunisticLocking,
1658
      opcodes._PIAllocFromDesc(""),
1659
      ]
1660

    
1661
  @classmethod
1662
  def _MakeOp(cls, **kwargs):
1663
    op = cls.OpTest(**kwargs)
1664
    op.Validate(True)
1665
    return op
1666

    
1667
  def testMissingAttributes(self):
1668
    self.assertRaises(AttributeError, instance._CheckOpportunisticLocking,
1669
                      object())
1670

    
1671
  def testDefaults(self):
1672
    op = self._MakeOp()
1673
    instance._CheckOpportunisticLocking(op)
1674

    
1675
  def test(self):
1676
    for iallocator in [None, "something", "other"]:
1677
      for opplock in [False, True]:
1678
        op = self._MakeOp(iallocator=iallocator,
1679
                          opportunistic_locking=opplock)
1680
        if opplock and not iallocator:
1681
          self.assertRaises(errors.OpPrereqError,
1682
                            instance._CheckOpportunisticLocking, op)
1683
        else:
1684
          instance._CheckOpportunisticLocking(op)
1685

    
1686

    
1687
class _OpTestVerifyErrors(opcodes.OpCode):
1688
  OP_PARAMS = [
1689
    opcodes._PDebugSimulateErrors,
1690
    opcodes._PErrorCodes,
1691
    opcodes._PIgnoreErrors,
1692
    ]
1693

    
1694

    
1695
class _LuTestVerifyErrors(cluster._VerifyErrors):
1696
  def __init__(self, **kwargs):
1697
    cluster._VerifyErrors.__init__(self)
1698
    self.op = _OpTestVerifyErrors(**kwargs)
1699
    self.op.Validate(True)
1700
    self.msglist = []
1701
    self._feedback_fn = self.msglist.append
1702
    self.bad = False
1703

    
1704
  def DispatchCallError(self, which, *args, **kwargs):
1705
    if which:
1706
      self._Error(*args, **kwargs)
1707
    else:
1708
      self._ErrorIf(True, *args, **kwargs)
1709

    
1710
  def CallErrorIf(self, c, *args, **kwargs):
1711
    self._ErrorIf(c, *args, **kwargs)
1712

    
1713

    
1714
class TestVerifyErrors(unittest.TestCase):
1715
  # Fake cluster-verify error code structures; we use two arbitary real error
1716
  # codes to pass validation of ignore_errors
1717
  (_, _ERR1ID, _) = constants.CV_ECLUSTERCFG
1718
  _NODESTR = "node"
1719
  _NODENAME = "mynode"
1720
  _ERR1CODE = (_NODESTR, _ERR1ID, "Error one")
1721
  (_, _ERR2ID, _) = constants.CV_ECLUSTERCERT
1722
  _INSTSTR = "instance"
1723
  _INSTNAME = "myinstance"
1724
  _ERR2CODE = (_INSTSTR, _ERR2ID, "Error two")
1725
  # Arguments used to call _Error() or _ErrorIf()
1726
  _ERR1ARGS = (_ERR1CODE, _NODENAME, "Error1 is %s", "an error")
1727
  _ERR2ARGS = (_ERR2CODE, _INSTNAME, "Error2 has no argument")
1728
  # Expected error messages
1729
  _ERR1MSG = _ERR1ARGS[2] % _ERR1ARGS[3]
1730
  _ERR2MSG = _ERR2ARGS[2]
1731

    
1732
  def testNoError(self):
1733
    lu = _LuTestVerifyErrors()
1734
    lu.CallErrorIf(False, self._ERR1CODE, *self._ERR1ARGS)
1735
    self.assertFalse(lu.bad)
1736
    self.assertFalse(lu.msglist)
1737

    
1738
  def _InitTest(self, **kwargs):
1739
    self.lu1 = _LuTestVerifyErrors(**kwargs)
1740
    self.lu2 = _LuTestVerifyErrors(**kwargs)
1741

    
1742
  def _CallError(self, *args, **kwargs):
1743
    # Check that _Error() and _ErrorIf() produce the same results
1744
    self.lu1.DispatchCallError(True, *args, **kwargs)
1745
    self.lu2.DispatchCallError(False, *args, **kwargs)
1746
    self.assertEqual(self.lu1.bad, self.lu2.bad)
1747
    self.assertEqual(self.lu1.msglist, self.lu2.msglist)
1748
    # Test-specific checks are made on one LU
1749
    return self.lu1
1750

    
1751
  def _checkMsgCommon(self, logstr, errmsg, itype, item, warning):
1752
    self.assertTrue(errmsg in logstr)
1753
    if warning:
1754
      self.assertTrue("WARNING" in logstr)
1755
    else:
1756
      self.assertTrue("ERROR" in logstr)
1757
    self.assertTrue(itype in logstr)
1758
    self.assertTrue(item in logstr)
1759

    
1760
  def _checkMsg1(self, logstr, warning=False):
1761
    self._checkMsgCommon(logstr, self._ERR1MSG, self._NODESTR,
1762
                         self._NODENAME, warning)
1763

    
1764
  def _checkMsg2(self, logstr, warning=False):
1765
    self._checkMsgCommon(logstr, self._ERR2MSG, self._INSTSTR,
1766
                         self._INSTNAME, warning)
1767

    
1768
  def testPlain(self):
1769
    self._InitTest()
1770
    lu = self._CallError(*self._ERR1ARGS)
1771
    self.assertTrue(lu.bad)
1772
    self.assertEqual(len(lu.msglist), 1)
1773
    self._checkMsg1(lu.msglist[0])
1774

    
1775
  def testMultiple(self):
1776
    self._InitTest()
1777
    self._CallError(*self._ERR1ARGS)
1778
    lu = self._CallError(*self._ERR2ARGS)
1779
    self.assertTrue(lu.bad)
1780
    self.assertEqual(len(lu.msglist), 2)
1781
    self._checkMsg1(lu.msglist[0])
1782
    self._checkMsg2(lu.msglist[1])
1783

    
1784
  def testIgnore(self):
1785
    self._InitTest(ignore_errors=[self._ERR1ID])
1786
    lu = self._CallError(*self._ERR1ARGS)
1787
    self.assertFalse(lu.bad)
1788
    self.assertEqual(len(lu.msglist), 1)
1789
    self._checkMsg1(lu.msglist[0], warning=True)
1790

    
1791
  def testWarning(self):
1792
    self._InitTest()
1793
    lu = self._CallError(*self._ERR1ARGS,
1794
                         code=_LuTestVerifyErrors.ETYPE_WARNING)
1795
    self.assertFalse(lu.bad)
1796
    self.assertEqual(len(lu.msglist), 1)
1797
    self._checkMsg1(lu.msglist[0], warning=True)
1798

    
1799
  def testWarning2(self):
1800
    self._InitTest()
1801
    self._CallError(*self._ERR1ARGS)
1802
    lu = self._CallError(*self._ERR2ARGS,
1803
                         code=_LuTestVerifyErrors.ETYPE_WARNING)
1804
    self.assertTrue(lu.bad)
1805
    self.assertEqual(len(lu.msglist), 2)
1806
    self._checkMsg1(lu.msglist[0])
1807
    self._checkMsg2(lu.msglist[1], warning=True)
1808

    
1809
  def testDebugSimulate(self):
1810
    lu = _LuTestVerifyErrors(debug_simulate_errors=True)
1811
    lu.CallErrorIf(False, *self._ERR1ARGS)
1812
    self.assertTrue(lu.bad)
1813
    self.assertEqual(len(lu.msglist), 1)
1814
    self._checkMsg1(lu.msglist[0])
1815

    
1816
  def testErrCodes(self):
1817
    self._InitTest(error_codes=True)
1818
    lu = self._CallError(*self._ERR1ARGS)
1819
    self.assertTrue(lu.bad)
1820
    self.assertEqual(len(lu.msglist), 1)
1821
    self._checkMsg1(lu.msglist[0])
1822
    self.assertTrue(self._ERR1ID in lu.msglist[0])
1823

    
1824

    
1825
class TestGetUpdatedIPolicy(unittest.TestCase):
1826
  """Tests for cmdlib._GetUpdatedIPolicy()"""
1827
  _OLD_CLUSTER_POLICY = {
1828
    constants.IPOLICY_VCPU_RATIO: 1.5,
1829
    constants.ISPECS_MINMAX: [
1830
      {
1831
        constants.ISPECS_MIN: {
1832
          constants.ISPEC_MEM_SIZE: 32768,
1833
          constants.ISPEC_CPU_COUNT: 8,
1834
          constants.ISPEC_DISK_COUNT: 1,
1835
          constants.ISPEC_DISK_SIZE: 1024,
1836
          constants.ISPEC_NIC_COUNT: 1,
1837
          constants.ISPEC_SPINDLE_USE: 1,
1838
          },
1839
        constants.ISPECS_MAX: {
1840
          constants.ISPEC_MEM_SIZE: 65536,
1841
          constants.ISPEC_CPU_COUNT: 10,
1842
          constants.ISPEC_DISK_COUNT: 5,
1843
          constants.ISPEC_DISK_SIZE: 1024 * 1024,
1844
          constants.ISPEC_NIC_COUNT: 3,
1845
          constants.ISPEC_SPINDLE_USE: 12,
1846
          },
1847
        },
1848
      constants.ISPECS_MINMAX_DEFAULTS,
1849
      ],
1850
    constants.ISPECS_STD: constants.IPOLICY_DEFAULTS[constants.ISPECS_STD],
1851
    }
1852
  _OLD_GROUP_POLICY = {
1853
    constants.IPOLICY_SPINDLE_RATIO: 2.5,
1854
    constants.ISPECS_MINMAX: [{
1855
      constants.ISPECS_MIN: {
1856
        constants.ISPEC_MEM_SIZE: 128,
1857
        constants.ISPEC_CPU_COUNT: 1,
1858
        constants.ISPEC_DISK_COUNT: 1,
1859
        constants.ISPEC_DISK_SIZE: 1024,
1860
        constants.ISPEC_NIC_COUNT: 1,
1861
        constants.ISPEC_SPINDLE_USE: 1,
1862
        },
1863
      constants.ISPECS_MAX: {
1864
        constants.ISPEC_MEM_SIZE: 32768,
1865
        constants.ISPEC_CPU_COUNT: 8,
1866
        constants.ISPEC_DISK_COUNT: 5,
1867
        constants.ISPEC_DISK_SIZE: 1024 * 1024,
1868
        constants.ISPEC_NIC_COUNT: 3,
1869
        constants.ISPEC_SPINDLE_USE: 12,
1870
        },
1871
      }],
1872
    }
1873

    
1874
  def _TestSetSpecs(self, old_policy, isgroup):
1875
    diff_minmax = [{
1876
      constants.ISPECS_MIN: {
1877
        constants.ISPEC_MEM_SIZE: 64,
1878
        constants.ISPEC_CPU_COUNT: 1,
1879
        constants.ISPEC_DISK_COUNT: 2,
1880
        constants.ISPEC_DISK_SIZE: 64,
1881
        constants.ISPEC_NIC_COUNT: 1,
1882
        constants.ISPEC_SPINDLE_USE: 1,
1883
        },
1884
      constants.ISPECS_MAX: {
1885
        constants.ISPEC_MEM_SIZE: 16384,
1886
        constants.ISPEC_CPU_COUNT: 10,
1887
        constants.ISPEC_DISK_COUNT: 12,
1888
        constants.ISPEC_DISK_SIZE: 1024,
1889
        constants.ISPEC_NIC_COUNT: 9,
1890
        constants.ISPEC_SPINDLE_USE: 18,
1891
        },
1892
      }]
1893
    diff_std = {
1894
        constants.ISPEC_DISK_COUNT: 10,
1895
        constants.ISPEC_DISK_SIZE: 512,
1896
        }
1897
    diff_policy = {
1898
      constants.ISPECS_MINMAX: diff_minmax
1899
      }
1900
    if not isgroup:
1901
      diff_policy[constants.ISPECS_STD] = diff_std
1902
    new_policy = common.GetUpdatedIPolicy(old_policy, diff_policy,
1903
                                          group_policy=isgroup)
1904

    
1905
    self.assertTrue(constants.ISPECS_MINMAX in new_policy)
1906
    self.assertEqual(new_policy[constants.ISPECS_MINMAX], diff_minmax)
1907
    for key in old_policy:
1908
      if not key in diff_policy:
1909
        self.assertTrue(key in new_policy)
1910
        self.assertEqual(new_policy[key], old_policy[key])
1911

    
1912
    if not isgroup:
1913
      new_std = new_policy[constants.ISPECS_STD]
1914
      for key in diff_std:
1915
        self.assertTrue(key in new_std)
1916
        self.assertEqual(new_std[key], diff_std[key])
1917
      old_std = old_policy.get(constants.ISPECS_STD, {})
1918
      for key in old_std:
1919
        self.assertTrue(key in new_std)
1920
        if key not in diff_std:
1921
          self.assertEqual(new_std[key], old_std[key])
1922

    
1923
  def _TestSet(self, old_policy, diff_policy, isgroup):
1924
    new_policy = common.GetUpdatedIPolicy(old_policy, diff_policy,
1925
                                           group_policy=isgroup)
1926
    for key in diff_policy:
1927
      self.assertTrue(key in new_policy)
1928
      self.assertEqual(new_policy[key], diff_policy[key])
1929
    for key in old_policy:
1930
      if not key in diff_policy:
1931
        self.assertTrue(key in new_policy)
1932
        self.assertEqual(new_policy[key], old_policy[key])
1933

    
1934
  def testSet(self):
1935
    diff_policy = {
1936
      constants.IPOLICY_VCPU_RATIO: 3,
1937
      constants.IPOLICY_DTS: [constants.DT_FILE],
1938
      }
1939
    self._TestSet(self._OLD_GROUP_POLICY, diff_policy, True)
1940
    self._TestSetSpecs(self._OLD_GROUP_POLICY, True)
1941
    self._TestSet({}, diff_policy, True)
1942
    self._TestSetSpecs({}, True)
1943
    self._TestSet(self._OLD_CLUSTER_POLICY, diff_policy, False)
1944
    self._TestSetSpecs(self._OLD_CLUSTER_POLICY, False)
1945

    
1946
  def testUnset(self):
1947
    old_policy = self._OLD_GROUP_POLICY
1948
    diff_policy = {
1949
      constants.IPOLICY_SPINDLE_RATIO: constants.VALUE_DEFAULT,
1950
      }
1951
    new_policy = common.GetUpdatedIPolicy(old_policy, diff_policy,
1952
                                          group_policy=True)
1953
    for key in diff_policy:
1954
      self.assertFalse(key in new_policy)
1955
    for key in old_policy:
1956
      if not key in diff_policy:
1957
        self.assertTrue(key in new_policy)
1958
        self.assertEqual(new_policy[key], old_policy[key])
1959

    
1960
    self.assertRaises(errors.OpPrereqError, common.GetUpdatedIPolicy,
1961
                      old_policy, diff_policy, group_policy=False)
1962

    
1963
  def testUnsetEmpty(self):
1964
    old_policy = {}
1965
    for key in constants.IPOLICY_ALL_KEYS:
1966
      diff_policy = {
1967
        key: constants.VALUE_DEFAULT,
1968
        }
1969
    new_policy = common.GetUpdatedIPolicy(old_policy, diff_policy,
1970
                                          group_policy=True)
1971
    self.assertEqual(new_policy, old_policy)
1972

    
1973
  def _TestInvalidKeys(self, old_policy, isgroup):
1974
    INVALID_KEY = "this_key_shouldnt_be_allowed"
1975
    INVALID_DICT = {
1976
      INVALID_KEY: 3,
1977
      }
1978
    invalid_policy = INVALID_DICT
1979
    self.assertRaises(errors.OpPrereqError, common.GetUpdatedIPolicy,
1980
                      old_policy, invalid_policy, group_policy=isgroup)
1981
    invalid_ispecs = {
1982
      constants.ISPECS_MINMAX: [INVALID_DICT],
1983
      }
1984
    self.assertRaises(errors.TypeEnforcementError, common.GetUpdatedIPolicy,
1985
                      old_policy, invalid_ispecs, group_policy=isgroup)
1986
    if isgroup:
1987
      invalid_for_group = {
1988
        constants.ISPECS_STD: constants.IPOLICY_DEFAULTS[constants.ISPECS_STD],
1989
        }
1990
      self.assertRaises(errors.OpPrereqError, common.GetUpdatedIPolicy,
1991
                        old_policy, invalid_for_group, group_policy=isgroup)
1992
    good_ispecs = self._OLD_CLUSTER_POLICY[constants.ISPECS_MINMAX]
1993
    invalid_ispecs = copy.deepcopy(good_ispecs)
1994
    invalid_policy = {
1995
      constants.ISPECS_MINMAX: invalid_ispecs,
1996
      }
1997
    for minmax in invalid_ispecs:
1998
      for key in constants.ISPECS_MINMAX_KEYS:
1999
        ispec = minmax[key]
2000
        ispec[INVALID_KEY] = None
2001
        self.assertRaises(errors.TypeEnforcementError,
2002
                          common.GetUpdatedIPolicy, old_policy,
2003
                          invalid_policy, group_policy=isgroup)
2004
        del ispec[INVALID_KEY]
2005
        for par in constants.ISPECS_PARAMETERS:
2006
          oldv = ispec[par]
2007
          ispec[par] = "this_is_not_good"
2008
          self.assertRaises(errors.TypeEnforcementError,
2009
                            common.GetUpdatedIPolicy,
2010
                            old_policy, invalid_policy, group_policy=isgroup)
2011
          ispec[par] = oldv
2012
    # This is to make sure that no two errors were present during the tests
2013
    common.GetUpdatedIPolicy(old_policy, invalid_policy,
2014
                             group_policy=isgroup)
2015

    
2016
  def testInvalidKeys(self):
2017
    self._TestInvalidKeys(self._OLD_GROUP_POLICY, True)
2018
    self._TestInvalidKeys(self._OLD_CLUSTER_POLICY, False)
2019

    
2020
  def testInvalidValues(self):
2021
    for par in (constants.IPOLICY_PARAMETERS |
2022
                frozenset([constants.IPOLICY_DTS])):
2023
      bad_policy = {
2024
        par: "invalid_value",
2025
        }
2026
      self.assertRaises(errors.OpPrereqError, common.GetUpdatedIPolicy, {},
2027
                        bad_policy, group_policy=True)
2028

    
2029
if __name__ == "__main__":
2030
  testutils.GanetiTestProgram()