Statistics
| Branch: | Tag: | Revision:

root / test / py / ganeti.cmdlib_unittest.py @ 94e252a3

History | View | Annotate | Download (70.9 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2008, 2011, 2012, 2013 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""Script for unittesting the cmdlib module"""
23

    
24

    
25
import os
26
import re
27
import unittest
28
import tempfile
29
import shutil
30
import operator
31
import itertools
32
import copy
33

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

    
57
import testutils
58
import mocks
59

    
60

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

    
65
    self.tmpdir = tempfile.mkdtemp()
66

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

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

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

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

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

    
83

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

    
96

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

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

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

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

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

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

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

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

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

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

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

    
160

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

    
168

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

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

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

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

    
185

    
186
class TestLUGroupAssignNodes(unittest.TestCase):
187

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

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

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

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

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

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

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

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

    
237

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

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

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

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

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

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

    
296

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

    
305
    if args:
306
      msg = msg % args
307

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

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

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

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

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

    
406
    verify_lu.cfg.GetNodeName = GetNodeName
407

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

    
433

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

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

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

    
449

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

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

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

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

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

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

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

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

    
503

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

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

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

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

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

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

    
578

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

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

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

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

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

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

    
627

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

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

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

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

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

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

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

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

    
689

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

    
694

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

    
701

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

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

    
709

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

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

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

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

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

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

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

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

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

    
833

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

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

    
855
    return []
856

    
857

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

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

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

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

    
874

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

    
906

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

    
924

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

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

    
934

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

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

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

    
957

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

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

    
965

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

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

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

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

    
1001

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1168

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

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

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

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

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

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

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

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

    
1201

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

    
1206

    
1207
class TestGenerateDiskTemplate(unittest.TestCase):
1208

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

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

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

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

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

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

    
1232
    assert disk_template not in constants.DISK_TEMPLATES
1233

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

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

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

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

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

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

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

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

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

    
1280
    return result
1281

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

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

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

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

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

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

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

    
1329
      for (idx, disk) in enumerate(result):
1330
        (file_driver, file_storage_dir) = disk.logical_id
1331
        dir_fmt = r"^/tmp/.*\.%s\.disk%d$" % (disk_template, idx + 2)
1332
        self.assertEqual(file_driver, constants.FD_BLKTAP)
1333
        # FIXME: use assertIsNotNone when py 2.7 is minimum supported version
1334
        self.assertNotEqual(re.match(dir_fmt, file_storage_dir), None)
1335

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1445

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

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

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

    
1458

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

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

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

    
1473

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

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

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

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

    
1486

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

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

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

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

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

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

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

    
1510
    assert cur_progress == offset
1511

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

    
1515
    return (True, None)
1516

    
1517

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1614
    instance.WipeDisks(lu, inst)
1615

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

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

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

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

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

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

    
1656

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

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

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

    
1688

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

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

    
1700

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

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

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

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

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

    
1733

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

    
1741

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

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

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

    
1760

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1871

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

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

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

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

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

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

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

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

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

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

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

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

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