Statistics
| Branch: | Tag: | Revision:

root / test / py / ganeti.cmdlib_unittest.py @ 6da90c0a

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.DT_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.DT_PLAIN: {
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, enabled_disk_templates):
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
    self._enabled_disk_templates = enabled_disk_templates
1175

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

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

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

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

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

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

    
1195
  def GetClusterInfo(self):
1196
    cluster = objects.Cluster()
1197
    cluster.enabled_disk_templates = self._enabled_disk_templates
1198
    return cluster
1199

    
1200

    
1201
class _FakeProcForGenDiskTemplate:
1202
  def GetECId(self):
1203
    return 0
1204

    
1205

    
1206
class TestGenerateDiskTemplate(unittest.TestCase):
1207

    
1208
  def _SetUpLUWithTemplates(self, enabled_disk_templates):
1209
    self._enabled_disk_templates = enabled_disk_templates
1210
    cfg = _FakeConfigForGenDiskTemplate(self._enabled_disk_templates)
1211
    proc = _FakeProcForGenDiskTemplate()
1212

    
1213
    self.lu = _FakeLU(cfg=cfg, proc=proc)
1214

    
1215
  def setUp(self):
1216
    nodegroup = objects.NodeGroup(name="ng")
1217
    nodegroup.UpgradeConfig()
1218

    
1219
    self._enabled_disk_templates = list(constants.DISK_TEMPLATES)
1220
    self._SetUpLUWithTemplates(self._enabled_disk_templates)
1221
    self.nodegroup = nodegroup
1222

    
1223
  @staticmethod
1224
  def GetDiskParams():
1225
    return copy.deepcopy(constants.DISK_DT_DEFAULTS)
1226

    
1227
  def testWrongDiskTemplate(self):
1228
    gdt = instance.GenerateDiskTemplate
1229
    disk_template = "##unknown##"
1230

    
1231
    assert disk_template not in constants.DISK_TEMPLATES
1232

    
1233
    self.assertRaises(errors.OpPrereqError, gdt, self.lu, disk_template,
1234
                      "inst26831.example.com", "node30113.example.com", [], [],
1235
                      NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1236
                      self.GetDiskParams())
1237

    
1238
  def testDiskless(self):
1239
    gdt = instance.GenerateDiskTemplate
1240

    
1241
    result = gdt(self.lu, constants.DT_DISKLESS, "inst27734.example.com",
1242
                 "node30113.example.com", [], [],
1243
                 NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1244
                 self.GetDiskParams())
1245
    self.assertEqual(result, [])
1246

    
1247
  def _TestTrivialDisk(self, template, disk_info, base_index, exp_dev_type,
1248
                       file_storage_dir=NotImplemented,
1249
                       file_driver=NotImplemented):
1250
    gdt = instance.GenerateDiskTemplate
1251

    
1252
    map(lambda params: utils.ForceDictType(params,
1253
                                           constants.IDISK_PARAMS_TYPES),
1254
        disk_info)
1255

    
1256
    # Check if non-empty list of secondaries is rejected
1257
    self.assertRaises(errors.ProgrammerError, gdt, self.lu,
1258
                      template, "inst25088.example.com",
1259
                      "node185.example.com", ["node323.example.com"], [],
1260
                      NotImplemented, NotImplemented, base_index,
1261
                      self.lu.LogInfo, self.GetDiskParams())
1262

    
1263
    result = gdt(self.lu, template, "inst21662.example.com",
1264
                 "node21741.example.com", [],
1265
                 disk_info, file_storage_dir, file_driver, base_index,
1266
                 self.lu.LogInfo, self.GetDiskParams())
1267

    
1268
    for (idx, disk) in enumerate(result):
1269
      self.assertTrue(isinstance(disk, objects.Disk))
1270
      self.assertEqual(disk.dev_type, exp_dev_type)
1271
      self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
1272
      self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
1273
      self.assertTrue(disk.children is None)
1274

    
1275
    self._CheckIvNames(result, base_index, base_index + len(disk_info))
1276
    instance._UpdateIvNames(base_index, result)
1277
    self._CheckIvNames(result, base_index, base_index + len(disk_info))
1278

    
1279
    return result
1280

    
1281
  def _CheckIvNames(self, disks, base_index, end_index):
1282
    self.assertEqual(map(operator.attrgetter("iv_name"), disks),
1283
                     ["disk/%s" % i for i in range(base_index, end_index)])
1284

    
1285
  def testPlain(self):
