Statistics
| Branch: | Tag: | Revision:

root / test / py / ganeti.cmdlib_unittest.py @ da4a52a3

History | View | Annotate | Download (70.8 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 unittest
27
import tempfile
28
import shutil
29
import operator
30
import itertools
31
import copy
32

    
33
from ganeti import constants
34
from ganeti import mcpu
35
from ganeti import cmdlib
36
from ganeti.cmdlib import cluster
37
from ganeti.cmdlib import group
38
from ganeti.cmdlib import instance
39
from ganeti.cmdlib import instance_storage
40
from ganeti.cmdlib import instance_utils
41
from ganeti.cmdlib import common
42
from ganeti.cmdlib import query
43
from ganeti import opcodes
44
from ganeti import errors
45
from ganeti import utils
46
from ganeti import luxi
47
from ganeti import ht
48
from ganeti import objects
49
from ganeti import compat
50
from ganeti import rpc
51
from ganeti import locking
52
from ganeti import pathutils
53
from ganeti.masterd import iallocator
54
from ganeti.hypervisor import hv_xen
55

    
56
import testutils
57
import mocks
58

    
59

    
60
class TestCertVerification(testutils.GanetiTestCase):
61
  def setUp(self):
62
    testutils.GanetiTestCase.setUp(self)
63

    
64
    self.tmpdir = tempfile.mkdtemp()
65

    
66
  def tearDown(self):
67
    shutil.rmtree(self.tmpdir)
68

    
69
  def testVerifyCertificate(self):
70
    cluster._VerifyCertificate(testutils.TestDataFilename("cert1.pem"))
71

    
72
    nonexist_filename = os.path.join(self.tmpdir, "does-not-exist")
73

    
74
    (errcode, msg) = cluster._VerifyCertificate(nonexist_filename)
75
    self.assertEqual(errcode, cluster.LUClusterVerifyConfig.ETYPE_ERROR)
76

    
77
    # Try to load non-certificate file
78
    invalid_cert = testutils.TestDataFilename("bdev-net.txt")
79
    (errcode, msg) = cluster._VerifyCertificate(invalid_cert)
80
    self.assertEqual(errcode, cluster.LUClusterVerifyConfig.ETYPE_ERROR)
81

    
82

    
83
class TestOpcodeParams(testutils.GanetiTestCase):
84
  def testParamsStructures(self):
85
    for op in sorted(mcpu.Processor.DISPATCH_TABLE):
86
      lu = mcpu.Processor.DISPATCH_TABLE[op]
87
      lu_name = lu.__name__
88
      self.failIf(hasattr(lu, "_OP_REQP"),
89
                  msg=("LU '%s' has old-style _OP_REQP" % lu_name))
90
      self.failIf(hasattr(lu, "_OP_DEFS"),
91
                  msg=("LU '%s' has old-style _OP_DEFS" % lu_name))
92
      self.failIf(hasattr(lu, "_OP_PARAMS"),
93
                  msg=("LU '%s' has old-style _OP_PARAMS" % lu_name))
94

    
95

    
96
class TestIAllocatorChecks(testutils.GanetiTestCase):
97
  def testFunction(self):
98
    class TestLU(object):
99
      def __init__(self, opcode):
100
        self.cfg = mocks.FakeConfig()
101
        self.op = opcode
102

    
103
    class OpTest(opcodes.OpCode):
104
       OP_PARAMS = [
105
        ("iallocator", None, ht.NoType, None),
106
        ("node", None, ht.NoType, None),
107
        ]
108

    
109
    default_iallocator = mocks.FakeConfig().GetDefaultIAllocator()
110
    other_iallocator = default_iallocator + "_not"
111

    
112
    op = OpTest()
113
    lu = TestLU(op)
114

    
115
    c_i = lambda: common.CheckIAllocatorOrNode(lu, "iallocator", "node")
116

    
117
    # Neither node nor iallocator given
118
    for n in (None, []):
119
      op.iallocator = None
120
      op.node = n
121
      c_i()
122
      self.assertEqual(lu.op.iallocator, default_iallocator)
123
      self.assertEqual(lu.op.node, n)
124

    
125
    # Both, iallocator and node given
126
    for a in ("test", constants.DEFAULT_IALLOCATOR_SHORTCUT):
127
      op.iallocator = a
128
      op.node = "test"
129
      self.assertRaises(errors.OpPrereqError, c_i)
130

    
131
    # Only iallocator given
132
    for n in (None, []):
133
      op.iallocator = other_iallocator
134
      op.node = n
135
      c_i()
136
      self.assertEqual(lu.op.iallocator, other_iallocator)
137
      self.assertEqual(lu.op.node, n)
138

    
139
    # Only node given
140
    op.iallocator = None
141
    op.node = "node"
142
    c_i()
143
    self.assertEqual(lu.op.iallocator, None)
144
    self.assertEqual(lu.op.node, "node")
145

    
146
    # Asked for default iallocator, no node given
147
    op.iallocator = constants.DEFAULT_IALLOCATOR_SHORTCUT
148
    op.node = None
149
    c_i()
150
    self.assertEqual(lu.op.iallocator, default_iallocator)
151
    self.assertEqual(lu.op.node, None)
152

    
153
    # No node, iallocator or default iallocator
154
    op.iallocator = None
155
    op.node = None
156
    lu.cfg.GetDefaultIAllocator = lambda: None
157
    self.assertRaises(errors.OpPrereqError, c_i)
158

    
159

    
160
class TestLUTestJqueue(unittest.TestCase):
161
  def test(self):
162
    self.assert_(cmdlib.LUTestJqueue._CLIENT_CONNECT_TIMEOUT <
163
                 (luxi.WFJC_TIMEOUT * 0.75),
164
                 msg=("Client timeout too high, might not notice bugs"
165
                      " in WaitForJobChange"))
166

    
167

    
168
class TestLUQuery(unittest.TestCase):
169
  def test(self):
170
    self.assertEqual(sorted(query._QUERY_IMPL.keys()),
171
                     sorted(constants.QR_VIA_OP))
172

    
173
    assert constants.QR_NODE in constants.QR_VIA_OP
174
    assert constants.QR_INSTANCE in constants.QR_VIA_OP
175

    
176
    for i in constants.QR_VIA_OP:
177
      self.assert_(query._GetQueryImplementation(i))
178

    
179
    self.assertRaises(errors.OpPrereqError, query._GetQueryImplementation,
180
                      "")
181
    self.assertRaises(errors.OpPrereqError, query._GetQueryImplementation,
182
                      "xyz")
183

    
184

    
185
class TestLUGroupAssignNodes(unittest.TestCase):
186

    
187
  def testCheckAssignmentForSplitInstances(self):
188
    node_data = dict((n, objects.Node(name=n, group=g))
189
                     for (n, g) in [("n1a", "g1"), ("n1b", "g1"),
190
                                    ("n2a", "g2"), ("n2b", "g2"),
191
                                    ("n3a", "g3"), ("n3b", "g3"),
192
                                    ("n3c", "g3"),
193
                                    ])
194

    
195
    def Instance(uuid, pnode, snode):
196
      if snode is None:
197
        disks = []
198
        disk_template = constants.DT_DISKLESS
199
      else:
200
        disks = [objects.Disk(dev_type=constants.LD_DRBD8,
201
                              logical_id=[pnode, snode, 1, 17, 17])]
202
        disk_template = constants.DT_DRBD8
203

    
204
      return objects.Instance(name="%s-name" % uuid, uuid="%s" % uuid,
205
                              primary_node=pnode, disks=disks,
206
                              disk_template=disk_template)
207

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

    
390
    verify_lu = cluster.LUClusterVerifyGroup(mocks.FakeProc(),
391
                                             opcodes.OpClusterVerify(),
392
                                             mocks.FakeContext(),
393
                                             None)
394

    
395
    verify_lu._ErrorIf = compat.partial(self._FakeErrorIf, errors)
396

    
397
    # TODO: That's a bit hackish to mock only this single method. We should
398
    # build a better FakeConfig which provides such a feature already.
399
    def GetNodeName(node_uuid):
400
      for node in nodeinfo:
401
        if node.uuid == node_uuid:
402
          return node.name
403
      return None
404

    
405
    verify_lu.cfg.GetNodeName = GetNodeName
406

    
407
    verify_lu._VerifyFiles(nodeinfo, "master-uuid", nvinfo,
408
                           (files_all, files_opt, files_mc, files_vm))
409
    self.assertEqual(sorted(errors), sorted([
410
      (None, ("File %s found with 2 different checksums (variant 1 on"
411
              " node2.example.com, node3.example.com, node4.example.com;"
412
              " variant 2 on master.example.com)" % pathutils.RAPI_CERT_FILE)),
413
      (None, ("File %s is missing from node(s) node2.example.com" %
414
              pathutils.CLUSTER_DOMAIN_SECRET_FILE)),
415
      (None, ("File %s should not exist on node(s) node4.example.com" %
416
              pathutils.CLUSTER_CONF_FILE)),
417
      (None, ("File %s is missing from node(s) node4.example.com" %
418
              hv_xen.XEND_CONFIG_FILE)),
419
      (None, ("File %s is missing from node(s) node3.example.com" %
420
              pathutils.CLUSTER_CONF_FILE)),
421
      (None, ("File %s found with 2 different checksums (variant 1 on"
422
              " master.example.com; variant 2 on node4.example.com)" %
423
              pathutils.CLUSTER_CONF_FILE)),
424
      (None, ("File %s is optional, but it must exist on all or no nodes (not"
425
              " found on master.example.com, node2.example.com,"
426
              " node3.example.com)" % pathutils.RAPI_USERS_FILE)),
427
      (None, ("File %s is optional, but it must exist on all or no nodes (not"
428
              " found on node2.example.com)" % hv_xen.XL_CONFIG_FILE)),
429
      ("nodata.example.com", "Node did not return file checksum data"),
430
      ]))
431

    
432

    
433
class _FakeLU:
434
  def __init__(self, cfg=NotImplemented, proc=NotImplemented,
435
               rpc=NotImplemented):
436
    self.warning_log = []
437
    self.info_log = []
438
    self.cfg = cfg
439
    self.proc = proc
440
    self.rpc = rpc
441

    
442
  def LogWarning(self, text, *args):
443
    self.warning_log.append((text, args))
444

    
445
  def LogInfo(self, text, *args):
446
    self.info_log.append((text, args))
447

    
448

    
449
class TestLoadNodeEvacResult(unittest.TestCase):
450
  def testSuccess(self):
451
    for moved in [[], [
452
      ("inst20153.example.com", "grp2", ["nodeA4509", "nodeB2912"]),
453
      ]]:
454
      for early_release in [False, True]:
455
        for use_nodes in [False, True]:
456
          jobs = [
457
            [opcodes.OpInstanceReplaceDisks().__getstate__()],
458
            [opcodes.OpInstanceMigrate().__getstate__()],
459
            ]
460

    
461
          alloc_result = (moved, [], jobs)
462
          assert iallocator._NEVAC_RESULT(alloc_result)
463

    
464
          lu = _FakeLU()
465
          result = common.LoadNodeEvacResult(lu, alloc_result,
466
                                             early_release, use_nodes)
467

    
468
          if moved:
469
            (_, (info_args, )) = lu.info_log.pop(0)
470
            for (instname, instgroup, instnodes) in moved:
471
              self.assertTrue(instname in info_args)
472
              if use_nodes:
473
                for i in instnodes:
474
                  self.assertTrue(i in info_args)
475
              else:
476
                self.assertTrue(instgroup in info_args)
477

    
478
          self.assertFalse(lu.info_log)
479
          self.assertFalse(lu.warning_log)
480

    
481
          for op in itertools.chain(*result):
482
            if hasattr(op.__class__, "early_release"):
483
              self.assertEqual(op.early_release, early_release)
484
            else:
485
              self.assertFalse(hasattr(op, "early_release"))
486

    
487
  def testFailed(self):
488
    alloc_result = ([], [
489
      ("inst5191.example.com", "errormsg21178"),
490
      ], [])
491
    assert iallocator._NEVAC_RESULT(alloc_result)
492

    
493
    lu = _FakeLU()
494
    self.assertRaises(errors.OpExecError, common.LoadNodeEvacResult,
495
                      lu, alloc_result, False, False)
496
    self.assertFalse(lu.info_log)
497
    (_, (args, )) = lu.warning_log.pop(0)
498
    self.assertTrue("inst5191.example.com" in args)
499
    self.assertTrue("errormsg21178" in args)
500
    self.assertFalse(lu.warning_log)
501

    
502

    
503
class TestUpdateAndVerifySubDict(unittest.TestCase):
504
  def setUp(self):
505
    self.type_check = {
506
        "a": constants.VTYPE_INT,
507
        "b": constants.VTYPE_STRING,
508
        "c": constants.VTYPE_BOOL,
509
        "d": constants.VTYPE_STRING,
510
        }
511

    
512
  def test(self):
513
    old_test = {
514
      "foo": {
515
        "d": "blubb",
516
        "a": 321,
517
        },
518
      "baz": {
519
        "a": 678,
520
        "b": "678",
521
        "c": True,
522
        },
523
      }
524
    test = {
525
      "foo": {
526
        "a": 123,
527
        "b": "123",
528
        "c": True,
529
        },
530
      "bar": {
531
        "a": 321,
532
        "b": "321",
533
        "c": False,
534
        },
535
      }
536

    
537
    mv = {
538
      "foo": {
539
        "a": 123,
540
        "b": "123",
541
        "c": True,
542
        "d": "blubb"
543
        },
544
      "bar": {
545
        "a": 321,
546
        "b": "321",
547
        "c": False,
548
        },
549
      "baz": {
550
        "a": 678,
551
        "b": "678",
552
        "c": True,
553
        },
554
      }
555

    
556
    verified = common._UpdateAndVerifySubDict(old_test, test, self.type_check)
557
    self.assertEqual(verified, mv)
558

    
559
  def testWrong(self):
560
    test = {
561
      "foo": {
562
        "a": "blubb",
563
        "b": "123",
564
        "c": True,
565
        },
566
      "bar": {
567
        "a": 321,
568
        "b": "321",
569
        "c": False,
570
        },
571
      }
572

    
573
    self.assertRaises(errors.TypeEnforcementError,
574
                      common._UpdateAndVerifySubDict, {}, test,
575
                      self.type_check)
576

    
577

    
578
class TestHvStateHelper(unittest.TestCase):
579
  def testWithoutOpData(self):
580
    self.assertEqual(common.MergeAndVerifyHvState(None, NotImplemented),
581
                     None)
582

    
583
  def testWithoutOldData(self):
584
    new = {
585
      constants.HT_XEN_PVM: {
586
        constants.HVST_MEMORY_TOTAL: 4096,
587
        },
588
      }
589
    self.assertEqual(common.MergeAndVerifyHvState(new, None), new)
590

    
591
  def testWithWrongHv(self):
592
    new = {
593
      "i-dont-exist": {
594
        constants.HVST_MEMORY_TOTAL: 4096,
595
        },
596
      }
597
    self.assertRaises(errors.OpPrereqError, common.MergeAndVerifyHvState,
598
                      new, None)
599

    
600
class TestDiskStateHelper(unittest.TestCase):
601
  def testWithoutOpData(self):
602
    self.assertEqual(common.MergeAndVerifyDiskState(None, NotImplemented),
603
                     None)
604

    
605
  def testWithoutOldData(self):
606
    new = {
607
      constants.LD_LV: {
608
        "xenvg": {
609
          constants.DS_DISK_RESERVED: 1024,
610
          },
611
        },
612
      }
613
    self.assertEqual(common.MergeAndVerifyDiskState(new, None), new)
614

    
615
  def testWithWrongStorageType(self):
616
    new = {
617
      "i-dont-exist": {
618
        "xenvg": {
619
          constants.DS_DISK_RESERVED: 1024,
620
          },
621
        },
622
      }
623
    self.assertRaises(errors.OpPrereqError, common.MergeAndVerifyDiskState,
624
                      new, None)
625

    
626

    
627
class TestComputeMinMaxSpec(unittest.TestCase):
628
  def setUp(self):
629
    self.ispecs = {
630
      constants.ISPECS_MAX: {
631
        constants.ISPEC_MEM_SIZE: 512,
632
        constants.ISPEC_DISK_SIZE: 1024,
633
        },
634
      constants.ISPECS_MIN: {
635
        constants.ISPEC_MEM_SIZE: 128,
636
        constants.ISPEC_DISK_COUNT: 1,
637
        },
638
      }
639

    
640
  def testNoneValue(self):
641
    self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_MEM_SIZE, None,
642
                                              self.ispecs, None) is None)
