Statistics
| Branch: | Tag: | Revision:

root / test / py / ganeti.cmdlib_unittest.py @ 5a13489b

History | View | Annotate | Download (69.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 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
  _VerifyFiles = cluster.LUClusterVerifyGroup._VerifyFiles
310

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

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

    
404

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

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

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

    
420

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

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

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

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

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

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

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

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

    
474

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

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

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

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

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

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

    
549

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

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

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

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

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

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

    
598

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

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

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

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

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

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

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

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

    
660

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

    
665

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

    
672

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

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

    
680

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

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

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

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

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

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

    
722
  def testInvalidSpec(self):
723
    spec = _SpecWrapper([None, False, "foo", None, "bar", None])
724
    compute_fn = spec.ComputeMinMaxSpec
725
    ret = common.ComputeIPolicySpecViolation(self._MICRO_IPOL, 1024, 1, 1, 1,
726
                                             [1024], 1, constants.DT_PLAIN,
727
                                             _compute_fn=compute_fn)
728
    self.assertEqual(ret, ["foo", "bar"])
729
    self.assertFalse(spec.spec)
730

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

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

    
804

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

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

    
826
    return []
827

    
828

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

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

    
837
  def GetNodeInfo(self, _):
838
    return {}
839

    
840
  def GetNdParams(self, _):
841
    return {
842
      constants.ND_EXCLUSIVE_STORAGE: self.excl_stor,
843
      }
844

    
845

    
846
class TestComputeIPolicyInstanceViolation(unittest.TestCase):
847
  def test(self):
848
    beparams = {
849
      constants.BE_MAXMEM: 2048,
850
      constants.BE_VCPUS: 2,
851
      constants.BE_SPINDLE_USE: 4,
852
      }
853
    disks = [objects.Disk(size=512, spindles=13)]
854
    cfg = _FakeConfigForComputeIPolicyInstanceViolation(beparams, False)
855
    instance = objects.Instance(beparams=beparams, disks=disks, nics=[],
856
                                disk_template=constants.DT_PLAIN)
857
    stub = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512], 4,
858
                                            constants.DT_PLAIN)
859
    ret = common.ComputeIPolicyInstanceViolation(NotImplemented, instance,
860
                                                 cfg, _compute_fn=stub)
861
    self.assertEqual(ret, [])
862
    instance2 = objects.Instance(beparams={}, disks=disks, nics=[],
863
                                 disk_template=constants.DT_PLAIN)
864
    ret = common.ComputeIPolicyInstanceViolation(NotImplemented, instance2,
865
                                                 cfg, _compute_fn=stub)
866
    self.assertEqual(ret, [])
867
    cfg_es = _FakeConfigForComputeIPolicyInstanceViolation(beparams, True)
868
    stub_es = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512], 13,
869
                                               constants.DT_PLAIN)
870
    ret = common.ComputeIPolicyInstanceViolation(NotImplemented, instance,
871
                                                 cfg_es, _compute_fn=stub_es)
872
    self.assertEqual(ret, [])
873
    ret = common.ComputeIPolicyInstanceViolation(NotImplemented, instance2,
874
                                                 cfg_es, _compute_fn=stub_es)
875
    self.assertEqual(ret, [])
876

    
877

    
878
class TestComputeIPolicyInstanceSpecViolation(unittest.TestCase):
879
  def test(self):
880
    ispec = {
881
      constants.ISPEC_MEM_SIZE: 2048,
882
      constants.ISPEC_CPU_COUNT: 2,
883
      constants.ISPEC_DISK_COUNT: 1,
884
      constants.ISPEC_DISK_SIZE: [512],
885
      constants.ISPEC_NIC_COUNT: 0,
886
      constants.ISPEC_SPINDLE_USE: 1,
887
      }
888
    stub = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512], 1,
889
                                            constants.DT_PLAIN)
890
    ret = instance._ComputeIPolicyInstanceSpecViolation(NotImplemented, ispec,
891
                                                        constants.DT_PLAIN,
892
                                                        _compute_fn=stub)
893
    self.assertEqual(ret, [])
894

    
895

    
896
class _CallRecorder:
897
  def __init__(self, return_value=None):
898
    self.called = False
899
    self.return_value = return_value
900

    
901
  def __call__(self, *args):
902
    self.called = True
903
    return self.return_value
904

    
905

    
906
class TestComputeIPolicyNodeViolation(unittest.TestCase):
907
  def setUp(self):
908
    self.recorder = _CallRecorder(return_value=[])
909

    
910
  def testSameGroup(self):
911
    ret = instance_utils._ComputeIPolicyNodeViolation(
912
      NotImplemented,
913
      NotImplemented,
914
      "foo", "foo", NotImplemented,
915
      _compute_fn=self.recorder)
916
    self.assertFalse(self.recorder.called)
917
    self.assertEqual(ret, [])
918

    
919
  def testDifferentGroup(self):
920
    ret = instance_utils._ComputeIPolicyNodeViolation(
921
      NotImplemented,
922
      NotImplemented,
923
      "foo", "bar", NotImplemented,
924
      _compute_fn=self.recorder)
925
    self.assertTrue(self.recorder.called)
926
    self.assertEqual(ret, [])
927

    
928

    
929
class _FakeConfigForTargetNodeIPolicy:
930
  def __init__(self, node_info=NotImplemented):
931
    self._node_info = node_info
932

    
933
  def GetNodeInfo(self, _):
934
    return self._node_info
935

    
936

    
937
class TestCheckTargetNodeIPolicy(unittest.TestCase):
938
  def setUp(self):
939
    self.instance = objects.Instance(primary_node="blubb")
940
    self.target_node = objects.Node(group="bar")
941
    node_info = objects.Node(group="foo")
942
    fake_cfg = _FakeConfigForTargetNodeIPolicy(node_info=node_info)
943
    self.lu = _FakeLU(cfg=fake_cfg)
944

    
945
  def testNoViolation(self):
946
    compute_recoder = _CallRecorder(return_value=[])
947
    instance.CheckTargetNodeIPolicy(self.lu, NotImplemented, self.instance,
948
                                    self.target_node, NotImplemented,
949
                                    _compute_fn=compute_recoder)
950
    self.assertTrue(compute_recoder.called)
951
    self.assertEqual(self.lu.warning_log, [])
952

    
953
  def testNoIgnore(self):
954
    compute_recoder = _CallRecorder(return_value=["mem_size not in range"])
955
    self.assertRaises(errors.OpPrereqError, instance.CheckTargetNodeIPolicy,
956
                      self.lu, NotImplemented, self.instance,
957
                      self.target_node, NotImplemented,
958
                      _compute_fn=compute_recoder)
959
    self.assertTrue(compute_recoder.called)
960
    self.assertEqual(self.lu.warning_log, [])
961

    
962
  def testIgnoreViolation(self):
963
    compute_recoder = _CallRecorder(return_value=["mem_size not in range"])
964
    instance.CheckTargetNodeIPolicy(self.lu, NotImplemented, self.instance,
965
                                     self.target_node, NotImplemented,
966
                                     ignore=True, _compute_fn=compute_recoder)
967
    self.assertTrue(compute_recoder.called)