1286
    disk_info = [{
1287
      constants.IDISK_SIZE: 1024,
1288
      constants.IDISK_MODE: constants.DISK_RDWR,
1289
      }, {
1290
      constants.IDISK_SIZE: 4096,
1291
      constants.IDISK_VG: "othervg",
1292
      constants.IDISK_MODE: constants.DISK_RDWR,
1293
      }]
1294

    
1295
    result = self._TestTrivialDisk(constants.DT_PLAIN, disk_info, 3,
1296
                                   constants.DT_PLAIN)
1297

    
1298
    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1299
      ("testvg", "ec0-uq0.disk3"),
1300
      ("othervg", "ec0-uq1.disk4"),
1301
      ])
1302

    
1303
  def testFile(self):
1304
    # anything != DT_FILE would do here
1305
    self._SetUpLUWithTemplates([constants.DT_PLAIN])
1306
    self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
1307
                      constants.DT_FILE, [], 0, NotImplemented)
1308
    self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
1309
                      constants.DT_SHARED_FILE, [], 0, NotImplemented)
1310

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

    
1323
      self._SetUpLUWithTemplates([disk_template])
1324
      result = self._TestTrivialDisk(disk_template, disk_info, 2,
1325
        disk_template, file_storage_dir="/tmp",
1326
        file_driver=constants.FD_BLKTAP)
1327

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

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

    
1341
    result = self._TestTrivialDisk(constants.DT_BLOCK, disk_info, 10,
1342
                                   constants.DT_BLOCK)
1343

    
1344
    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1345
      (constants.BLOCKDEV_DRIVER_MANUAL, "/tmp/some/block/dev"),
1346
      ])
1347

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

    
1357
    result = self._TestTrivialDisk(constants.DT_RBD, disk_info, 0,
1358
                                   constants.DT_RBD)
1359

    
1360
    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1361
      ("rbd", "ec0-uq0.rbd.disk0"),
1362
      ("rbd", "ec0-uq1.rbd.disk1"),
1363
      ])
1364

    
1365
  def testDrbd8(self):
1366
    gdt = instance.GenerateDiskTemplate
1367
    drbd8_defaults = constants.DISK_LD_DEFAULTS[constants.DT_DRBD8]
1368
    drbd8_default_metavg = drbd8_defaults[constants.LDP_DEFAULT_METAVG]
1369

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

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

    
1395
    assert len(exp_logical_ids) == len(disk_info)
1396

    
1397
    map(lambda params: utils.ForceDictType(params,
1398
                                           constants.IDISK_PARAMS_TYPES),
1399
        disk_info)
1400

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

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

    
1412
    for (idx, disk) in enumerate(result):
1413
      self.assertTrue(isinstance(disk, objects.Disk))
1414
      self.assertEqual(disk.dev_type, constants.DT_DRBD8)
1415
      self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
1416
      self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
1417

    
1418
      for child in disk.children:
1419
        self.assertTrue(isinstance(disk, objects.Disk))
1420
        self.assertEqual(child.dev_type, constants.DT_PLAIN)
1421
        self.assertTrue(child.children is None)
1422

    
1423
      self.assertEqual(map(operator.attrgetter("logical_id"), disk.children),
1424
                       exp_logical_ids[idx])
1425

    
1426
      self.assertEqual(len(disk.children), 2)
1427
      self.assertEqual(disk.children[0].size, disk.size)
1428
      self.assertEqual(disk.children[1].size, constants.DRBD_META_SIZE)
1429

    
1430
    self._CheckIvNames(result, 0, len(disk_info))
1431
    instance._UpdateIvNames(0, result)
1432
    self._CheckIvNames(result, 0, len(disk_info))
1433

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

    
1443

    
1444
class _ConfigForDiskWipe:
1445
  def __init__(self, exp_node_uuid):
1446
    self._exp_node_uuid = exp_node_uuid
1447

    
1448
  def SetDiskID(self, device, node_uuid):
1449
    assert isinstance(device, objects.Disk)
1450
    assert node_uuid == self._exp_node_uuid
1451

    
1452
  def GetNodeName(self, node_uuid):
1453
    assert node_uuid == self._exp_node_uuid
1454
    return "name.of.expected.node"
1455

    
1456

    
1457
class _RpcForDiskWipe:
1458
  def __init__(self, exp_node, pause_cb, wipe_cb):
1459
    self._exp_node = exp_node
1460
    self._pause_cb = pause_cb
1461
    self._wipe_cb = wipe_cb
1462

    
1463
  def call_blockdev_pause_resume_sync(self, node, disks, pause):