643

    
644
  def testAutoValue(self):
645
    self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_MEM_SIZE, None,
646
                                              self.ispecs,
647
                                              constants.VALUE_AUTO) is None)
648

    
649
  def testNotDefined(self):
650
    self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_NIC_COUNT, None,
651
                                              self.ispecs, 3) is None)
652

    
653
  def testNoMinDefined(self):
654
    self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_DISK_SIZE, None,
655
                                              self.ispecs, 128) is None)
656

    
657
  def testNoMaxDefined(self):
658
    self.assertTrue(common._ComputeMinMaxSpec(constants.ISPEC_DISK_COUNT,
659
                                              None, self.ispecs, 16) is None)
660

    
661
  def testOutOfRange(self):
662
    for (name, val) in ((constants.ISPEC_MEM_SIZE, 64),
663
                        (constants.ISPEC_MEM_SIZE, 768),
664
                        (constants.ISPEC_DISK_SIZE, 4096),
665
                        (constants.ISPEC_DISK_COUNT, 0)):
666
      min_v = self.ispecs[constants.ISPECS_MIN].get(name, val)
667
      max_v = self.ispecs[constants.ISPECS_MAX].get(name, val)
668
      self.assertEqual(common._ComputeMinMaxSpec(name, None,
669
                                                 self.ispecs, val),
670
                       "%s value %s is not in range [%s, %s]" %
671
                       (name, val,min_v, max_v))
672
      self.assertEqual(common._ComputeMinMaxSpec(name, "1",
673
                                                 self.ispecs, val),
674
                       "%s/1 value %s is not in range [%s, %s]" %
675
                       (name, val,min_v, max_v))
676

    
677
  def test(self):
678
    for (name, val) in ((constants.ISPEC_MEM_SIZE, 256),
679
                        (constants.ISPEC_MEM_SIZE, 128),
680
                        (constants.ISPEC_MEM_SIZE, 512),
681
                        (constants.ISPEC_DISK_SIZE, 1024),
682
                        (constants.ISPEC_DISK_SIZE, 0),
683
                        (constants.ISPEC_DISK_COUNT, 1),
684
                        (constants.ISPEC_DISK_COUNT, 5)):
685
      self.assertTrue(common._ComputeMinMaxSpec(name, None, self.ispecs, val)
686
                      is None)
687

    
688

    
689
def _ValidateComputeMinMaxSpec(name, *_):
690
  assert name in constants.ISPECS_PARAMETERS
691
  return None
692

    
693

    
694
def _NoDiskComputeMinMaxSpec(name, *_):
695
  if name == constants.ISPEC_DISK_COUNT:
696
    return name
697
  else:
698
    return None
699

    
700

    
701
class _SpecWrapper:
702
  def __init__(self, spec):
703
    self.spec = spec
704

    
705
  def ComputeMinMaxSpec(self, *args):
706
    return self.spec.pop(0)
707

    
708

    
709
class TestComputeIPolicySpecViolation(unittest.TestCase):
710
  # Minimal policy accepted by _ComputeIPolicySpecViolation()
711
  _MICRO_IPOL = {
712
    constants.IPOLICY_DTS: [constants.DT_PLAIN, constants.DT_DISKLESS],
713
    constants.ISPECS_MINMAX: [NotImplemented],
714
    }
715

    
716
  def test(self):
717
    compute_fn = _ValidateComputeMinMaxSpec
718
    ret = common.ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1,
719
                                             [1024], 1, constants.DT_PLAIN,
720
                                             _compute_fn=compute_fn)
721
    self.assertEqual(ret, [])
722

    
723
  def testDiskFull(self):
724
    compute_fn = _NoDiskComputeMinMaxSpec
725
    ret = common.ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1,
