Statistics
| Branch: | Tag: | Revision:

root / test / py / ganeti.cmdlib_unittest.py @ 1c3231aa

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(name, 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=name, primary_node=pnode, disks=disks,
205
                              disk_template=disk_template)
206

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

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

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

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

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

    
235

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

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

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

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

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

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

    
294

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

    
303
    if args:
304
      msg = msg % args
305

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

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

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

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

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

    
404
    verify_lu.cfg.GetNodeName = GetNodeName
405

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

    
431

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

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

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

    
447

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

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

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

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

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

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

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

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

    
501

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

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

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

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

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

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

    
576

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

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

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

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

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

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

    
625

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

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

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

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

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

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

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

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

    
687

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

    
692

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

    
699

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

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

    
707

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

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

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

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

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

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

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

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

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

    
831

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

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

    
853
    return []
854

    
855

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

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

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

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

    
872

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

    
904

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

    
922

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

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

    
932

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

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

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

    
955

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

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

    
963

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

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

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

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

    
999

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1166

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

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

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

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

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

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

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

    
1193

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

    
1198

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

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

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

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

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

    
1218
    assert disk_template not in constants.DISK_TEMPLATES
1219

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

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

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

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

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

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

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

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

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

    
1272
    return result
1273

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1445

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

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

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

    
1458

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

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

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

    
1473

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

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

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

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

    
1486

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

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

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

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

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

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

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

    
1510
    assert cur_progress == offset
1511

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

    
1515
    return (True, None)
1516

    
1517

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1614
    instance.WipeDisks(lu, inst)
1615

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

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

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

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

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

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

    
1656

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

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

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

    
1688

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

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

    
1700

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

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

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

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

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

    
1733

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

    
1741

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

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

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

    
1760

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1871

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

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

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

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

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

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

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

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

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

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

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

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

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