1464
    assert node == self._exp_node
1465
    return rpc.RpcResult(data=self._pause_cb(disks, pause))
1466

    
1467
  def call_blockdev_wipe(self, node, bdev, offset, size):
1468
    assert node == self._exp_node
1469
    return rpc.RpcResult(data=self._wipe_cb(bdev, offset, size))
1470

    
1471

    
1472
class _DiskPauseTracker:
1473
  def __init__(self):
1474
    self.history = []
1475

    
1476
  def __call__(self, (disks, instance), pause):
1477
    assert not (set(disks) - set(instance.disks))
1478

    
1479
    self.history.extend((i.logical_id, i.size, pause)
1480
                        for i in disks)
1481

    
1482
    return (True, [True] * len(disks))
1483

    
1484

    
1485
class _DiskWipeProgressTracker:
1486
  def __init__(self, start_offset):
1487
    self._start_offset = start_offset
1488
    self.progress = {}
1489

    
1490
  def __call__(self, (disk, _), offset, size):
1491
    assert isinstance(offset, (long, int))
1492
    assert isinstance(size, (long, int))
1493

    
1494
    max_chunk_size = (disk.size / 100.0 * constants.MIN_WIPE_CHUNK_PERCENT)
1495

    
1496
    assert offset >= self._start_offset
1497
    assert (offset + size) <= disk.size
1498

    
1499
    assert size > 0
1500
    assert size <= constants.MAX_WIPE_CHUNK
1501
    assert size <= max_chunk_size
1502

    
1503
    assert offset == self._start_offset or disk.logical_id in self.progress
1504

    
1505
    # Keep track of progress
1506
    cur_progress = self.progress.setdefault(disk.logical_id, self._start_offset)
1507

    
1508
    assert cur_progress == offset
1509

    
1510
    # Record progress
1511
    self.progress[disk.logical_id] += size
1512

    
1513
    return (True, None)
1514

    
1515

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

    
1523
  def testPauseFailure(self):
1524
    node_name = "node1372.example.com"
1525

    
1526
    lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, self._FailingPauseCb,
1527
                                     NotImplemented),
1528
                 cfg=_ConfigForDiskWipe(node_name))
1529

    
1530
    disks = [
1531
      objects.Disk(dev_type=constants.DT_PLAIN),
1532
      objects.Disk(dev_type=constants.DT_PLAIN),
1533
      objects.Disk(dev_type=constants.DT_PLAIN),
1534
      ]
1535

    
1536
    inst = objects.Instance(name="inst21201",
1537
                            primary_node=node_name,
1538
                            disk_template=constants.DT_PLAIN,
1539
                            disks=disks)
1540

    
1541
    self.assertRaises(errors.OpExecError, instance.WipeDisks, lu, inst)
1542

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

    
1548
  def testFailingWipe(self):
1549
    node_uuid = "node13445-uuid"
1550
    pt = _DiskPauseTracker()
1551

    
1552
    lu = _FakeLU(rpc=_RpcForDiskWipe(node_uuid, pt, self._FailingWipeCb),
1553
                 cfg=_ConfigForDiskWipe(node_uuid))
1554

    
1555
    disks = [
1556
      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk0",
1557
                   size=100 * 1024),
1558
      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk1",
1559
                   size=500 * 1024),
1560
      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk2", size=256),
1561
      ]
1562

    
1563
    inst = objects.Instance(name="inst562",
1564
                            primary_node=node_uuid,
1565
                            disk_template=constants.DT_PLAIN,
1566
                            disks=disks)
1567

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

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

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

    
1590
    lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, pauset, progresst),
1591
                 cfg=_ConfigForDiskWipe(node_name))
1592

    
1593
    instance = objects.Instance(name="inst3560",
1594
                                primary_node=node_name,
1595
                                disk_template=constants.DT_PLAIN,
1596
                                disks=disks)
1597

    
1598
    return (lu, instance, pauset, progresst)
1599

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

    
1610
    (lu, inst, pauset, progresst) = self._PrepareWipeTest(0, disks)
1611

    
1612
    instance.WipeDisks(lu, inst)
1613

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

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

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

    
1638
      (lu, inst, pauset, progresst) = \
1639
        self._PrepareWipeTest(start_offset, disks)
1640

    
1641
      # Test start offset with only one disk
1642
      instance.WipeDisks(lu, inst,
1643
                         disks=[(1, disks[1], start_offset)])
1644

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

    
1654

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

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

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

    
1686

    
1687
class TestCopyLockList(unittest.TestCase):
1688
  def test(self):