968
    msg = ("Instance does not meet target node group's (bar) instance policy:"
969
           " mem_size not in range")
970
    self.assertEqual(self.lu.warning_log, [(msg, ())])
971

    
972

    
973
class TestApplyContainerMods(unittest.TestCase):
974
  def testEmptyContainer(self):
975
    container = []
976
    chgdesc = []
977
    instance._ApplyContainerMods("test", container, chgdesc, [], None, None,
978
                                None)
979
    self.assertEqual(container, [])
980
    self.assertEqual(chgdesc, [])
981

    
982
  def testAdd(self):
983
    container = []
984
    chgdesc = []
985
    mods = instance._PrepareContainerMods([
986
      (constants.DDM_ADD, -1, "Hello"),
987
      (constants.DDM_ADD, -1, "World"),
988
      (constants.DDM_ADD, 0, "Start"),
989
      (constants.DDM_ADD, -1, "End"),
990
      ], None)
991
    instance._ApplyContainerMods("test", container, chgdesc, mods,
992
                                None, None, None)
993
    self.assertEqual(container, ["Start", "Hello", "World", "End"])
994
    self.assertEqual(chgdesc, [])
995

    
996
    mods = instance._PrepareContainerMods([
997
      (constants.DDM_ADD, 0, "zero"),
998
      (constants.DDM_ADD, 3, "Added"),
999
      (constants.DDM_ADD, 5, "four"),
1000
      (constants.DDM_ADD, 7, "xyz"),
1001
      ], None)
1002
    instance._ApplyContainerMods("test", container, chgdesc, mods,
1003
                                None, None, None)
1004
    self.assertEqual(container,
1005
                     ["zero", "Start", "Hello", "Added", "World", "four",
1006
                      "End", "xyz"])
1007
    self.assertEqual(chgdesc, [])
1008

    
1009
    for idx in [-2, len(container) + 1]:
1010
      mods = instance._PrepareContainerMods([
1011
        (constants.DDM_ADD, idx, "error"),
1012
        ], None)
1013
      self.assertRaises(IndexError, instance._ApplyContainerMods,
1014
                        "test", container, None, mods, None, None, None)
1015

    
1016
  def testRemoveError(self):
1017
    for idx in [0, 1, 2, 100, -1, -4]:
1018
      mods = instance._PrepareContainerMods([
1019
        (constants.DDM_REMOVE, idx, None),
1020
        ], None)
1021
      self.assertRaises(IndexError, instance._ApplyContainerMods,
1022
                        "test", [], None, mods, None, None, None)
1023

    
1024
    mods = instance._PrepareContainerMods([
1025
      (constants.DDM_REMOVE, 0, object()),
1026
      ], None)
1027
    self.assertRaises(AssertionError, instance._ApplyContainerMods,
1028
                      "test", [""], None, mods, None, None, None)
1029

    
1030
  def testAddError(self):
1031
    for idx in range(-100, -1) + [100]:
1032
      mods = instance._PrepareContainerMods([
1033
        (constants.DDM_ADD, idx, None),
1034
        ], None)
1035
      self.assertRaises(IndexError, instance._ApplyContainerMods,
1036
                        "test", [], None, mods, None, None, None)
1037

    
1038
  def testRemove(self):
1039
    container = ["item 1", "item 2"]
1040
    mods = instance._PrepareContainerMods([
1041
      (constants.DDM_ADD, -1, "aaa"),
1042
      (constants.DDM_REMOVE, -1, None),
1043
      (constants.DDM_ADD, -1, "bbb"),
1044
      ], None)
1045
    chgdesc = []
1046
    instance._ApplyContainerMods("test", container, chgdesc, mods,
1047
                                None, None, None)
1048
    self.assertEqual(container, ["item 1", "item 2", "bbb"])
1049
    self.assertEqual(chgdesc, [
1050
      ("test/2", "remove"),
1051
      ])
1052

    
1053
  def testModify(self):
1054
    container = ["item 1", "item 2"]
1055
    mods = instance._PrepareContainerMods([
1056
      (constants.DDM_MODIFY, -1, "a"),
1057
      (constants.DDM_MODIFY, 0, "b"),
1058
      (constants.DDM_MODIFY, 1, "c"),
1059
      ], None)
1060
    chgdesc = []
1061
    instance._ApplyContainerMods("test", container, chgdesc, mods,
1062
                                None, None, None)
1063
    self.assertEqual(container, ["item 1", "item 2"])
1064
    self.assertEqual(chgdesc, [])
1065

    
1066
    for idx in [-2, len(container) + 1]:
1067
      mods = instance._PrepareContainerMods([
1068
        (constants.DDM_MODIFY, idx, "error"),
1069
        ], None)
1070
      self.assertRaises(IndexError, instance._ApplyContainerMods,
1071
                        "test", container, None, mods, None, None, None)
1072

    
1073
  class _PrivateData:
1074
    def __init__(self):
1075
      self.data = None
1076

    
1077
  @staticmethod
1078
  def _CreateTestFn(idx, params, private):
1079
    private.data = ("add", idx, params)
1080
    return ((100 * idx, params), [
1081
      ("test/%s" % idx, hex(idx)),
1082
      ])
1083

    
1084
  @staticmethod
1085
  def _ModifyTestFn(idx, item, params, private):
1086
    private.data = ("modify", idx, params)
1087
    return [
1088
      ("test/%s" % idx, "modify %s" % params),
1089
      ]
1090

    
1091
  @staticmethod
1092
  def _RemoveTestFn(idx, item, private):
1093
    private.data = ("remove", idx, item)
1094

    
1095
  def testAddWithCreateFunction(self):
1096
    container = []
1097
    chgdesc = []
1098
    mods = instance._PrepareContainerMods([
1099
      (constants.DDM_ADD, -1, "Hello"),
1100
      (constants.DDM_ADD, -1, "World"),
1101
      (constants.DDM_ADD, 0, "Start"),
1102
      (constants.DDM_ADD, -1, "End"),
1103
      (constants.DDM_REMOVE, 2, None),
1104
      (constants.DDM_MODIFY, -1, "foobar"),
1105
      (constants.DDM_REMOVE, 2, None),
1106
      (constants.DDM_ADD, 1, "More"),
1107
      ], self._PrivateData)
1108
    instance._ApplyContainerMods("test", container, chgdesc, mods,
1109
                                self._CreateTestFn, self._ModifyTestFn,
1110
                                self._RemoveTestFn)
1111
    self.assertEqual(container, [
1112
      (000, "Start"),
1113
      (100, "More"),
1114
      (000, "Hello"),
1115
      ])
1116
    self.assertEqual(chgdesc, [
1117
      ("test/0", "0x0"),
1118
      ("test/1", "0x1"),
1119
      ("test/0", "0x0"),
1120
      ("test/3", "0x3"),
1121
      ("test/2", "remove"),
1122
      ("test/2", "modify foobar"),
1123
      ("test/2", "remove"),
1124
      ("test/1", "0x1")
1125
      ])
1126
    self.assertTrue(compat.all(op == private.data[0]
1127
                               for (op, _, _, private) in mods))