726
                                             [1024], 1, constants.DT_PLAIN,
727
                                             _compute_fn=compute_fn)
728
    self.assertEqual(ret, [constants.ISPEC_DISK_COUNT])
729

    
730
  def testDiskLess(self):
731
    compute_fn = _NoDiskComputeMinMaxSpec
732
    ret = common.ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1,
733
                                             [1024], 1, constants.DT_DISKLESS,
734
                                             _compute_fn=compute_fn)
735
    self.assertEqual(ret, [])
736

    
737
  def testWrongTemplates(self):
738
    compute_fn = _ValidateComputeMinMaxSpec
739
    ret = common.ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1,
740
                                             [1024], 1, constants.DT_DRBD8,
741
                                             _compute_fn=compute_fn)
742
    self.assertEqual(len(ret), 1)
743
    self.assertTrue("Disk template" in ret[0])
744

    
745
  def testInvalidArguments(self):
746
    self.assertRaises(AssertionError, common.ComputeIPolicySpecViolation,
747
                      self._MICRO_IPOL, 1024, 1, 1, 1, [], 1,
748
                      constants.DT_PLAIN,)
749

    
750
  def testInvalidSpec(self):
751
    spec = _SpecWrapper([None, False, "foo", None, "bar", None])
752
    compute_fn = spec.ComputeMinMaxSpec
753
    ret = common.ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1,
754
                                             [1024], 1, constants.DT_PLAIN,
755
                                             _compute_fn=compute_fn)
756
    self.assertEqual(ret, ["foo", "bar"])
757
    self.assertFalse(spec.spec)
758

    
759
  def testWithIPolicy(self):
760
    mem_size = 2048
761
    cpu_count = 2
762
    disk_count = 1
763
    disk_sizes = [512]
764
    nic_count = 1
765
    spindle_use = 4
766
    disk_template = "mytemplate"
767
    ispec = {
768
      constants.ISPEC_MEM_SIZE: mem_size,
769
      constants.ISPEC_CPU_COUNT: cpu_count,
770
      constants.ISPEC_DISK_COUNT: disk_count,
771
      constants.ISPEC_DISK_SIZE: disk_sizes[0],
772
      constants.ISPEC_NIC_COUNT: nic_count,
773
      constants.ISPEC_SPINDLE_USE: spindle_use,
774
      }
775
    ipolicy1 = {
776
      constants.ISPECS_MINMAX: [{
777
        constants.ISPECS_MIN: ispec,
778
        constants.ISPECS_MAX: ispec,
779
        }],
780
      constants.IPOLICY_DTS: [disk_template],
781
      }
782
    ispec_copy = copy.deepcopy(ispec)
783
    ipolicy2 = {
784
      constants.ISPECS_MINMAX: [
785
        {
786
          constants.ISPECS_MIN: ispec_copy,
787
          constants.ISPECS_MAX: ispec_copy,
788
          },
789
        {
790
          constants.ISPECS_MIN: ispec,
791
          constants.ISPECS_MAX: ispec,
792
          },
793
        ],
794
      constants.IPOLICY_DTS: [disk_template],
795
      }
796
    ipolicy3 = {
797
      constants.ISPECS_MINMAX: [
798
        {
799
          constants.ISPECS_MIN: ispec,
800
          constants.ISPECS_MAX: ispec,
801
          },
802
        {
803
          constants.ISPECS_MIN: ispec_copy,
804
          constants.ISPECS_MAX: ispec_copy,
805
          },
806
        ],
807
      constants.IPOLICY_DTS: [disk_template],
808
      }
809
    def AssertComputeViolation(ipolicy, violations):
810
      ret = common.ComputeIPolicySpecViolation(ipolicy, mem_size, cpu_count,
811
                                               disk_count, nic_count,
812
                                               disk_sizes, spindle_use,
813
                                               disk_template)
814
      self.assertEqual(len(ret), violations)
815

    
816
    AssertComputeViolation(ipolicy1, 0)
817
    AssertComputeViolation(ipolicy2, 0)
818
    AssertComputeViolation(ipolicy3, 0)
819
    for par in constants.ISPECS_PARAMETERS:
820
      ispec[par] += 1
821
      AssertComputeViolation(ipolicy1, 1)
822
      AssertComputeViolation(ipolicy2, 0)
823
      AssertComputeViolation(ipolicy3, 0)
824
      ispec[par] -= 2
825
      AssertComputeViolation(ipolicy1, 1)
826
      AssertComputeViolation(ipolicy2, 0)
827
      AssertComputeViolation(ipolicy3, 0)
828
      ispec[par] += 1 # Restore
829
    ipolicy1[constants.IPOLICY_DTS] = ["another_template"]
830
    AssertComputeViolation(ipolicy1, 1)
831

    
832

    
833
class _StubComputeIPolicySpecViolation:
834
  def __init__(self, mem_size, cpu_count, disk_count, nic_count, disk_sizes,
835
               spindle_use, disk_template):
836
    self.mem_size = mem_size
837
    self.cpu_count = cpu_count
838
    self.disk_count = disk_count
839
    self.nic_count = nic_count
840
    self.disk_sizes = disk_sizes
841
    self.spindle_use = spindle_use
842
    self.disk_template = disk_template
843

    
844
  def __call__(self, _, mem_size, cpu_count, disk_count, nic_count, disk_sizes,
845
               spindle_use, disk_template):
846
    assert self.mem_size == mem_size
847
    assert self.cpu_count == cpu_count
848
    assert self.disk_count == disk_count
849
    assert self.nic_count == nic_count
850
    assert self.disk_sizes == disk_sizes
851
    assert self.spindle_use == spindle_use
852
    assert self.disk_template == disk_template
853

    
854
    return []
855

    
856

    
857
class _FakeConfigForComputeIPolicyInstanceViolation:
858
  def __init__(self, be, excl_stor):
859
    self.cluster = objects.Cluster(beparams={"default": be})
860
    self.excl_stor = excl_stor
861

    
862
  def GetClusterInfo(self):
863
    return self.cluster
864

    
865
  def GetNodeInfo(self, _):
866
    return {}
867

    
868
  def GetNdParams(self, _):
869
    return {
870
      constants.ND_EXCLUSIVE_STORAGE: self.excl_stor,
871
      }
872

    
873

    
874
class TestComputeIPolicyInstanceViolation(unittest.TestCase):
875
  def test(self):
876
    beparams = {
877
      constants.BE_MAXMEM: 2048,
878
      constants.BE_VCPUS: 2,
879
      constants.BE_SPINDLE_USE: 4,
880
      }
881
    disks = [objects.Disk(size=512, spindles=13)]
882
    cfg = _FakeConfigForComputeIPolicyInstanceViolation(beparams, False)
883
    instance = objects.Instance(beparams=beparams, disks=disks, nics=[],
884
                                disk_template=constants.DT_PLAIN)
885
    stub = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512], 4,
886
                                            constants.DT_PLAIN)
887
    ret = common.ComputeIPolicyInstanceViolation(NotImplemented, instance,
888
                                                 cfg, _compute_fn=stub)
889
    self.assertEqual(ret, [])
890
    instance2 = objects.Instance(beparams={}, disks=disks, nics=[],
891
                                 disk_template=constants.DT_PLAIN)
892
    ret = common.ComputeIPolicyInstanceViolation(NotImplemented, instance2,
893
                                                 cfg, _compute_fn=stub)
894
    self.assertEqual(ret, [])
895
    cfg_es = _FakeConfigForComputeIPolicyInstanceViolation(beparams, True)
896
    stub_es = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512], 13,
897
                                               constants.DT_PLAIN)
898
    ret = common.ComputeIPolicyInstanceViolation(NotImplemented, instance,
899
                                                 cfg_es, _compute_fn=stub_es)
900
    self.assertEqual(ret, [])
901
    ret = common.ComputeIPolicyInstanceViolation(NotImplemented, instance2,
902
                                                 cfg_es, _compute_fn=stub_es)
903
    self.assertEqual(ret, [])
904

    
905

    
906
class TestComputeIPolicyInstanceSpecViolation(unittest.TestCase):
907
  def test(self):
908
    ispec = {
909
      constants.ISPEC_MEM_SIZE: 2048,
910
      constants.ISPEC_CPU_COUNT: 2,
911
      constants.ISPEC_DISK_COUNT: 1,
912
      constants.ISPEC_DISK_SIZE: [512],
913
      constants.ISPEC_NIC_COUNT: 0,
914
      constants.ISPEC_SPINDLE_USE: 1,
915
      }
916
    stub = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512], 1,
917
                                            constants.DT_PLAIN)
918
    ret = instance._ComputeIPolicyInstanceSpecViolation(NotImplemented, ispec,
919
                                                        constants.DT_PLAIN,
920
                                                        _compute_fn=stub)
921
    self.assertEqual(ret, [])
922

    
923

    
924
class _CallRecorder:
925
  def __init__(self, return_value=None):
926
    self.called = False
927
    self.return_value = return_value
928

    
929
  def __call__(self, *args):
930
    self.called = True
931
    return self.return_value
932

    
933

    
934
class TestComputeIPolicyNodeViolation(unittest.TestCase):
935
  def setUp(self):
936
    self.recorder = _CallRecorder(return_value=[])
937

    
938
  def testSameGroup(self):
939
    ret = instance_utils._ComputeIPolicyNodeViolation(
940
      NotImplemented,
941
      NotImplemented,
942
      "foo", "foo", NotImplemented,
943
      _compute_fn=self.recorder)
944
    self.assertFalse(self.recorder.called)
945
    self.assertEqual(ret, [])
946

    
947
  def testDifferentGroup(self):
948
    ret = instance_utils._ComputeIPolicyNodeViolation(
949
      NotImplemented,
950
      NotImplemented,
951
      "foo", "bar", NotImplemented,
952
      _compute_fn=self.recorder)
953
    self.assertTrue(self.recorder.called)
954
    self.assertEqual(ret, [])
955

    
956

    
957
class _FakeConfigForTargetNodeIPolicy:
958
  def __init__(self, node_info=NotImplemented):
959
    self._node_info = node_info
960

    
961
  def GetNodeInfo(self, _):
962
    return self._node_info
963

    
964

    
965
class TestCheckTargetNodeIPolicy(unittest.TestCase):
966
  def setUp(self):