1689
    self.assertEqual(instance.CopyLockList([]), [])
1690
    self.assertEqual(instance.CopyLockList(None), None)
1691
    self.assertEqual(instance.CopyLockList(locking.ALL_SET), locking.ALL_SET)
1692

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

    
1698

    
1699
class TestCheckOpportunisticLocking(unittest.TestCase):
1700
  class OpTest(opcodes.OpCode):
1701
    OP_PARAMS = [
1702
      opcodes._POpportunisticLocking,
1703
      opcodes._PIAllocFromDesc(""),
1704
      ]
1705

    
1706
  @classmethod
1707
  def _MakeOp(cls, **kwargs):
1708
    op = cls.OpTest(**kwargs)
1709
    op.Validate(True)
1710
    return op
1711

    
1712
  def testMissingAttributes(self):
1713
    self.assertRaises(AttributeError, instance._CheckOpportunisticLocking,
1714
                      object())
1715

    
1716
  def testDefaults(self):
1717
    op = self._MakeOp()
1718
    instance._CheckOpportunisticLocking(op)
1719

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

    
1731

    
1732
class _OpTestVerifyErrors(opcodes.OpCode):
1733
  OP_PARAMS = [
1734
    opcodes._PDebugSimulateErrors,
1735
    opcodes._PErrorCodes,
1736
    opcodes._PIgnoreErrors,
1737
    ]
1738

    
1739

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

    
1749
  def DispatchCallError(self, which, *args, **kwargs):
1750
    if which:
1751
      self._Error(*args, **kwargs)
1752
    else:
1753
      self._ErrorIf(True, *args, **kwargs)
1754

    
1755
  def CallErrorIf(self, c, *args, **kwargs):
1756
    self._ErrorIf(c, *args, **kwargs)
1757

    
1758

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

    
1777
  def testNoError(self):
1778
    lu = _LuTestVerifyErrors()
1779
    lu.CallErrorIf(False, self._ERR1CODE, *self._ERR1ARGS)
1780
    self.assertFalse(lu.bad)
1781
    self.assertFalse(lu.msglist)
1782

    
1783
  def _InitTest(self, **kwargs):
1784
    self.lu1 = _LuTestVerifyErrors(**kwargs)
1785
    self.lu2 = _LuTestVerifyErrors(**kwargs)
1786

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

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

    
1805
  def _checkMsg1(self, logstr, warning=False):
1806
    self._checkMsgCommon(logstr, self._ERR1MSG, self._NODESTR,
1807
                         self._NODENAME, warning)
1808

    
1809
  def _checkMsg2(self, logstr, warning=False):
1810
    self._checkMsgCommon(logstr, self._ERR2MSG, self._INSTSTR,
1811
                         self._INSTNAME, warning)
1812

    
1813
  def testPlain(self):
1814
    self._InitTest()
1815
    lu = self._CallError(*self._ERR1ARGS)
1816
    self.assertTrue(lu.bad)
1817
    self.assertEqual(len(lu.msglist), 1)
1818
    self._checkMsg1(lu.msglist[0])
1819

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

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

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

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

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

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

    
1869

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

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

    
1950
    self.assertTrue(constants.ISPECS_MINMAX in new_policy)
1951
    self.assertEqual(new_policy[constants.ISPECS_MINMAX], diff_minmax)
1952
    for key in old_policy:
1953
      if not key in diff_policy:
1954
        self.assertTrue(key in new_policy)
1955
        self.assertEqual(new_policy[key], old_policy[key])
1956

    
1957
    if not isgroup:
1958
      new_std = new_policy[constants.ISPECS_STD]
1959
      for key in diff_std:
1960
        self.assertTrue(key in new_std)
1961
        self.assertEqual(new_std[key], diff_std[key])
1962
      old_std = old_policy.get(constants.ISPECS_STD, {})
1963
      for key in old_std:
1964
        self.assertTrue(key in new_std)
1965
        if key not in diff_std:
1966
          self.assertEqual(new_std[key], old_std[key])
1967

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

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

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

    
2005
    self.assertRaises(errors.OpPrereqError, common.GetUpdatedIPolicy,
2006
                      old_policy, diff_policy, group_policy=False)
2007

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

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

    
2061
  def testInvalidKeys(self):
2062
    self._TestInvalidKeys(self._OLD_GROUP_POLICY, True)
2063
    self._TestInvalidKeys(self._OLD_CLUSTER_POLICY, False)
2064

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

    
2074
if __name__ == "__main__":
2075
  testutils.GanetiTestProgram()