1128
    self.assertEqual([private.data for (op, _, _, private) in mods], [
1129
      ("add", 0, "Hello"),
1130
      ("add", 1, "World"),
1131
      ("add", 0, "Start"),
1132
      ("add", 3, "End"),
1133
      ("remove", 2, (100, "World")),
1134
      ("modify", 2, "foobar"),
1135
      ("remove", 2, (300, "End")),
1136
      ("add", 1, "More"),
1137
      ])
1138

    
1139

    
1140
class _FakeConfigForGenDiskTemplate:
1141
  def __init__(self):
1142
    self._unique_id = itertools.count()
1143
    self._drbd_minor = itertools.count(20)
1144
    self._port = itertools.count(constants.FIRST_DRBD_PORT)
1145
    self._secret = itertools.count()
1146

    
1147
  def GetVGName(self):
1148
    return "testvg"
1149

    
1150
  def GenerateUniqueID(self, ec_id):
1151
    return "ec%s-uq%s" % (ec_id, self._unique_id.next())
1152

    
1153
  def AllocateDRBDMinor(self, nodes, instance):
1154
    return [self._drbd_minor.next()
1155
            for _ in nodes]
1156

    
1157
  def AllocatePort(self):
1158
    return self._port.next()
1159

    
1160
  def GenerateDRBDSecret(self, ec_id):
1161
    return "ec%s-secret%s" % (ec_id, self._secret.next())
1162

    
1163
  def GetInstanceInfo(self, _):
1164
    return "foobar"
1165

    
1166

    
1167
class _FakeProcForGenDiskTemplate:
1168
  def GetECId(self):
1169
    return 0
1170

    
1171

    
1172
class TestGenerateDiskTemplate(unittest.TestCase):
1173
  def setUp(self):
1174
    nodegroup = objects.NodeGroup(name="ng")
1175
    nodegroup.UpgradeConfig()
1176

    
1177
    cfg = _FakeConfigForGenDiskTemplate()
1178
    proc = _FakeProcForGenDiskTemplate()
1179

    
1180
    self.lu = _FakeLU(cfg=cfg, proc=proc)
1181
    self.nodegroup = nodegroup
1182

    
1183
  @staticmethod
1184
  def GetDiskParams():
1185
    return copy.deepcopy(constants.DISK_DT_DEFAULTS)
1186

    
1187
  def testWrongDiskTemplate(self):
1188
    gdt = instance.GenerateDiskTemplate
1189
    disk_template = "##unknown##"
1190

    
1191
    assert disk_template not in constants.DISK_TEMPLATES
1192

    
1193
    self.assertRaises(errors.ProgrammerError, gdt, self.lu, disk_template,
1194
                      "inst26831.example.com", "node30113.example.com", [], [],
1195
                      NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1196
                      self.GetDiskParams())
1197

    
1198
  def testDiskless(self):
1199
    gdt = instance.GenerateDiskTemplate
1200

    
1201
    result = gdt(self.lu, constants.DT_DISKLESS, "inst27734.example.com",
1202
                 "node30113.example.com", [], [],
1203
                 NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1204
                 self.GetDiskParams())
1205
    self.assertEqual(result, [])
1206

    
1207
  def _TestTrivialDisk(self, template, disk_info, base_index, exp_dev_type,
1208
                       file_storage_dir=NotImplemented,
1209
                       file_driver=NotImplemented,
1210
                       req_file_storage=NotImplemented,
1211
                       req_shr_file_storage=NotImplemented):
1212
    gdt = instance.GenerateDiskTemplate
1213

    
1214
    map(lambda params: utils.ForceDictType(params,
1215
                                           constants.IDISK_PARAMS_TYPES),
1216
        disk_info)
1217

    
1218
    # Check if non-empty list of secondaries is rejected
1219
    self.assertRaises(errors.ProgrammerError, gdt, self.lu,
1220
                      template, "inst25088.example.com",
1221
                      "node185.example.com", ["node323.example.com"], [],
1222
                      NotImplemented, NotImplemented, base_index,
1223
                      self.lu.LogInfo, self.GetDiskParams(),
1224
                      _req_file_storage=req_file_storage,
1225
                      _req_shr_file_storage=req_shr_file_storage)
1226

    
1227
    result = gdt(self.lu, template, "inst21662.example.com",
1228
                 "node21741.example.com", [],
1229
                 disk_info, file_storage_dir, file_driver, base_index,
1230
                 self.lu.LogInfo, self.GetDiskParams(),
1231
                 _req_file_storage=req_file_storage,
1232
                 _req_shr_file_storage=req_shr_file_storage)
1233

    
1234
    for (idx, disk) in enumerate(result):
1235
      self.assertTrue(isinstance(disk, objects.Disk))
1236
      self.assertEqual(disk.dev_type, exp_dev_type)
1237
      self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
1238
      self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
1239
      self.assertTrue(disk.children is None)
1240

    
1241
    self._CheckIvNames(result, base_index, base_index + len(disk_info))
1242
    instance._UpdateIvNames(base_index, result)
1243
    self._CheckIvNames(result, base_index, base_index + len(disk_info))
1244

    
1245
    return result
1246

    
1247
  def _CheckIvNames(self, disks, base_index, end_index):
1248
    self.assertEqual(map(operator.attrgetter("iv_name"), disks),
1249
                     ["disk/%s" % i for i in range(base_index, end_index)])
1250

    
1251
  def testPlain(self):
1252
    disk_info = [{
1253
      constants.IDISK_SIZE: 1024,
1254
      constants.IDISK_MODE: constants.DISK_RDWR,
1255
      }, {
1256
      constants.IDISK_SIZE: 4096,
1257
      constants.IDISK_VG: "othervg",
1258
      constants.IDISK_MODE: constants.DISK_RDWR,
1259
      }]
1260

    
1261
    result = self._TestTrivialDisk(constants.DT_PLAIN, disk_info, 3,
1262
                                   constants.LD_LV)
1263

    
1264
    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1265
      ("testvg", "ec0-uq0.disk3"),
1266
      ("othervg", "ec0-uq1.disk4"),
1267
      ])
1268

    
1269
  @staticmethod
1270
  def _AllowFileStorage():
1271
    pass
1272

    
1273
  @staticmethod
1274
  def _ForbidFileStorage():
1275
    raise errors.OpPrereqError("Disallowed in test")
1276

    
1277
  def testFile(self):
1278
    self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
1279
                      constants.DT_FILE, [], 0, NotImplemented,
1280
                      req_file_storage=self._ForbidFileStorage)
1281
    self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
1282
                      constants.DT_SHARED_FILE, [], 0, NotImplemented,
1283
                      req_shr_file_storage=self._ForbidFileStorage)
1284

    
1285
    for disk_template in [constants.DT_FILE, constants.DT_SHARED_FILE]:
1286
      disk_info = [{
1287
        constants.IDISK_SIZE: 80 * 1024,
1288
        constants.IDISK_MODE: constants.DISK_RDONLY,
1289
        }, {
1290
        constants.IDISK_SIZE: 4096,
1291
        constants.IDISK_MODE: constants.DISK_RDWR,
1292
        }, {
1293
        constants.IDISK_SIZE: 6 * 1024,
1294
        constants.IDISK_MODE: constants.DISK_RDWR,
1295
        }]