967
    self.instance = objects.Instance(primary_node="blubb")
968
    self.target_node = objects.Node(group="bar")
969
    node_info = objects.Node(group="foo")
970
    fake_cfg = _FakeConfigForTargetNodeIPolicy(node_info=node_info)
971
    self.lu = _FakeLU(cfg=fake_cfg)
972

    
973
  def testNoViolation(self):
974
    compute_recoder = _CallRecorder(return_value=[])
975
    instance.CheckTargetNodeIPolicy(self.lu, NotImplemented, self.instance,
976
                                    self.target_node, NotImplemented,
977
                                    _compute_fn=compute_recoder)
978
    self.assertTrue(compute_recoder.called)
979
    self.assertEqual(self.lu.warning_log, [])
980

    
981
  def testNoIgnore(self):
982
    compute_recoder = _CallRecorder(return_value=["mem_size not in range"])
983
    self.assertRaises(errors.OpPrereqError, instance.CheckTargetNodeIPolicy,
984
                      self.lu, NotImplemented, self.instance,
985
                      self.target_node, NotImplemented,
986
                      _compute_fn=compute_recoder)
987
    self.assertTrue(compute_recoder.called)
988
    self.assertEqual(self.lu.warning_log, [])
989

    
990
  def testIgnoreViolation(self):
991
    compute_recoder = _CallRecorder(return_value=["mem_size not in range"])
992
    instance.CheckTargetNodeIPolicy(self.lu, NotImplemented, self.instance,
993
                                     self.target_node, NotImplemented,
994
                                     ignore=True, _compute_fn=compute_recoder)
995
    self.assertTrue(compute_recoder.called)
996
    msg = ("Instance does not meet target node group's (bar) instance policy:"
997
           " mem_size not in range")
998
    self.assertEqual(self.lu.warning_log, [(msg, ())])
999

    
1000

    
1001
class TestApplyContainerMods(unittest.TestCase):
1002
  def testEmptyContainer(self):
1003
    container = []
1004
    chgdesc = []
1005
    instance._ApplyContainerMods("test", container, chgdesc, [], None, None,
1006
                                None)
1007
    self.assertEqual(container, [])
1008
    self.assertEqual(chgdesc, [])
1009

    
1010
  def testAdd(self):
1011
    container = []
1012
    chgdesc = []
1013
    mods = instance._PrepareContainerMods([
1014
      (constants.DDM_ADD, -1, "Hello"),
1015
      (constants.DDM_ADD, -1, "World"),
1016
      (constants.DDM_ADD, 0, "Start"),
1017
      (constants.DDM_ADD, -1, "End"),
1018
      ], None)
1019
    instance._ApplyContainerMods("test", container, chgdesc, mods,
1020
                                None, None, None)
1021
    self.assertEqual(container, ["Start", "Hello", "World", "End"])
1022
    self.assertEqual(chgdesc, [])
1023

    
1024
    mods = instance._PrepareContainerMods([
1025
      (constants.DDM_ADD, 0, "zero"),
1026
      (constants.DDM_ADD, 3, "Added"),
1027
      (constants.DDM_ADD, 5, "four"),
1028
      (constants.DDM_ADD, 7, "xyz"),
1029
      ], None)
1030
    instance._ApplyContainerMods("test", container, chgdesc, mods,
1031
                                None, None, None)
1032
    self.assertEqual(container,
1033
                     ["zero", "Start", "Hello", "Added", "World", "four",
1034
                      "End", "xyz"])
1035
    self.assertEqual(chgdesc, [])
1036

    
1037
    for idx in [-2, len(container) + 1]:
1038
      mods = instance._PrepareContainerMods([
1039
        (constants.DDM_ADD, idx, "error"),
1040
        ], None)
1041
      self.assertRaises(IndexError, instance._ApplyContainerMods,
1042
                        "test", container, None, mods, None, None, None)
1043

    
1044
  def testRemoveError(self):
1045
    for idx in [0, 1, 2, 100, -1, -4]:
1046
      mods = instance._PrepareContainerMods([
1047
        (constants.DDM_REMOVE, idx, None),
1048
        ], None)
1049
      self.assertRaises(IndexError, instance._ApplyContainerMods,
1050
                        "test", [], None, mods, None, None, None)
1051

    
1052
    mods = instance._PrepareContainerMods([
1053
      (constants.DDM_REMOVE, 0, object()),
1054
      ], None)
1055
    self.assertRaises(AssertionError, instance._ApplyContainerMods,
1056
                      "test", [""], None, mods, None, None, None)
1057

    
1058
  def testAddError(self):
1059
    for idx in range(-100, -1) + [100]:
1060
      mods = instance._PrepareContainerMods([
1061
        (constants.DDM_ADD, idx, None),
1062
        ], None)
1063
      self.assertRaises(IndexError, instance._ApplyContainerMods,
1064
                        "test", [], None, mods, None, None, None)
1065

    
1066
  def testRemove(self):
1067
    container = ["item 1", "item 2"]
1068
    mods = instance._PrepareContainerMods([
1069
      (constants.DDM_ADD, -1, "aaa"),
1070
      (constants.DDM_REMOVE, -1, None),
1071
      (constants.DDM_ADD, -1, "bbb"),
1072
      ], None)
1073
    chgdesc = []
1074
    instance._ApplyContainerMods("test", container, chgdesc, mods,
1075
                                None, None, None)
1076
    self.assertEqual(container, ["item 1", "item 2", "bbb"])
1077
    self.assertEqual(chgdesc, [
1078
      ("test/2", "remove"),
1079
      ])
1080

    
1081
  def testModify(self):
1082
    container = ["item 1", "item 2"]
1083
    mods = instance._PrepareContainerMods([
1084
      (constants.DDM_MODIFY, -1, "a"),
1085
      (constants.DDM_MODIFY, 0, "b"),
1086
      (constants.DDM_MODIFY, 1, "c"),
1087
      ], None)
1088
    chgdesc = []
1089
    instance._ApplyContainerMods("test", container, chgdesc, mods,
1090
                                None, None, None)
1091
    self.assertEqual(container, ["item 1", "item 2"])
1092
    self.assertEqual(chgdesc, [])
1093

    
1094
    for idx in [-2, len(container) + 1]:
1095
      mods = instance._PrepareContainerMods([
1096
        (constants.DDM_MODIFY, idx, "error"),
1097
        ], None)
1098
      self.assertRaises(IndexError, instance._ApplyContainerMods,
1099
                        "test", container, None, mods, None, None, None)
1100

    
1101
  class _PrivateData:
1102
    def __init__(self):
1103
      self.data = None
1104

    
1105
  @staticmethod
1106
  def _CreateTestFn(idx, params, private):
1107
    private.data = ("add", idx, params)
1108
    return ((100 * idx, params), [
1109
      ("test/%s" % idx, hex(idx)),
1110
      ])
1111

    
1112
  @staticmethod
1113
  def _ModifyTestFn(idx, item, params, private):
1114
    private.data = ("modify", idx, params)
1115
    return [
1116
      ("test/%s" % idx, "modify %s" % params),
1117
      ]
1118

    
1119
  @staticmethod
1120
  def _RemoveTestFn(idx, item, private):
1121
    private.data = ("remove", idx, item)
1122

    
1123
  def testAddWithCreateFunction(self):
1124
    container = []
1125
    chgdesc = []
1126
    mods = instance._PrepareContainerMods([
1127
      (constants.DDM_ADD, -1, "Hello"),
1128
      (constants.DDM_ADD, -1, "World"),
1129
      (constants.DDM_ADD, 0, "Start"),
1130
      (constants.DDM_ADD, -1, "End"),
1131
      (constants.DDM_REMOVE, 2, None),
1132
      (constants.DDM_MODIFY, -1, "foobar"),
1133
      (constants.DDM_REMOVE, 2, None),
1134
      (constants.DDM_ADD, 1, "More"),
1135
      ], self._PrivateData)
1136
    instance._ApplyContainerMods("test", container, chgdesc, mods,
1137
                                self._CreateTestFn, self._ModifyTestFn,
1138
                                self._RemoveTestFn)
1139
    self.assertEqual(container, [
1140
      (000, "Start"),
1141
      (100, "More"),
1142
      (000, "Hello"),
1143
      ])
1144
    self.assertEqual(chgdesc, [
1145
      ("test/0", "0x0"),
1146
      ("test/1", "0x1"),
1147
      ("test/0", "0x0"),
1148
      ("test/3", "0x3"),
1149
      ("test/2", "remove"),
1150
      ("test/2", "modify foobar"),
1151
      ("test/2", "remove"),
1152
      ("test/1", "0x1")
1153
      ])
1154
    self.assertTrue(compat.all(op == private.data[0]
1155
                               for (op, _, _, private) in mods))
1156
    self.assertEqual([private.data for (op, _, _, private) in mods], [
1157
      ("add", 0, "Hello"),
1158
      ("add", 1, "World"),
1159
      ("add", 0, "Start"),
1160
      ("add", 3, "End"),
1161
      ("remove", 2, (100, "World")),
1162
      ("modify", 2, "foobar"),
1163
      ("remove", 2, (300, "End")),
1164
      ("add", 1, "More"),
1165
      ])
1166

    
1167

    
1168
class _FakeConfigForGenDiskTemplate:
1169
  def __init__(self):
1170
    self._unique_id = itertools.count()
1171
    self._drbd_minor = itertools.count(20)
1172
    self._port = itertools.count(constants.FIRST_DRBD_PORT)
1173
    self._secret = itertools.count()
1174

    
1175
  def GetVGName(self):
1176
    return "testvg"
1177

    
1178
  def GenerateUniqueID(self, ec_id):
1179
    return "ec%s-uq%s" % (ec_id, self._unique_id.next())
1180

    
1181
  def AllocateDRBDMinor(self, nodes, instance):
1182
    return [self._drbd_minor.next()
1183
            for _ in nodes]
1184

    
1185
  def AllocatePort(self):
1186
    return self._port.next()
1187

    
1188
  def GenerateDRBDSecret(self, ec_id):
1189
    return "ec%s-secret%s" % (ec_id, self._secret.next())
1190

    
1191
  def GetInstanceInfo(self, _):
1192
    return "foobar"
1193

    
1194

    
1195
class _FakeProcForGenDiskTemplate:
1196
  def GetECId(self):
1197
    return 0
1198

    
1199

    
1200
class TestGenerateDiskTemplate(unittest.TestCase):
1201
  def setUp(self):
1202
    nodegroup = objects.NodeGroup(name="ng")
1203
    nodegroup.UpgradeConfig()
1204

    
1205
    cfg = _FakeConfigForGenDiskTemplate()
1206
    proc = _FakeProcForGenDiskTemplate()
1207

    
1208
    self.lu = _FakeLU(cfg=cfg, proc=proc)
1209
    self.nodegroup = nodegroup
1210

    
1211
  @staticmethod
1212
  def GetDiskParams():
1213
    return copy.deepcopy(constants.DISK_DT_DEFAULTS)
1214

    
1215
  def testWrongDiskTemplate(self):
1216
    gdt = instance.GenerateDiskTemplate
1217
    disk_template = "##unknown##"
1218

    
1219
    assert disk_template not in constants.DISK_TEMPLATES
1220

    
1221
    self.assertRaises(errors.ProgrammerError, gdt, self.lu, disk_template,
1222
                      "inst26831.example.com", "node30113.example.com", [], [],
1223
                      NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1224
                      self.GetDiskParams())
1225

    
1226
  def testDiskless(self):
1227
    gdt = instance.GenerateDiskTemplate
1228

    
1229
    result = gdt(self.lu, constants.DT_DISKLESS, "inst27734.example.com",
1230
                 "node30113.example.com", [], [],
1231
                 NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1232
                 self.GetDiskParams())
1233
    self.assertEqual(result, [])
1234

    
1235
  def _TestTrivialDisk(self, template, disk_info, base_index, exp_dev_type,
1236
                       file_storage_dir=NotImplemented,
1237
                       file_driver=NotImplemented,
1238
                       req_file_storage=NotImplemented,
1239
                       req_shr_file_storage=NotImplemented):
1240
    gdt = instance.GenerateDiskTemplate
1241

    
1242
    map(lambda params: utils.ForceDictType(params,
1243
                                           constants.IDISK_PARAMS_TYPES),
1244
        disk_info)
1245

    
1246
    # Check if non-empty list of secondaries is rejected
1247
    self.assertRaises(errors.ProgrammerError, gdt, self.lu,
1248
                      template, "inst25088.example.com",
1249
                      "node185.example.com", ["node323.example.com"], [],
1250
                      NotImplemented, NotImplemented, base_index,
1251
                      self.lu.LogInfo, self.GetDiskParams(),
1252
                      _req_file_storage=req_file_storage,
1253
                      _req_shr_file_storage=req_shr_file_storage)
1254

    
1255
    result = gdt(self.lu, template, "inst21662.example.com",
1256
                 "node21741.example.com", [],
1257
                 disk_info, file_storage_dir, file_driver, base_index,
1258
                 self.lu.LogInfo, self.GetDiskParams(),
1259
                 _req_file_storage=req_file_storage,
1260
                 _req_shr_file_storage=req_shr_file_storage)
1261

    
1262
    for (idx, disk) in enumerate(result):
1263
      self.assertTrue(isinstance(disk, objects.Disk))
1264
      self.assertEqual(disk.dev_type, exp_dev_type)
1265
      self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
1266
      self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
1267
      self.assertTrue(disk.children is None)
1268

    
1269
    self._CheckIvNames(result, base_index, base_index + len(disk_info))
1270
    instance._UpdateIvNames(base_index, result)
1271
    self._CheckIvNames(result, base_index, base_index + len(disk_info))
1272

    
1273
    return result
1274

    
1275
  def _CheckIvNames(self, disks, base_index, end_index):
1276
    self.assertEqual(map(operator.attrgetter("iv_name"), disks),
1277
                     ["disk/%s" % i for i in range(base_index, end_index)])
1278

    
1279
  def testPlain(self):
1280
    disk_info = [{
1281
      constants.IDISK_SIZE: 1024,
1282
      constants.IDISK_MODE: constants.DISK_RDWR,
1283
      }, {
1284
      constants.IDISK_SIZE: 4096,
1285
      constants.IDISK_VG: "othervg",
1286
      constants.IDISK_MODE: constants.DISK_RDWR,
1287
      }]
1288

    
1289
    result = self._TestTrivialDisk(constants.DT_PLAIN, disk_info, 3,
1290
                                   constants.LD_LV)
1291

    
1292
    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1293
      ("testvg", "ec0-uq0.disk3"),
1294
      ("othervg", "ec0-uq1.disk4"),
1295
      ])
1296

    
1297
  @staticmethod
1298
  def _AllowFileStorage():
1299
    pass
1300

    
1301
  @staticmethod
1302
  def _ForbidFileStorage():
1303
    raise errors.OpPrereqError("Disallowed in test")
1304

    
1305
  def testFile(self):
1306
    self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
1307
                      constants.DT_FILE, [], 0, NotImplemented,
1308
                      req_file_storage=self._ForbidFileStorage)
1309
    self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
1310
                      constants.DT_SHARED_FILE, [], 0, NotImplemented,
1311
                      req_shr_file_storage=self._ForbidFileStorage)
1312

    
1313
    for disk_template in [constants.DT_FILE, constants.DT_SHARED_FILE]:
1314
      disk_info = [{
1315
        constants.IDISK_SIZE: 80 * 1024,
1316
        constants.IDISK_MODE: constants.DISK_RDONLY,
1317
        }, {
1318
        constants.IDISK_SIZE: 4096,
1319
        constants.IDISK_MODE: constants.DISK_RDWR,
1320
        }, {
1321
        constants.IDISK_SIZE: 6 * 1024,
1322
        constants.IDISK_MODE: constants.DISK_RDWR,
1323
        }]
1324

    
1325
      result = self._TestTrivialDisk(disk_template, disk_info, 2,
1326
        constants.LD_FILE, file_storage_dir="/tmp",
1327
        file_driver=constants.FD_BLKTAP,
1328
        req_file_storage=self._AllowFileStorage,
1329
        req_shr_file_storage=self._AllowFileStorage)
1330

    
1331
      self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1332
        (constants.FD_BLKTAP, "/tmp/disk2"),
1333
        (constants.FD_BLKTAP, "/tmp/disk3"),
1334
        (constants.FD_BLKTAP, "/tmp/disk4"),
1335
        ])
1336

    
1337
  def testBlock(self):
1338
    disk_info = [{
1339
      constants.IDISK_SIZE: 8 * 1024,
1340
      constants.IDISK_MODE: constants.DISK_RDWR,
1341
      constants.IDISK_ADOPT: "/tmp/some/block/dev",
1342
      }]
1343

    
1344
    result = self._TestTrivialDisk(constants.DT_BLOCK, disk_info, 10,
1345
                                   constants.LD_BLOCKDEV)
1346

    
1347
    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1348
      (constants.BLOCKDEV_DRIVER_MANUAL, "/tmp/some/block/dev"),
1349
      ])
1350

    
1351
  def testRbd(self):
1352
    disk_info = [{
1353
      constants.IDISK_SIZE: 8 * 1024,
1354
      constants.IDISK_MODE: constants.DISK_RDONLY,
1355
      }, {
1356
      constants.IDISK_SIZE: 100 * 1024,
1357
      constants.IDISK_MODE: constants.DISK_RDWR,
1358
      }]
1359

    
1360
    result = self._TestTrivialDisk(constants.DT_RBD, disk_info, 0,
1361
                                   constants.LD_RBD)
1362

    
1363
    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1364
      ("rbd", "ec0-uq0.rbd.disk0"),
1365
      ("rbd", "ec0-uq1.rbd.disk1"),
1366
      ])
1367

    
1368
  def testDrbd8(self):
1369
    gdt = instance.GenerateDiskTemplate
1370
    drbd8_defaults = constants.DISK_LD_DEFAULTS[constants.LD_DRBD8]
1371
    drbd8_default_metavg = drbd8_defaults[constants.LDP_DEFAULT_METAVG]
1372

    
1373
    disk_info = [{
1374
      constants.IDISK_SIZE: 1024,
1375
      constants.IDISK_MODE: constants.DISK_RDWR,
1376
      }, {
1377
      constants.IDISK_SIZE: 100 * 1024,
1378
      constants.IDISK_MODE: constants.DISK_RDONLY,
1379
      constants.IDISK_METAVG: "metavg",
1380
      }, {
1381
      constants.IDISK_SIZE: 4096,
1382
      constants.IDISK_MODE: constants.DISK_RDWR,
1383
      constants.IDISK_VG: "vgxyz",
1384
      },
1385
      ]
1386

    
1387
    exp_logical_ids = [[
1388
      (self.lu.cfg.GetVGName(), "ec0-uq0.disk0_data"),
1389
      (drbd8_default_metavg, "ec0-uq0.disk0_meta"),
1390
      ], [
1391
      (self.lu.cfg.GetVGName(), "ec0-uq1.disk1_data"),
1392
      ("metavg", "ec0-uq1.disk1_meta"),
1393
      ], [
1394
      ("vgxyz", "ec0-uq2.disk2_data"),
1395
      (drbd8_default_metavg, "ec0-uq2.disk2_meta"),
1396
      ]]
1397

    
1398
    assert len(exp_logical_ids) == len(disk_info)
1399

    
1400
    map(lambda params: utils.ForceDictType(params,
1401
                                           constants.IDISK_PARAMS_TYPES),
1402
        disk_info)
1403

    
1404
    # Check if empty list of secondaries is rejected
1405
    self.assertRaises(errors.ProgrammerError, gdt, self.lu, constants.DT_DRBD8,
1406
                      "inst827.example.com", "node1334.example.com", [],
1407
                      disk_info, NotImplemented, NotImplemented, 0,
1408
                      self.lu.LogInfo, self.GetDiskParams())
1409

    
1410
    result = gdt(self.lu, constants.DT_DRBD8, "inst827.example.com",
1411
                 "node1334.example.com", ["node12272.example.com"],
1412
                 disk_info, NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1413
                 self.GetDiskParams())
1414

    
1415
    for (idx, disk) in enumerate(result):
1416
      self.assertTrue(isinstance(disk, objects.Disk))