1296

    
1297
      result = self._TestTrivialDisk(disk_template, disk_info, 2,
1298
        constants.LD_FILE, file_storage_dir="/tmp",
1299
        file_driver=constants.FD_BLKTAP,
1300
        req_file_storage=self._AllowFileStorage,
1301
        req_shr_file_storage=self._AllowFileStorage)
1302

    
1303
      self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1304
        (constants.FD_BLKTAP, "/tmp/disk2"),
1305
        (constants.FD_BLKTAP, "/tmp/disk3"),
1306
        (constants.FD_BLKTAP, "/tmp/disk4"),
1307
        ])
1308

    
1309
  def testBlock(self):
1310
    disk_info = [{
1311
      constants.IDISK_SIZE: 8 * 1024,
1312
      constants.IDISK_MODE: constants.DISK_RDWR,
1313
      constants.IDISK_ADOPT: "/tmp/some/block/dev",
1314
      }]
1315

    
1316
    result = self._TestTrivialDisk(constants.DT_BLOCK, disk_info, 10,
1317
                                   constants.LD_BLOCKDEV)
1318

    
1319
    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1320
      (constants.BLOCKDEV_DRIVER_MANUAL, "/tmp/some/block/dev"),
1321
      ])
1322

    
1323
  def testRbd(self):
1324
    disk_info = [{
1325
      constants.IDISK_SIZE: 8 * 1024,
1326
      constants.IDISK_MODE: constants.DISK_RDONLY,
1327
      }, {
1328
      constants.IDISK_SIZE: 100 * 1024,
1329
      constants.IDISK_MODE: constants.DISK_RDWR,
1330
      }]
1331

    
1332
    result = self._TestTrivialDisk(constants.DT_RBD, disk_info, 0,
1333
                                   constants.LD_RBD)
1334

    
1335
    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1336
      ("rbd", "ec0-uq0.rbd.disk0"),
1337
      ("rbd", "ec0-uq1.rbd.disk1"),
1338
      ])
1339

    
1340
  def testDrbd8(self):
1341
    gdt = instance.GenerateDiskTemplate
1342
    drbd8_defaults = constants.DISK_LD_DEFAULTS[constants.LD_DRBD8]
1343
    drbd8_default_metavg = drbd8_defaults[constants.LDP_DEFAULT_METAVG]
1344

    
1345
    disk_info = [{
1346
      constants.IDISK_SIZE: 1024,
1347
      constants.IDISK_MODE: constants.DISK_RDWR,
1348
      }, {
1349
      constants.IDISK_SIZE: 100 * 1024,
1350
      constants.IDISK_MODE: constants.DISK_RDONLY,
1351
      constants.IDISK_METAVG: "metavg",
1352
      }, {
1353
      constants.IDISK_SIZE: 4096,
1354
      constants.IDISK_MODE: constants.DISK_RDWR,
1355
      constants.IDISK_VG: "vgxyz",
1356
      },
1357
      ]
1358

    
1359
    exp_logical_ids = [[
1360
      (self.lu.cfg.GetVGName(), "ec0-uq0.disk0_data"),
1361
      (drbd8_default_metavg, "ec0-uq0.disk0_meta"),
1362
      ], [
1363
      (self.lu.cfg.GetVGName(), "ec0-uq1.disk1_data"),
1364
      ("metavg", "ec0-uq1.disk1_meta"),
1365
      ], [
1366
      ("vgxyz", "ec0-uq2.disk2_data"),
1367
      (drbd8_default_metavg, "ec0-uq2.disk2_meta"),
1368
      ]]
1369

    
1370
    assert len(exp_logical_ids) == len(disk_info)
1371

    
1372
    map(lambda params: utils.ForceDictType(params,
1373
                                           constants.IDISK_PARAMS_TYPES),
1374
        disk_info)
1375

    
1376
    # Check if empty list of secondaries is rejected
1377
    self.assertRaises(errors.ProgrammerError, gdt, self.lu, constants.DT_DRBD8,
1378
                      "inst827.example.com", "node1334.example.com", [],
1379
                      disk_info, NotImplemented, NotImplemented, 0,
1380
                      self.lu.LogInfo, self.GetDiskParams())
1381

    
1382
    result = gdt(self.lu, constants.DT_DRBD8, "inst827.example.com",
1383
                 "node1334.example.com", ["node12272.example.com"],
1384
                 disk_info, NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1385
                 self.GetDiskParams())
1386

    
1387
    for (idx, disk) in enumerate(result):
1388
      self.assertTrue(isinstance(disk, objects.Disk))
1389
      self.assertEqual(disk.dev_type, constants.LD_DRBD8)
1390
      self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
1391
      self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
1392

    
1393
      for child in disk.children:
1394
        self.assertTrue(isinstance(disk, objects.Disk))
1395
        self.assertEqual(child.dev_type, constants.LD_LV)
1396
        self.assertTrue(child.children is None)
1397

    
1398
      self.assertEqual(map(operator.attrgetter("logical_id"), disk.children),
1399
                       exp_logical_ids[idx])
1400

    
1401
      self.assertEqual(len(disk.children), 2)
1402
      self.assertEqual(disk.children[0].size, disk.size)
1403
      self.assertEqual(disk.children[1].size, constants.DRBD_META_SIZE)
1404

    
1405
    self._CheckIvNames(result, 0, len(disk_info))
1406
    instance._UpdateIvNames(0, result)
1407
    self._CheckIvNames(result, 0, len(disk_info))
1408

    
1409
    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1410
      ("node1334.example.com", "node12272.example.com",
1411
       constants.FIRST_DRBD_PORT, 20, 21, "ec0-secret0"),
1412
      ("node1334.example.com", "node12272.example.com",
1413
       constants.FIRST_DRBD_PORT + 1, 22, 23, "ec0-secret1"),
1414
      ("node1334.example.com", "node12272.example.com",
1415
       constants.FIRST_DRBD_PORT + 2, 24, 25, "ec0-secret2"),
1416
      ])
1417

    
1418

    
1419
class _ConfigForDiskWipe:
1420
  def __init__(self, exp_node):
1421
    self._exp_node = exp_node
1422

    
1423
  def SetDiskID(self, device, node):
1424
    assert isinstance(device, objects.Disk)
1425
    assert node == self._exp_node
1426

    
1427

    
1428
class _RpcForDiskWipe:
1429
  def __init__(self, exp_node, pause_cb, wipe_cb):
1430
    self._exp_node = exp_node
1431
    self._pause_cb = pause_cb
1432
    self._wipe_cb = wipe_cb
1433

    
1434
  def call_blockdev_pause_resume_sync(self, node, disks, pause):
1435
    assert node == self._exp_node
1436
    return rpc.RpcResult(data=self._pause_cb(disks, pause))
1437

    
1438
  def call_blockdev_wipe(self, node, bdev, offset, size):
1439
    assert node == self._exp_node
1440
    return rpc.RpcResult(data=self._wipe_cb(bdev, offset, size))