1417
      self.assertEqual(disk.dev_type, constants.LD_DRBD8)
1418
      self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
1419
      self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
1420

    
1421
      for child in disk.children:
1422
        self.assertTrue(isinstance(disk, objects.Disk))
1423
        self.assertEqual(child.dev_type, constants.LD_LV)
1424
        self.assertTrue(child.children is None)
1425

    
1426
      self.assertEqual(map(operator.attrgetter("logical_id"), disk.children),
1427
                       exp_logical_ids[idx])
1428

    
1429
      self.assertEqual(len(disk.children), 2)
1430
      self.assertEqual(disk.children[0].size, disk.size)
1431
      self.assertEqual(disk.children[1].size, constants.DRBD_META_SIZE)
1432

    
1433
    self._CheckIvNames(result, 0, len(disk_info))
1434
    instance._UpdateIvNames(0, result)
1435
    self._CheckIvNames(result, 0, len(disk_info))
1436

    
1437
    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1438
      ("node1334.example.com", "node12272.example.com",
1439
       constants.FIRST_DRBD_PORT, 20, 21, "ec0-secret0"),
1440
      ("node1334.example.com", "node12272.example.com",
1441
       constants.FIRST_DRBD_PORT + 1, 22, 23, "ec0-secret1"),
1442
      ("node1334.example.com", "node12272.example.com",
1443
       constants.FIRST_DRBD_PORT + 2, 24, 25, "ec0-secret2"),
1444
      ])
1445

    
1446

    
1447
class _ConfigForDiskWipe:
1448
  def __init__(self, exp_node_uuid):
1449
    self._exp_node_uuid = exp_node_uuid
1450

    
1451
  def SetDiskID(self, device, node_uuid):
1452
    assert isinstance(device, objects.Disk)
1453
    assert node_uuid == self._exp_node_uuid
1454

    
1455
  def GetNodeName(self, node_uuid):
1456
    assert node_uuid == self._exp_node_uuid
1457
    return "name.of.expected.node"
1458

    
1459

    
1460
class _RpcForDiskWipe:
1461
  def __init__(self, exp_node, pause_cb, wipe_cb):
1462
    self._exp_node = exp_node
1463
    self._pause_cb = pause_cb
1464
    self._wipe_cb = wipe_cb
1465

    
1466
  def call_blockdev_pause_resume_sync(self, node, disks, pause):
1467
    assert node == self._exp_node
1468
    return rpc.RpcResult(data=self._pause_cb(disks, pause))
1469

    
1470
  def call_blockdev_wipe(self, node, bdev, offset, size):
1471
    assert node == self._exp_node
1472
    return rpc.RpcResult(data=self._wipe_cb(bdev, offset, size))
1473

    
1474

    
1475
class _DiskPauseTracker:
1476
  def __init__(self):
1477
    self.history = []
1478

    
1479
  def __call__(self, (disks, instance), pause):
1480
    assert not (set(disks) - set(instance.disks))
1481

    
1482
    self.history.extend((i.logical_id, i.size, pause)
1483
                        for i in disks)
1484

    
1485
    return (True, [True] * len(disks))
1486

    
1487

    
1488
class _DiskWipeProgressTracker:
1489
  def __init__(self, start_offset):
1490
    self._start_offset = start_offset
1491
    self.progress = {}
1492

    
1493
  def __call__(self, (disk, _), offset, size):
1494
    assert isinstance(offset, (long, int))
1495
    assert isinstance(size, (long, int))
1496

    
1497
    max_chunk_size = (disk.size / 100.0 * constants.MIN_WIPE_CHUNK_PERCENT)
1498

    
1499
    assert offset >= self._start_offset
1500
    assert (offset + size) <= disk.size
1501

    
1502
    assert size > 0
1503
    assert size <= constants.MAX_WIPE_CHUNK
1504
    assert size <= max_chunk_size
1505

    
1506
    assert offset == self._start_offset or disk.logical_id in self.progress
1507

    
1508
    # Keep track of progress
1509
    cur_progress = self.progress.setdefault(disk.logical_id, self._start_offset)
1510

    
1511
    assert cur_progress == offset
1512

    
1513
    # Record progress
1514
    self.progress[disk.logical_id] += size
1515

    
1516
    return (True, None)
1517

    
1518

    
1519
class TestWipeDisks(unittest.TestCase):
1520
  def _FailingPauseCb(self, (disks, _), pause):
1521
    self.assertEqual(len(disks), 3)
1522
    self.assertTrue(pause)
1523
    # Simulate an RPC error
1524
    return (False, "error")
1525

    
1526
  def testPauseFailure(self):
1527
    node_name = "node1372.example.com"
1528

    
1529
    lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, self._FailingPauseCb,
1530
                                     NotImplemented),
1531
                 cfg=_ConfigForDiskWipe(node_name))
1532

    
1533
    disks = [
1534
      objects.Disk(dev_type=constants.LD_LV),
1535
      objects.Disk(dev_type=constants.LD_LV),
1536
      objects.Disk(dev_type=constants.LD_LV),
1537
      ]
1538

    
1539
    inst = objects.Instance(name="inst21201",
1540
                            primary_node=node_name,
1541
                            disk_template=constants.DT_PLAIN,
1542
                            disks=disks)
1543

    
1544
    self.assertRaises(errors.OpExecError, instance.WipeDisks, lu, inst)
1545

    
1546
  def _FailingWipeCb(self, (disk, _), offset, size):
1547
    # This should only ever be called for the first disk
1548
    self.assertEqual(disk.logical_id, "disk0")
1549
    return (False, None)
1550

    
1551
  def testFailingWipe(self):
1552
    node_uuid = "node13445-uuid"
1553
    pt = _DiskPauseTracker()
1554

    
1555
    lu = _FakeLU(rpc=_RpcForDiskWipe(node_uuid, pt, self._FailingWipeCb),
1556
                 cfg=_ConfigForDiskWipe(node_uuid))
1557

    
1558
    disks = [
1559
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk0",
1560
                   size=100 * 1024),
1561
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk1",
1562
                   size=500 * 1024),
1563
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk2", size=256),
1564
      ]
1565

    
1566
    inst = objects.Instance(name="inst562",
1567
                            primary_node=node_uuid,
1568
                            disk_template=constants.DT_PLAIN,
1569
                            disks=disks)
1570

    
1571
    try:
1572
      instance.WipeDisks(lu, inst)
1573
    except errors.OpExecError, err:
1574
      self.assertTrue(str(err), "Could not wipe disk 0 at offset 0 ")
1575
    else:
1576
      self.fail("Did not raise exception")
1577

    
1578
    # Check if all disks were paused and resumed
1579
    self.assertEqual(pt.history, [
1580
      ("disk0", 100 * 1024, True),
1581
      ("disk1", 500 * 1024, True),
1582
      ("disk2", 256, True),
1583
      ("disk0", 100 * 1024, False),
1584
      ("disk1", 500 * 1024, False),
1585
      ("disk2", 256, False),
1586
      ])
1587

    
1588
  def _PrepareWipeTest(self, start_offset, disks):
1589
    node_name = "node-with-offset%s.example.com" % start_offset
1590
    pauset = _DiskPauseTracker()
1591
    progresst = _DiskWipeProgressTracker(start_offset)
1592

    
1593
    lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, pauset, progresst),
1594
                 cfg=_ConfigForDiskWipe(node_name))
1595

    
1596
    instance = objects.Instance(name="inst3560",
1597
                                primary_node=node_name,
1598
                                disk_template=constants.DT_PLAIN,
1599
                                disks=disks)
1600

    
1601
    return (lu, instance, pauset, progresst)
1602

    
1603
  def testNormalWipe(self):
1604
    disks = [
1605
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk0", size=1024),
1606
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk1",
1607
                   size=500 * 1024),
1608
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk2", size=128),
1609
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk3",
1610
                   size=constants.MAX_WIPE_CHUNK),
1611
      ]
1612

    
1613
    (lu, inst, pauset, progresst) = self._PrepareWipeTest(0, disks)
1614

    
1615
    instance.WipeDisks(lu, inst)
1616

    
1617
    self.assertEqual(pauset.history, [
1618
      ("disk0", 1024, True),
1619
      ("disk1", 500 * 1024, True),
1620
      ("disk2", 128, True),
1621
      ("disk3", constants.MAX_WIPE_CHUNK, True),
1622
      ("disk0", 1024, False),
1623
      ("disk1", 500 * 1024, False),
1624
      ("disk2", 128, False),
1625
      ("disk3", constants.MAX_WIPE_CHUNK, False),
1626
      ])
1627

    
1628
    # Ensure the complete disk has been wiped
1629
    self.assertEqual(progresst.progress,
1630
                     dict((i.logical_id, i.size) for i in disks))
1631

    
1632
  def testWipeWithStartOffset(self):
1633
    for start_offset in [0, 280, 8895, 1563204]:
1634
      disks = [
1635
        objects.Disk(dev_type=constants.LD_LV, logical_id="disk0",
1636
                     size=128),
1637
        objects.Disk(dev_type=constants.LD_LV, logical_id="disk1",
1638
                     size=start_offset + (100 * 1024)),
1639
        ]
1640

    
1641
      (lu, inst, pauset, progresst) = \
1642
        self._PrepareWipeTest(start_offset, disks)
1643

    
1644
      # Test start offset with only one disk
1645
      instance.WipeDisks(lu, inst,
1646
                         disks=[(1, disks[1], start_offset)])
1647

    
1648
      # Only the second disk may have been paused and wiped
1649
      self.assertEqual(pauset.history, [
1650
        ("disk1", start_offset + (100 * 1024), True),
1651
        ("disk1", start_offset + (100 * 1024), False),
1652
        ])
1653
      self.assertEqual(progresst.progress, {
1654
        "disk1": disks[1].size,
1655
        })
1656

    
1657

    
1658
class TestDiskSizeInBytesToMebibytes(unittest.TestCase):
1659
  def testLessThanOneMebibyte(self):
1660
    for i in [1, 2, 7, 512, 1000, 1023]:
1661
      lu = _FakeLU()
1662
      result = instance_storage._DiskSizeInBytesToMebibytes(lu, i)
1663
      self.assertEqual(result, 1)
1664
      self.assertEqual(len(lu.warning_log), 1)
1665
      self.assertEqual(len(lu.warning_log[0]), 2)
1666
      (_, (warnsize, )) = lu.warning_log[0]
1667
      self.assertEqual(warnsize, (1024 * 1024) - i)
1668

    
1669
  def testEven(self):
1670
    for i in [1, 2, 7, 512, 1000, 1023]:
1671
      lu = _FakeLU()
1672
      result = instance_storage._DiskSizeInBytesToMebibytes(lu,
1673
                                                            i * 1024 * 1024)
1674
      self.assertEqual(result, i)
1675
      self.assertFalse(lu.warning_log)
1676

    
1677
  def testLargeNumber(self):
1678
    for i in [1, 2, 7, 512, 1000, 1023, 2724, 12420]:
1679
      for j in [1, 2, 486, 326, 986, 1023]:
1680
        lu = _FakeLU()
1681
        size = (1024 * 1024 * i) + j
1682
        result = instance_storage._DiskSizeInBytesToMebibytes(lu, size)
1683
        self.assertEqual(result, i + 1, msg="Amount was not rounded up")
1684
        self.assertEqual(len(lu.warning_log), 1)
1685
        self.assertEqual(len(lu.warning_log[0]), 2)
1686
        (_, (warnsize, )) = lu.warning_log[0]
1687
        self.assertEqual(warnsize, (1024 * 1024) - j)
1688

    
1689

    
1690
class TestCopyLockList(unittest.TestCase):
1691
  def test(self):
1692
    self.assertEqual(instance.CopyLockList([]), [])
1693
    self.assertEqual(instance.CopyLockList(None), None)
1694
    self.assertEqual(instance.CopyLockList(locking.ALL_SET), locking.ALL_SET)
1695

    
1696
    names = ["foo", "bar"]
1697
    output = instance.CopyLockList(names)
1698
    self.assertEqual(names, output)
1699
    self.assertNotEqual(id(names), id(output), msg="List was not copied")
1700

    
1701

    
1702
class TestCheckOpportunisticLocking(unittest.TestCase):
1703
  class OpTest(opcodes.OpCode):
1704
    OP_PARAMS = [
1705
      opcodes._POpportunisticLocking,
1706
      opcodes._PIAllocFromDesc(""),
1707
      ]
1708

    
1709
  @classmethod
1710
  def _MakeOp(cls, **kwargs):
1711
    op = cls.OpTest(**kwargs)
1712
    op.Validate(True)
1713
    return op
1714

    
1715
  def testMissingAttributes(self):
1716
    self.assertRaises(AttributeError, instance._CheckOpportunisticLocking,
1717
                      object())
1718

    
1719
  def testDefaults(self):
1720
    op = self._MakeOp()
1721
    instance._CheckOpportunisticLocking(op)
1722

    
1723
  def test(self):
1724
    for iallocator in [None, "something", "other"]:
1725
      for opplock in [False, True]:
1726
        op = self._MakeOp(iallocator=iallocator,
1727
                          opportunistic_locking=opplock)
1728
        if opplock and not iallocator:
1729
          self.assertRaises(errors.OpPrereqError,
1730
                            instance._CheckOpportunisticLocking, op)
1731
        else:
1732
          instance._CheckOpportunisticLocking(op)
1733

    
1734

    
1735
class _OpTestVerifyErrors(opcodes.OpCode):
1736
  OP_PARAMS = [
1737
    opcodes._PDebugSimulateErrors,
1738
    opcodes._PErrorCodes,
1739
    opcodes._PIgnoreErrors,
1740
    ]
1741

    
1742

    
1743
class _LuTestVerifyErrors(cluster._VerifyErrors):
1744
  def __init__(self, **kwargs):
1745
    cluster._VerifyErrors.__init__(self)
1746
    self.op = _OpTestVerifyErrors(**kwargs)
1747
    self.op.Validate(True)
1748
    self.msglist = []
1749
    self._feedback_fn = self.msglist.append
1750
    self.bad = False
1751

    
1752
  def DispatchCallError(self, which, *args, **kwargs):
1753
    if which:
1754
      self._Error(*args, **kwargs)
1755
    else:
1756
      self._ErrorIf(True, *args, **kwargs)
1757

    
1758
  def CallErrorIf(self, c, *args, **kwargs):
1759
    self._ErrorIf(c, *args, **kwargs)
1760

    
1761

    
1762
class TestVerifyErrors(unittest.TestCase):
1763
  # Fake cluster-verify error code structures; we use two arbitary real error
1764
  # codes to pass validation of ignore_errors
1765
  (_, _ERR1ID, _) = constants.CV_ECLUSTERCFG
1766
  _NODESTR = "node"
1767
  _NODENAME = "mynode"
1768
  _ERR1CODE = (_NODESTR, _ERR1ID, "Error one")
1769
  (_, _ERR2ID, _) = constants.CV_ECLUSTERCERT
1770
  _INSTSTR = "instance"
1771
  _INSTNAME = "myinstance"
1772
  _ERR2CODE = (_INSTSTR, _ERR2ID, "Error two")
1773
  # Arguments used to call _Error() or _ErrorIf()
1774
  _ERR1ARGS = (_ERR1CODE, _NODENAME, "Error1 is %s", "an error")
1775
  _ERR2ARGS = (_ERR2CODE, _INSTNAME, "Error2 has no argument")
1776
  # Expected error messages
1777
  _ERR1MSG = _ERR1ARGS[2] % _ERR1ARGS[3]
1778
  _ERR2MSG = _ERR2ARGS[2]
1779

    
1780
  def testNoError(self):
1781
    lu = _LuTestVerifyErrors()
1782
    lu.CallErrorIf(False, self._ERR1CODE, *self._ERR1ARGS)
1783
    self.assertFalse(lu.bad)
1784
    self.assertFalse(lu.msglist)
1785

    
1786
  def _InitTest(self, **kwargs):
1787
    self.lu1 = _LuTestVerifyErrors(**kwargs)
1788
    self.lu2 = _LuTestVerifyErrors(**kwargs)
1789

    
1790
  def _CallError(self, *args, **kwargs):
1791
    # Check that _Error() and _ErrorIf() produce the same results
1792
    self.lu1.DispatchCallError(True, *args, **kwargs)
1793
    self.lu2.DispatchCallError(False, *args, **kwargs)
1794
    self.assertEqual(self.lu1.bad, self.lu2.bad)
1795
    self.assertEqual(self.lu1.msglist, self.lu2.msglist)
1796
    # Test-specific checks are made on one LU
1797
    return self.lu1
1798

    
1799
  def _checkMsgCommon(self, logstr, errmsg, itype, item, warning):
1800
    self.assertTrue(errmsg in logstr)
1801
    if warning:
1802
      self.assertTrue("WARNING" in logstr)
1803
    else:
1804
      self.assertTrue("ERROR" in logstr)
1805
    self.assertTrue(itype in logstr)
1806
    self.assertTrue(item in logstr)
1807

    
1808
  def _checkMsg1(self, logstr, warning=False):
1809
    self._checkMsgCommon(logstr, self._ERR1MSG, self._NODESTR,
1810
                         self._NODENAME, warning)
1811

    
1812
  def _checkMsg2(self, logstr, warning=False):
1813
    self._checkMsgCommon(logstr, self._ERR2MSG, self._INSTSTR,
1814
                         self._INSTNAME, warning)
1815

    
1816
  def testPlain(self):
1817
    self._InitTest()
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

    
1823
  def testMultiple(self):
1824
    self._InitTest()
1825
    self._CallError(*self._ERR1ARGS)
1826
    lu = self._CallError(*self._ERR2ARGS)
1827
    self.assertTrue(lu.bad)
1828
    self.assertEqual(len(lu.msglist), 2)
1829
    self._checkMsg1(lu.msglist[0])
1830
    self._checkMsg2(lu.msglist[1])
1831

    
1832
  def testIgnore(self):
1833
    self._InitTest(ignore_errors=[self._ERR1ID])
1834
    lu = self._CallError(*self._ERR1ARGS)
1835
    self.assertFalse(lu.bad)
1836
    self.assertEqual(len(lu.msglist), 1)
1837
    self._checkMsg1(lu.msglist[0], warning=True)
1838

    
1839
  def testWarning(self):
1840
    self._InitTest()
1841
    lu = self._CallError(*self._ERR1ARGS,
1842
                         code=_LuTestVerifyErrors.ETYPE_WARNING)
1843
    self.assertFalse(lu.bad)
1844
    self.assertEqual(len(lu.msglist), 1)
1845
    self._checkMsg1(lu.msglist[0], warning=True)
1846

    
1847
  def testWarning2(self):
1848
    self._InitTest()
1849
    self._CallError(*self._ERR1ARGS)
1850
    lu = self._CallError(*self._ERR2ARGS,
1851
                         code=_LuTestVerifyErrors.ETYPE_WARNING)
1852
    self.assertTrue(lu.bad)
1853
    self.assertEqual(len(lu.msglist), 2)
1854
    self._checkMsg1(lu.msglist[0])
1855
    self._checkMsg2(lu.msglist[1], warning=True)
1856

    
1857
  def testDebugSimulate(self):
1858
    lu = _LuTestVerifyErrors(debug_simulate_errors=True)
1859
    lu.CallErrorIf(False, *self._ERR1ARGS)
1860
    self.assertTrue(lu.bad)
1861
    self.assertEqual(len(lu.msglist), 1)
1862
    self._checkMsg1(lu.msglist[0])
1863

    
1864
  def testErrCodes(self):
1865
    self._InitTest(error_codes=True)
1866
    lu = self._CallError(*self._ERR1ARGS)
1867
    self.assertTrue(lu.bad)
1868
    self.assertEqual(len(lu.msglist), 1)
1869
    self._checkMsg1(lu.msglist[0])
1870
    self.assertTrue(self._ERR1ID in lu.msglist[0])
1871

    
1872

    
1873
class TestGetUpdatedIPolicy(unittest.TestCase):
1874
  """Tests for cmdlib._GetUpdatedIPolicy()"""