1441

    
1442

    
1443
class _DiskPauseTracker:
1444
  def __init__(self):
1445
    self.history = []
1446

    
1447
  def __call__(self, (disks, instance), pause):
1448
    assert not (set(disks) - set(instance.disks))
1449

    
1450
    self.history.extend((i.logical_id, i.size, pause)
1451
                        for i in disks)
1452

    
1453
    return (True, [True] * len(disks))
1454

    
1455

    
1456
class _DiskWipeProgressTracker:
1457
  def __init__(self, start_offset):
1458
    self._start_offset = start_offset
1459
    self.progress = {}
1460

    
1461
  def __call__(self, (disk, _), offset, size):
1462
    assert isinstance(offset, (long, int))
1463
    assert isinstance(size, (long, int))
1464

    
1465
    max_chunk_size = (disk.size / 100.0 * constants.MIN_WIPE_CHUNK_PERCENT)
1466

    
1467
    assert offset >= self._start_offset
1468
    assert (offset + size) <= disk.size
1469

    
1470
    assert size > 0
1471
    assert size <= constants.MAX_WIPE_CHUNK
1472
    assert size <= max_chunk_size
1473

    
1474
    assert offset == self._start_offset or disk.logical_id in self.progress
1475

    
1476
    # Keep track of progress
1477
    cur_progress = self.progress.setdefault(disk.logical_id, self._start_offset)
1478

    
1479
    assert cur_progress == offset
1480

    
1481
    # Record progress
1482
    self.progress[disk.logical_id] += size
1483

    
1484
    return (True, None)
1485

    
1486

    
1487
class TestWipeDisks(unittest.TestCase):
1488
  def _FailingPauseCb(self, (disks, _), pause):
1489
    self.assertEqual(len(disks), 3)
1490
    self.assertTrue(pause)
1491
    # Simulate an RPC error
1492
    return (False, "error")
1493

    
1494
  def testPauseFailure(self):
1495
    node_name = "node1372.example.com"
1496

    
1497
    lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, self._FailingPauseCb,
1498
                                     NotImplemented),
1499
                 cfg=_ConfigForDiskWipe(node_name))
1500

    
1501
    disks = [
1502
      objects.Disk(dev_type=constants.LD_LV),
1503
      objects.Disk(dev_type=constants.LD_LV),
1504
      objects.Disk(dev_type=constants.LD_LV),
1505
      ]
1506

    
1507
    inst = objects.Instance(name="inst21201",
1508
                            primary_node=node_name,
1509
                            disk_template=constants.DT_PLAIN,
1510
                            disks=disks)
1511

    
1512
    self.assertRaises(errors.OpExecError, instance.WipeDisks, lu, inst)
1513

    
1514
  def _FailingWipeCb(self, (disk, _), offset, size):
1515
    # This should only ever be called for the first disk
1516
    self.assertEqual(disk.logical_id, "disk0")
1517
    return (False, None)
1518

    
1519
  def testFailingWipe(self):
1520
    node_name = "node13445.example.com"
1521
    pt = _DiskPauseTracker()
1522

    
1523
    lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, pt, self._FailingWipeCb),
1524
                 cfg=_ConfigForDiskWipe(node_name))
1525

    
1526
    disks = [
1527
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk0",
1528
                   size=100 * 1024),
1529
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk1",
1530
                   size=500 * 1024),
1531
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk2", size=256),
1532
      ]
1533

    
1534
    inst = objects.Instance(name="inst562",
1535
                            primary_node=node_name,
1536
                            disk_template=constants.DT_PLAIN,
1537
                            disks=disks)
1538

    
1539
    try:
1540
      instance.WipeDisks(lu, inst)
1541
    except errors.OpExecError, err:
1542
      self.assertTrue(str(err), "Could not wipe disk 0 at offset 0 ")
1543
    else:
1544
      self.fail("Did not raise exception")
1545

    
1546
    # Check if all disks were paused and resumed
1547
    self.assertEqual(pt.history, [
1548
      ("disk0", 100 * 1024, True),
1549
      ("disk1", 500 * 1024, True),
1550
      ("disk2", 256, True),
1551
      ("disk0", 100 * 1024, False),
1552
      ("disk1", 500 * 1024, False),
1553
      ("disk2", 256, False),
1554
      ])
1555

    
1556
  def _PrepareWipeTest(self, start_offset, disks):
1557
    node_name = "node-with-offset%s.example.com" % start_offset
1558
    pauset = _DiskPauseTracker()
1559
    progresst = _DiskWipeProgressTracker(start_offset)
1560

    
1561
    lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, pauset, progresst),
1562
                 cfg=_ConfigForDiskWipe(node_name))
1563

    
1564
    instance = objects.Instance(name="inst3560",
1565
                                primary_node=node_name,
1566
                                disk_template=constants.DT_PLAIN,
1567
                                disks=disks)
1568

    
1569
    return (lu, instance, pauset, progresst)
1570

    
1571
  def testNormalWipe(self):
1572
    disks = [
1573
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk0", size=1024),
1574
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk1",
1575
                   size=500 * 1024),
1576
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk2", size=128),
1577
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk3",
1578
                   size=constants.MAX_WIPE_CHUNK),
1579
      ]
1580

    
1581
    (lu, inst, pauset, progresst) = self._PrepareWipeTest(0, disks)
1582

    
1583
    instance.WipeDisks(lu, inst)
1584

    
1585
    self.assertEqual(pauset.history, [
1586
      ("disk0", 1024, True),
1587
      ("disk1", 500 * 1024, True),
1588
      ("disk2", 128, True),
1589
      ("disk3", constants.MAX_WIPE_CHUNK, True),
1590
      ("disk0", 1024, False),
1591
      ("disk1", 500 * 1024, False),
1592
      ("disk2", 128, False),
1593
      ("disk3", constants.MAX_WIPE_CHUNK, False),
1594
      ])
1595

    
1596
    # Ensure the complete disk has been wiped
1597
    self.assertEqual(progresst.progress,
1598
                     dict((i.logical_id, i.size) for i in disks))
1599

    
1600
  def testWipeWithStartOffset(self):
1601
    for start_offset in [0, 280, 8895, 1563204]:
1602
      disks = [
1603
        objects.Disk(dev_type=constants.LD_LV, logical_id="disk0",
1604
                     size=128),
1605
        objects.Disk(dev_type=constants.LD_LV, logical_id="disk1",
1606
                     size=start_offset + (100 * 1024)),
1607
        ]
1608

    
1609
      (lu, inst, pauset, progresst) = \
1610
        self._PrepareWipeTest(start_offset, disks)
1611

    
1612
      # Test start offset with only one disk
1613
      instance.WipeDisks(lu, inst,
1614
                         disks=[(1, disks[1], start_offset)])
1615

    
1616
      # Only the second disk may have been paused and wiped
1617
      self.assertEqual(pauset.history, [
1618
        ("disk1", start_offset + (100 * 1024), True),
1619
        ("disk1", start_offset + (100 * 1024), False),
1620
        ])
1621
      self.assertEqual(progresst.progress, {
1622
        "disk1": disks[1].size,
1623
        })
1624

    
1625

    
1626
class TestDiskSizeInBytesToMebibytes(unittest.TestCase):
1627
  def testLessThanOneMebibyte(self):
1628
    for i in [1, 2, 7, 512, 1000, 1023]:
1629
      lu = _FakeLU()
1630
      result = instance_storage._DiskSizeInBytesToMebibytes(lu, i)
1631
      self.assertEqual(result, 1)
1632
      self.assertEqual(len(lu.warning_log), 1)
1633
      self.assertEqual(len(lu.warning_log[0]), 2)
1634
      (_, (warnsize, )) = lu.warning_log[0]
1635
      self.assertEqual(warnsize, (1024 * 1024) - i)
1636

    
1637
  def testEven(self):
1638
    for i in [1, 2, 7, 512, 1000, 1023]:
1639
      lu = _FakeLU()
1640
      result = instance_storage._DiskSizeInBytesToMebibytes(lu,
1641
                                                            i * 1024 * 1024)
1642
      self.assertEqual(result, i)
1643
      self.assertFalse(lu.warning_log)
1644

    
1645
  def testLargeNumber(self):
1646
    for i in [1, 2, 7, 512, 1000, 1023, 2724, 12420]:
1647
      for j in [1, 2, 486, 326, 986, 1023]:
1648
        lu = _FakeLU()
1649
        size = (1024 * 1024 * i) + j
1650
        result = instance_storage._DiskSizeInBytesToMebibytes(lu, size)
1651
        self.assertEqual(result, i + 1, msg="Amount was not rounded up")
1652
        self.assertEqual(len(lu.warning_log), 1)
1653
        self.assertEqual(len(lu.warning_log[0]), 2)
1654
        (_, (warnsize, )) = lu.warning_log[0]
1655
        self.assertEqual(warnsize, (1024 * 1024) - j)
1656

    
1657

    
1658
class TestCopyLockList(unittest.TestCase):
1659
  def test(self):
1660
    self.assertEqual(instance.CopyLockList([]), [])
1661
    self.assertEqual(instance.CopyLockList(None), None)
1662
    self.assertEqual(instance.CopyLockList(locking.ALL_SET), locking.ALL_SET)
1663

    
1664
    names = ["foo", "bar"]
1665
    output = instance.CopyLockList(names)
1666
    self.assertEqual(names, output)
1667
    self.assertNotEqual(id(names), id(output), msg="List was not copied")
1668

    
1669

    
1670
class TestCheckOpportunisticLocking(unittest.TestCase):
1671
  class OpTest(opcodes.OpCode):
1672
    OP_PARAMS = [
1673
      opcodes._POpportunisticLocking,
1674
      opcodes._PIAllocFromDesc(""),
1675
      ]
1676

    
1677
  @classmethod
1678
  def _MakeOp(cls, **kwargs):
1679
    op = cls.OpTest(**kwargs)
1680
    op.Validate(True)
1681
    return op
1682

    
1683
  def testMissingAttributes(self):
1684
    self.assertRaises(AttributeError, instance._CheckOpportunisticLocking,
1685
                      object())
1686

    
1687
  def testDefaults(self):
1688
    op = self._MakeOp()
1689
    instance._CheckOpportunisticLocking(op)
1690

    
1691
  def test(self):
1692
    for iallocator in [None, "something", "other"]:
1693
      for opplock in [False, True]:
1694
        op = self._MakeOp(iallocator=iallocator,
1695
                          opportunistic_locking=opplock)
1696
        if opplock and not iallocator:
1697
          self.assertRaises(errors.OpPrereqError,
1698
                            instance._CheckOpportunisticLocking, op)
1699
        else:
1700
          instance._CheckOpportunisticLocking(op)
1701

    
1702

    
1703
class _OpTestVerifyErrors(opcodes.OpCode):
1704
  OP_PARAMS = [
1705
    opcodes._PDebugSimulateErrors,
1706
    opcodes._PErrorCodes,
1707
    opcodes._PIgnoreErrors,
1708
    ]
1709

    
1710

    
1711
class _LuTestVerifyErrors(cluster._VerifyErrors):
1712
  def __init__(self, **kwargs):
1713
    cluster._VerifyErrors.__init__(self)
1714
    self.op = _OpTestVerifyErrors(**kwargs)
1715
    self.op.Validate(True)
1716
    self.msglist = []
1717
    self._feedback_fn = self.msglist.append
1718
    self.bad = False
1719

    
1720
  def DispatchCallError(self, which, *args, **kwargs):
1721
    if which:
1722
      self._Error(*args, **kwargs)
1723
    else:
1724
      self._ErrorIf(True, *args, **kwargs)
1725

    
1726
  def CallErrorIf(self, c, *args, **kwargs):
1727
    self._ErrorIf(c, *args, **kwargs)
1728

    
1729

    
1730
class TestVerifyErrors(unittest.TestCase):
1731
  # Fake cluster-verify error code structures; we use two arbitary real error
1732
  # codes to pass validation of ignore_errors
1733
  (_, _ERR1ID, _) = constants.CV_ECLUSTERCFG
1734
  _NODESTR = "node"
1735
  _NODENAME = "mynode"
1736
  _ERR1CODE = (_NODESTR, _ERR1ID, "Error one")
1737
  (_, _ERR2ID, _) = constants.CV_ECLUSTERCERT
1738
  _INSTSTR = "instance"
1739
  _INSTNAME = "myinstance"
1740
  _ERR2CODE = (_INSTSTR, _ERR2ID, "Error two")
1741
  # Arguments used to call _Error() or _ErrorIf()
1742
  _ERR1ARGS = (_ERR1CODE, _NODENAME, "Error1 is %s", "an error")
1743
  _ERR2ARGS = (_ERR2CODE, _INSTNAME, "Error2 has no argument")
1744
  # Expected error messages
1745
  _ERR1MSG = _ERR1ARGS[2] % _ERR1ARGS[3]
1746
  _ERR2MSG = _ERR2ARGS[2]
1747

    
1748
  def testNoError(self):
1749
    lu = _LuTestVerifyErrors()
1750
    lu.CallErrorIf(False, self._ERR1CODE, *self._ERR1ARGS)
1751
    self.assertFalse(lu.bad)
1752
    self.assertFalse(lu.msglist)
1753

    
1754
  def _InitTest(self, **kwargs):
1755
    self.lu1 = _LuTestVerifyErrors(**kwargs)
1756
    self.lu2 = _LuTestVerifyErrors(**kwargs)
1757

    
1758
  def _CallError(self, *args, **kwargs):
1759
    # Check that _Error() and _ErrorIf() produce the same results
1760
    self.lu1.DispatchCallError(True, *args, **kwargs)
1761
    self.lu2.DispatchCallError(False, *args, **kwargs)
1762
    self.assertEqual(self.lu1.bad, self.lu2.bad)
1763
    self.assertEqual(self.lu1.msglist, self.lu2.msglist)
1764
    # Test-specific checks are made on one LU
1765
    return self.lu1
1766

    
1767
  def _checkMsgCommon(self, logstr, errmsg, itype, item, warning):
1768
    self.assertTrue(errmsg in logstr)
1769
    if warning:
1770
      self.assertTrue("WARNING" in logstr)
1771
    else:
1772
      self.assertTrue("ERROR" in logstr)
1773
    self.assertTrue(itype in logstr)
1774
    self.assertTrue(item in logstr)
1775

    
1776
  def _checkMsg1(self, logstr, warning=False):
1777
    self._checkMsgCommon(logstr, self._ERR1MSG, self._NODESTR,
1778
                         self._NODENAME, warning)
1779

    
1780
  def _checkMsg2(self, logstr, warning=False):
1781
    self._checkMsgCommon(logstr, self._ERR2MSG, self._INSTSTR,
1782
                         self._INSTNAME, warning)
1783

    
1784
  def testPlain(self):
1785
    self._InitTest()
1786
    lu = self._CallError(*self._ERR1ARGS)
1787
    self.assertTrue(lu.bad)
1788
    self.assertEqual(len(lu.msglist), 1)
1789
    self._checkMsg1(lu.msglist[0])
1790

    
1791
  def testMultiple(self):
1792
    self._InitTest()
1793
    self._CallError(*self._ERR1ARGS)
1794
    lu = self._CallError(*self._ERR2ARGS)
1795
    self.assertTrue(lu.bad)
1796
    self.assertEqual(len(lu.msglist), 2)
1797
    self._checkMsg1(lu.msglist[0])
1798
    self._checkMsg2(lu.msglist[1])
1799

    
1800
  def testIgnore(self):
1801
    self._InitTest(ignore_errors=[self._ERR1ID])
1802
    lu = self._CallError(*self._ERR1ARGS)
1803
    self.assertFalse(lu.bad)
1804
    self.assertEqual(len(lu.msglist), 1)
1805
    self._checkMsg1(lu.msglist[0], warning=True)
1806

    
1807
  def testWarning(self):
1808
    self._InitTest()
1809
    lu = self._CallError(*self._ERR1ARGS,
1810
                         code=_LuTestVerifyErrors.ETYPE_WARNING)
1811
    self.assertFalse(lu.bad)
1812
    self.assertEqual(len(lu.msglist), 1)
1813
    self._checkMsg1(lu.msglist[0], warning=True)
1814

    
1815
  def testWarning2(self):
1816
    self._InitTest()
1817
    self._CallError(*self._ERR1ARGS)
1818
    lu = self._CallError(*self._ERR2ARGS,
1819
                         code=_LuTestVerifyErrors.ETYPE_WARNING)
1820
    self.assertTrue(lu.bad)
1821
    self.assertEqual(len(lu.msglist), 2)
1822
    self._checkMsg1(lu.msglist[0])
1823
    self._checkMsg2(lu.msglist[1], warning=True)
1824

    
1825
  def testDebugSimulate(self):
1826
    lu = _LuTestVerifyErrors(debug_simulate_errors=True)
1827
    lu.CallErrorIf(False, *self._ERR1ARGS)
1828
    self.assertTrue(lu.bad)
1829
    self.assertEqual(len(lu.msglist), 1)
1830
    self._checkMsg1(lu.msglist[0])
1831

    
1832
  def testErrCodes(self):
1833
    self._InitTest(error_codes=True)
1834
    lu = self._CallError(*self._ERR1ARGS)
1835
    self.assertTrue(lu.bad)
1836
    self.assertEqual(len(lu.msglist), 1)
1837
    self._checkMsg1(lu.msglist[0])
1838
    self.assertTrue(self._ERR1ID in lu.msglist[0])
1839

    
1840

    
1841
class TestGetUpdatedIPolicy(unittest.TestCase):
1842
  """Tests for cmdlib._GetUpdatedIPolicy()"""
1843
  _OLD_CLUSTER_POLICY = {
1844
    constants.IPOLICY_VCPU_RATIO: 1.5,
1845
    constants.ISPECS_MINMAX: [
1846
      {
1847
        constants.ISPECS_MIN: {
1848
          constants.ISPEC_MEM_SIZE: 32768,
1849
          constants.ISPEC_CPU_COUNT: 8,
1850
          constants.ISPEC_DISK_COUNT: 1,
1851
          constants.ISPEC_DISK_SIZE: 1024,
1852
          constants.ISPEC_NIC_COUNT: 1,
1853
          constants.ISPEC_SPINDLE_USE: 1,
1854
          },
1855
        constants.ISPECS_MAX: {
1856
          constants.ISPEC_MEM_SIZE: 65536,
1857
          constants.ISPEC_CPU_COUNT: 10,
1858
          constants.ISPEC_DISK_COUNT: 5,
1859
          constants.ISPEC_DISK_SIZE: 1024 * 1024,
1860
          constants.ISPEC_NIC_COUNT: 3,
1861
          constants.ISPEC_SPINDLE_USE: 12,
1862
          },
1863
        },
1864
      constants.ISPECS_MINMAX_DEFAULTS,
1865
      ],
1866
    constants.ISPECS_STD: constants.IPOLICY_DEFAULTS[constants.ISPECS_STD],
1867
    }
1868
  _OLD_GROUP_POLICY = {
1869
    constants.IPOLICY_SPINDLE_RATIO: 2.5,
1870
    constants.ISPECS_MINMAX: [{
1871
      constants.ISPECS_MIN: {
1872
        constants.ISPEC_MEM_SIZE: 128,
1873
        constants.ISPEC_CPU_COUNT: 1,
1874
        constants.ISPEC_DISK_COUNT: 1,
1875
        constants.ISPEC_DISK_SIZE: 1024,
1876
        constants.ISPEC_NIC_COUNT: 1,
1877
        constants.ISPEC_SPINDLE_USE: 1,
1878
        },
1879
      constants.ISPECS_MAX: {
1880
        constants.ISPEC_MEM_SIZE: 32768,
1881
        constants.ISPEC_CPU_COUNT: 8,
1882
        constants.ISPEC_DISK_COUNT: 5,
1883
        constants.ISPEC_DISK_SIZE: 1024 * 1024,
1884
        constants.ISPEC_NIC_COUNT: 3,
1885
        constants.ISPEC_SPINDLE_USE: 12,
1886
        },
1887
      }],
1888
    }
1889

    
1890
  def _TestSetSpecs(self, old_policy, isgroup):
1891
    diff_minmax = [{
1892
      constants.ISPECS_MIN: {
1893
        constants.ISPEC_MEM_SIZE: 64,
1894
        constants.ISPEC_CPU_COUNT: 1,
1895
        constants.ISPEC_DISK_COUNT: 2,
1896
        constants.ISPEC_DISK_SIZE: 64,
1897
        constants.ISPEC_NIC_COUNT: 1,
1898
        constants.ISPEC_SPINDLE_USE: 1,
1899
        },
1900
      constants.ISPECS_MAX: {
1901
        constants.ISPEC_MEM_SIZE: 16384,
1902
        constants.ISPEC_CPU_COUNT: 10,
1903
        constants.ISPEC_DISK_COUNT: 12,
1904
        constants.ISPEC_DISK_SIZE: 1024,
1905
        constants.ISPEC_NIC_COUNT: 9,
1906
        constants.ISPEC_SPINDLE_USE: 18,
1907
        },
1908
      }]