1875
  _OLD_CLUSTER_POLICY = {
1876
    constants.IPOLICY_VCPU_RATIO: 1.5,
1877
    constants.ISPECS_MINMAX: [
1878
      {
1879
        constants.ISPECS_MIN: {
1880
          constants.ISPEC_MEM_SIZE: 32768,
1881
          constants.ISPEC_CPU_COUNT: 8,
1882
          constants.ISPEC_DISK_COUNT: 1,
1883
          constants.ISPEC_DISK_SIZE: 1024,
1884
          constants.ISPEC_NIC_COUNT: 1,
1885
          constants.ISPEC_SPINDLE_USE: 1,
1886
          },
1887
        constants.ISPECS_MAX: {
1888
          constants.ISPEC_MEM_SIZE: 65536,
1889
          constants.ISPEC_CPU_COUNT: 10,
1890
          constants.ISPEC_DISK_COUNT: 5,
1891
          constants.ISPEC_DISK_SIZE: 1024 * 1024,
1892
          constants.ISPEC_NIC_COUNT: 3,
1893
          constants.ISPEC_SPINDLE_USE: 12,
1894
          },
1895
        },
1896
      constants.ISPECS_MINMAX_DEFAULTS,
1897
      ],
1898
    constants.ISPECS_STD: constants.IPOLICY_DEFAULTS[constants.ISPECS_STD],
1899
    }
1900
  _OLD_GROUP_POLICY = {
1901
    constants.IPOLICY_SPINDLE_RATIO: 2.5,
1902
    constants.ISPECS_MINMAX: [{
1903
      constants.ISPECS_MIN: {
1904
        constants.ISPEC_MEM_SIZE: 128,
1905
        constants.ISPEC_CPU_COUNT: 1,
1906
        constants.ISPEC_DISK_COUNT: 1,
1907
        constants.ISPEC_DISK_SIZE: 1024,
1908
        constants.ISPEC_NIC_COUNT: 1,
1909
        constants.ISPEC_SPINDLE_USE: 1,
1910
        },
1911
      constants.ISPECS_MAX: {
1912
        constants.ISPEC_MEM_SIZE: 32768,
1913
        constants.ISPEC_CPU_COUNT: 8,
1914
        constants.ISPEC_DISK_COUNT: 5,
1915
        constants.ISPEC_DISK_SIZE: 1024 * 1024,
1916
        constants.ISPEC_NIC_COUNT: 3,
1917
        constants.ISPEC_SPINDLE_USE: 12,
1918
        },
1919
      }],
1920
    }
1921

    
1922
  def _TestSetSpecs(self, old_policy, isgroup):
1923
    diff_minmax = [{
1924
      constants.ISPECS_MIN: {
1925
        constants.ISPEC_MEM_SIZE: 64,
1926
        constants.ISPEC_CPU_COUNT: 1,
1927
        constants.ISPEC_DISK_COUNT: 2,
1928
        constants.ISPEC_DISK_SIZE: 64,
1929
        constants.ISPEC_NIC_COUNT: 1,
1930
        constants.ISPEC_SPINDLE_USE: 1,
1931
        },
1932
      constants.ISPECS_MAX: {
1933
        constants.ISPEC_MEM_SIZE: 16384,
1934
        constants.ISPEC_CPU_COUNT: 10,
1935
        constants.ISPEC_DISK_COUNT: 12,
1936
        constants.ISPEC_DISK_SIZE: 1024,
1937
        constants.ISPEC_NIC_COUNT: 9,
1938
        constants.ISPEC_SPINDLE_USE: 18,
1939
        },
1940
      }]
1941
    diff_std = {
1942
        constants.ISPEC_DISK_COUNT: 10,
1943
        constants.ISPEC_DISK_SIZE: 512,
1944
        }
1945
    diff_policy = {
1946
      constants.ISPECS_MINMAX: diff_minmax
1947
      }
1948
    if not isgroup:
1949
      diff_policy[constants.ISPECS_STD] = diff_std
1950
    new_policy = common.GetUpdatedIPolicy(old_policy, diff_policy,
1951
                                          group_policy=isgroup)
1952

    
1953
    self.assertTrue(constants.ISPECS_MINMAX in new_policy)
1954
    self.assertEqual(new_policy[constants.ISPECS_MINMAX], diff_minmax)
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
    if not isgroup:
1961
      new_std = new_policy[constants.ISPECS_STD]
1962
      for key in diff_std:
1963
        self.assertTrue(key in new_std)
1964
        self.assertEqual(new_std[key], diff_std[key])
1965
      old_std = old_policy.get(constants.ISPECS_STD, {})
1966
      for key in old_std:
1967
        self.assertTrue(key in new_std)
1968
        if key not in diff_std:
1969
          self.assertEqual(new_std[key], old_std[key])
1970

    
1971
  def _TestSet(self, old_policy, diff_policy, isgroup):
1972
    new_policy = common.GetUpdatedIPolicy(old_policy, diff_policy,
1973
                                           group_policy=isgroup)
1974
    for key in diff_policy:
1975
      self.assertTrue(key in new_policy)
1976
      self.assertEqual(new_policy[key], diff_policy[key])
1977
    for key in old_policy:
1978
      if not key in diff_policy:
1979
        self.assertTrue(key in new_policy)
1980
        self.assertEqual(new_policy[key], old_policy[key])
1981

    
1982
  def testSet(self):
1983
    diff_policy = {
1984
      constants.IPOLICY_VCPU_RATIO: 3,
1985
      constants.IPOLICY_DTS: [constants.DT_FILE],
1986
      }
1987
    self._TestSet(self._OLD_GROUP_POLICY, diff_policy, True)
1988
    self._TestSetSpecs(self._OLD_GROUP_POLICY, True)
1989
    self._TestSet({}, diff_policy, True)
1990
    self._TestSetSpecs({}, True)
1991
    self._TestSet(self._OLD_CLUSTER_POLICY, diff_policy, False)
1992
    self._TestSetSpecs(self._OLD_CLUSTER_POLICY, False)
1993

    
1994
  def testUnset(self):
1995
    old_policy = self._OLD_GROUP_POLICY
1996
    diff_policy = {
1997
      constants.IPOLICY_SPINDLE_RATIO: constants.VALUE_DEFAULT,
1998
      }
1999
    new_policy = common.GetUpdatedIPolicy(old_policy, diff_policy,
2000
                                          group_policy=True)
2001
    for key in diff_policy:
2002
      self.assertFalse(key in new_policy)
2003
    for key in old_policy:
2004
      if not key in diff_policy:
2005
        self.assertTrue(key in new_policy)
2006
        self.assertEqual(new_policy[key], old_policy[key])
2007

    
2008
    self.assertRaises(errors.OpPrereqError, common.GetUpdatedIPolicy,
2009
                      old_policy, diff_policy, group_policy=False)
2010

    
2011
  def testUnsetEmpty(self):
2012
    old_policy = {}
2013
    for key in constants.IPOLICY_ALL_KEYS:
2014
      diff_policy = {
2015
        key: constants.VALUE_DEFAULT,
2016
        }
2017
    new_policy = common.GetUpdatedIPolicy(old_policy, diff_policy,
2018
                                          group_policy=True)
2019
    self.assertEqual(new_policy, old_policy)
2020

    
2021
  def _TestInvalidKeys(self, old_policy, isgroup):
2022
    INVALID_KEY = "this_key_shouldnt_be_allowed"
2023
    INVALID_DICT = {
2024
      INVALID_KEY: 3,
2025
      }
2026
    invalid_policy = INVALID_DICT
2027
    self.assertRaises(errors.OpPrereqError, common.GetUpdatedIPolicy,
2028
                      old_policy, invalid_policy, group_policy=isgroup)
2029
    invalid_ispecs = {
2030
      constants.ISPECS_MINMAX: [INVALID_DICT],
2031
      }
2032
    self.assertRaises(errors.TypeEnforcementError, common.GetUpdatedIPolicy,
2033
                      old_policy, invalid_ispecs, group_policy=isgroup)
2034
    if isgroup:
2035
      invalid_for_group = {
2036
        constants.ISPECS_STD: constants.IPOLICY_DEFAULTS[constants.ISPECS_STD],
2037
        }
2038
      self.assertRaises(errors.OpPrereqError, common.GetUpdatedIPolicy,
2039
                        old_policy, invalid_for_group, group_policy=isgroup)
2040
    good_ispecs = self._OLD_CLUSTER_POLICY[constants.ISPECS_MINMAX]
2041
    invalid_ispecs = copy.deepcopy(good_ispecs)
2042
    invalid_policy = {
2043
      constants.ISPECS_MINMAX: invalid_ispecs,
2044
      }
2045
    for minmax in invalid_ispecs:
2046
      for key in constants.ISPECS_MINMAX_KEYS:
2047
        ispec = minmax[key]
2048
        ispec[INVALID_KEY] = None
2049
        self.assertRaises(errors.TypeEnforcementError,
2050
                          common.GetUpdatedIPolicy, old_policy,
2051
                          invalid_policy, group_policy=isgroup)
2052
        del ispec[INVALID_KEY]
2053
        for par in constants.ISPECS_PARAMETERS:
2054
          oldv = ispec[par]
2055
          ispec[par] = "this_is_not_good"
2056
          self.assertRaises(errors.TypeEnforcementError,
2057
                            common.GetUpdatedIPolicy,
2058
                            old_policy, invalid_policy, group_policy=isgroup)
2059
          ispec[par] = oldv
2060
    # This is to make sure that no two errors were present during the tests
2061
    common.GetUpdatedIPolicy(old_policy, invalid_policy,
2062
                             group_policy=isgroup)
2063

    
2064
  def testInvalidKeys(self):
2065
    self._TestInvalidKeys(self._OLD_GROUP_POLICY, True)
2066
    self._TestInvalidKeys(self._OLD_CLUSTER_POLICY, False)
2067

    
2068
  def testInvalidValues(self):
2069
    for par in (constants.IPOLICY_PARAMETERS |
2070
                frozenset([constants.IPOLICY_DTS])):
2071
      bad_policy = {
2072
        par: "invalid_value",
2073
        }
2074
      self.assertRaises(errors.OpPrereqError, common.GetUpdatedIPolicy, {},
2075
                        bad_policy, group_policy=True)
2076

    
2077
if __name__ == "__main__":
2078
  testutils.GanetiTestProgram()