1909
    diff_std = {
1910
        constants.ISPEC_DISK_COUNT: 10,
1911
        constants.ISPEC_DISK_SIZE: 512,
1912
        }
1913
    diff_policy = {
1914
      constants.ISPECS_MINMAX: diff_minmax
1915
      }
1916
    if not isgroup:
1917
      diff_policy[constants.ISPECS_STD] = diff_std
1918
    new_policy = common.GetUpdatedIPolicy(old_policy, diff_policy,
1919
                                          group_policy=isgroup)
1920

    
1921
    self.assertTrue(constants.ISPECS_MINMAX in new_policy)
1922
    self.assertEqual(new_policy[constants.ISPECS_MINMAX], diff_minmax)
1923
    for key in old_policy:
1924
      if not key in diff_policy:
1925
        self.assertTrue(key in new_policy)
1926
        self.assertEqual(new_policy[key], old_policy[key])
1927

    
1928
    if not isgroup:
1929
      new_std = new_policy[constants.ISPECS_STD]
1930
      for key in diff_std:
1931
        self.assertTrue(key in new_std)
1932
        self.assertEqual(new_std[key], diff_std[key])
1933
      old_std = old_policy.get(constants.ISPECS_STD, {})
1934
      for key in old_std:
1935
        self.assertTrue(key in new_std)
1936
        if key not in diff_std:
1937
          self.assertEqual(new_std[key], old_std[key])
1938

    
1939
  def _TestSet(self, old_policy, diff_policy, isgroup):
1940
    new_policy = common.GetUpdatedIPolicy(old_policy, diff_policy,
1941
                                           group_policy=isgroup)
1942
    for key in diff_policy:
1943
      self.assertTrue(key in new_policy)
1944
      self.assertEqual(new_policy[key], diff_policy[key])
1945
    for key in old_policy:
1946
      if not key in diff_policy:
1947
        self.assertTrue(key in new_policy)
1948
        self.assertEqual(new_policy[key], old_policy[key])
1949

    
1950
  def testSet(self):
1951
    diff_policy = {
1952
      constants.IPOLICY_VCPU_RATIO: 3,
1953
      constants.IPOLICY_DTS: [constants.DT_FILE],
1954
      }
1955
    self._TestSet(self._OLD_GROUP_POLICY, diff_policy, True)
1956
    self._TestSetSpecs(self._OLD_GROUP_POLICY, True)
1957
    self._TestSet({}, diff_policy, True)
1958
    self._TestSetSpecs({}, True)
1959
    self._TestSet(self._OLD_CLUSTER_POLICY, diff_policy, False)
1960
    self._TestSetSpecs(self._OLD_CLUSTER_POLICY, False)
1961

    
1962
  def testUnset(self):
1963
    old_policy = self._OLD_GROUP_POLICY
1964
    diff_policy = {
1965
      constants.IPOLICY_SPINDLE_RATIO: constants.VALUE_DEFAULT,
1966
      }
1967
    new_policy = common.GetUpdatedIPolicy(old_policy, diff_policy,
1968
                                          group_policy=True)
1969
    for key in diff_policy:
1970
      self.assertFalse(key in new_policy)
1971
    for key in old_policy:
1972
      if not key in diff_policy:
1973
        self.assertTrue(key in new_policy)
1974
        self.assertEqual(new_policy[key], old_policy[key])
1975

    
1976
    self.assertRaises(errors.OpPrereqError, common.GetUpdatedIPolicy,
1977
                      old_policy, diff_policy, group_policy=False)
1978

    
1979
  def testUnsetEmpty(self):
1980
    old_policy = {}
1981
    for key in constants.IPOLICY_ALL_KEYS:
1982
      diff_policy = {
1983
        key: constants.VALUE_DEFAULT,
1984
        }
1985
    new_policy = common.GetUpdatedIPolicy(old_policy, diff_policy,
1986
                                          group_policy=True)
1987
    self.assertEqual(new_policy, old_policy)
1988

    
1989
  def _TestInvalidKeys(self, old_policy, isgroup):
1990
    INVALID_KEY = "this_key_shouldnt_be_allowed"
1991
    INVALID_DICT = {
1992
      INVALID_KEY: 3,
1993
      }
1994
    invalid_policy = INVALID_DICT
1995
    self.assertRaises(errors.OpPrereqError, common.GetUpdatedIPolicy,
1996
                      old_policy, invalid_policy, group_policy=isgroup)
1997
    invalid_ispecs = {
1998
      constants.ISPECS_MINMAX: [INVALID_DICT],
1999
      }
2000
    self.assertRaises(errors.TypeEnforcementError, common.GetUpdatedIPolicy,
2001
                      old_policy, invalid_ispecs, group_policy=isgroup)
2002
    if isgroup:
2003
      invalid_for_group = {
2004
        constants.ISPECS_STD: constants.IPOLICY_DEFAULTS[constants.ISPECS_STD],
2005
        }
2006
      self.assertRaises(errors.OpPrereqError, common.GetUpdatedIPolicy,
2007
                        old_policy, invalid_for_group, group_policy=isgroup)
2008
    good_ispecs = self._OLD_CLUSTER_POLICY[constants.ISPECS_MINMAX]
2009
    invalid_ispecs = copy.deepcopy(good_ispecs)
2010
    invalid_policy = {
2011
      constants.ISPECS_MINMAX: invalid_ispecs,
2012
      }
2013
    for minmax in invalid_ispecs:
2014
      for key in constants.ISPECS_MINMAX_KEYS:
2015
        ispec = minmax[key]
2016
        ispec[INVALID_KEY] = None
2017
        self.assertRaises(errors.TypeEnforcementError,
2018
                          common.GetUpdatedIPolicy, old_policy,
2019
                          invalid_policy, group_policy=isgroup)
2020
        del ispec[INVALID_KEY]
2021
        for par in constants.ISPECS_PARAMETERS:
2022
          oldv = ispec[par]
2023
          ispec[par] = "this_is_not_good"
2024
          self.assertRaises(errors.TypeEnforcementError,
2025
                            common.GetUpdatedIPolicy,
2026
                            old_policy, invalid_policy, group_policy=isgroup)
2027
          ispec[par] = oldv
2028
    # This is to make sure that no two errors were present during the tests
2029
    common.GetUpdatedIPolicy(old_policy, invalid_policy,
2030
                             group_policy=isgroup)
2031

    
2032
  def testInvalidKeys(self):
2033
    self._TestInvalidKeys(self._OLD_GROUP_POLICY, True)
2034
    self._TestInvalidKeys(self._OLD_CLUSTER_POLICY, False)
2035

    
2036
  def testInvalidValues(self):
2037
    for par in (constants.IPOLICY_PARAMETERS |
2038
                frozenset([constants.IPOLICY_DTS])):
2039
      bad_policy = {
2040
        par: "invalid_value",
2041
        }
2042
      self.assertRaises(errors.OpPrereqError, common.GetUpdatedIPolicy, {},
2043
                        bad_policy, group_policy=True)
2044

    
2045
if __name__ == "__main__":
2046
  testutils.GanetiTestProgram()