Statistics
| Branch: | Tag: | Revision:

root / test / py / ganeti.cmdlib_unittest.py @ 8bb2df7d

History | View | Annotate | Download (56.5 kB)

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

    
4
# Copyright (C) 2008, 2011, 2012 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 time
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 import opcodes
38
from ganeti import errors
39
from ganeti import utils
40
from ganeti import luxi
41
from ganeti import ht
42
from ganeti import objects
43
from ganeti import compat
44
from ganeti import rpc
45
from ganeti import locking
46
from ganeti import pathutils
47
from ganeti.masterd import iallocator
48
from ganeti.hypervisor import hv_xen
49

    
50
import testutils
51
import mocks
52

    
53

    
54
class TestCertVerification(testutils.GanetiTestCase):
55
  def setUp(self):
56
    testutils.GanetiTestCase.setUp(self)
57

    
58
    self.tmpdir = tempfile.mkdtemp()
59

    
60
  def tearDown(self):
61
    shutil.rmtree(self.tmpdir)
62

    
63
  def testVerifyCertificate(self):
64
    cmdlib._VerifyCertificate(testutils.TestDataFilename("cert1.pem"))
65

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

    
68
    (errcode, msg) = cmdlib._VerifyCertificate(nonexist_filename)
69
    self.assertEqual(errcode, cmdlib.LUClusterVerifyConfig.ETYPE_ERROR)
70

    
71
    # Try to load non-certificate file
72
    invalid_cert = testutils.TestDataFilename("bdev-net.txt")
73
    (errcode, msg) = cmdlib._VerifyCertificate(invalid_cert)
74
    self.assertEqual(errcode, cmdlib.LUClusterVerifyConfig.ETYPE_ERROR)
75

    
76

    
77
class TestOpcodeParams(testutils.GanetiTestCase):
78
  def testParamsStructures(self):
79
    for op in sorted(mcpu.Processor.DISPATCH_TABLE):
80
      lu = mcpu.Processor.DISPATCH_TABLE[op]
81
      lu_name = lu.__name__
82
      self.failIf(hasattr(lu, "_OP_REQP"),
83
                  msg=("LU '%s' has old-style _OP_REQP" % lu_name))
84
      self.failIf(hasattr(lu, "_OP_DEFS"),
85
                  msg=("LU '%s' has old-style _OP_DEFS" % lu_name))
86
      self.failIf(hasattr(lu, "_OP_PARAMS"),
87
                  msg=("LU '%s' has old-style _OP_PARAMS" % lu_name))
88

    
89

    
90
class TestIAllocatorChecks(testutils.GanetiTestCase):
91
  def testFunction(self):
92
    class TestLU(object):
93
      def __init__(self, opcode):
94
        self.cfg = mocks.FakeConfig()
95
        self.op = opcode
96

    
97
    class OpTest(opcodes.OpCode):
98
       OP_PARAMS = [
99
        ("iallocator", None, ht.NoType, None),
100
        ("node", None, ht.NoType, None),
101
        ]
102

    
103
    default_iallocator = mocks.FakeConfig().GetDefaultIAllocator()
104
    other_iallocator = default_iallocator + "_not"
105

    
106
    op = OpTest()
107
    lu = TestLU(op)
108

    
109
    c_i = lambda: cmdlib._CheckIAllocatorOrNode(lu, "iallocator", "node")
110

    
111
    # Neither node nor iallocator given
112
    for n in (None, []):
113
      op.iallocator = None
114
      op.node = n
115
      c_i()
116
      self.assertEqual(lu.op.iallocator, default_iallocator)
117
      self.assertEqual(lu.op.node, n)
118

    
119
    # Both, iallocator and node given
120
    for a in ("test", constants.DEFAULT_IALLOCATOR_SHORTCUT):
121
      op.iallocator = a
122
      op.node = "test"
123
      self.assertRaises(errors.OpPrereqError, c_i)
124

    
125
    # Only iallocator given
126
    for n in (None, []):
127
      op.iallocator = other_iallocator
128
      op.node = n
129
      c_i()
130
      self.assertEqual(lu.op.iallocator, other_iallocator)
131
      self.assertEqual(lu.op.node, n)
132

    
133
    # Only node given
134
    op.iallocator = None
135
    op.node = "node"
136
    c_i()
137
    self.assertEqual(lu.op.iallocator, None)
138
    self.assertEqual(lu.op.node, "node")
139

    
140
    # Asked for default iallocator, no node given
141
    op.iallocator = constants.DEFAULT_IALLOCATOR_SHORTCUT
142
    op.node = None
143
    c_i()
144
    self.assertEqual(lu.op.iallocator, default_iallocator)
145
    self.assertEqual(lu.op.node, None)
146

    
147
    # No node, iallocator or default iallocator
148
    op.iallocator = None
149
    op.node = None
150
    lu.cfg.GetDefaultIAllocator = lambda: None
151
    self.assertRaises(errors.OpPrereqError, c_i)
152

    
153

    
154
class TestLUTestJqueue(unittest.TestCase):
155
  def test(self):
156
    self.assert_(cmdlib.LUTestJqueue._CLIENT_CONNECT_TIMEOUT <
157
                 (luxi.WFJC_TIMEOUT * 0.75),
158
                 msg=("Client timeout too high, might not notice bugs"
159
                      " in WaitForJobChange"))
160

    
161

    
162
class TestLUQuery(unittest.TestCase):
163
  def test(self):
164
    self.assertEqual(sorted(cmdlib._QUERY_IMPL.keys()),
165
                     sorted(constants.QR_VIA_OP))
166

    
167
    assert constants.QR_NODE in constants.QR_VIA_OP
168
    assert constants.QR_INSTANCE in constants.QR_VIA_OP
169

    
170
    for i in constants.QR_VIA_OP:
171
      self.assert_(cmdlib._GetQueryImplementation(i))
172

    
173
    self.assertRaises(errors.OpPrereqError, cmdlib._GetQueryImplementation, "")
174
    self.assertRaises(errors.OpPrereqError, cmdlib._GetQueryImplementation,
175
                      "xyz")
176

    
177

    
178
class TestLUGroupAssignNodes(unittest.TestCase):
179

    
180
  def testCheckAssignmentForSplitInstances(self):
181
    node_data = dict((name, objects.Node(name=name, group=group))
182
                     for (name, group) in [("n1a", "g1"), ("n1b", "g1"),
183
                                           ("n2a", "g2"), ("n2b", "g2"),
184
                                           ("n3a", "g3"), ("n3b", "g3"),
185
                                           ("n3c", "g3"),
186
                                           ])
187

    
188
    def Instance(name, pnode, snode):
189
      if snode is None:
190
        disks = []
191
        disk_template = constants.DT_DISKLESS
192
      else:
193
        disks = [objects.Disk(dev_type=constants.LD_DRBD8,
194
                              logical_id=[pnode, snode, 1, 17, 17])]
195
        disk_template = constants.DT_DRBD8
196

    
197
      return objects.Instance(name=name, primary_node=pnode, disks=disks,
198
                              disk_template=disk_template)
199

    
200
    instance_data = dict((name, Instance(name, pnode, snode))
201
                         for name, pnode, snode in [("inst1a", "n1a", "n1b"),
202
                                                    ("inst1b", "n1b", "n1a"),
203
                                                    ("inst2a", "n2a", "n2b"),
204
                                                    ("inst3a", "n3a", None),
205
                                                    ("inst3b", "n3b", "n1b"),
206
                                                    ("inst3c", "n3b", "n2b"),
207
                                                    ])
208

    
209
    # Test first with the existing state.
210
    (new, prev) = \
211
      cmdlib.LUGroupAssignNodes.CheckAssignmentForSplitInstances([],
212
                                                                 node_data,
213
                                                                 instance_data)
214

    
215
    self.assertEqual([], new)
216
    self.assertEqual(set(["inst3b", "inst3c"]), set(prev))
217

    
218
    # And now some changes.
219
    (new, prev) = \
220
      cmdlib.LUGroupAssignNodes.CheckAssignmentForSplitInstances([("n1b",
221
                                                                   "g3")],
222
                                                                 node_data,
223
                                                                 instance_data)
224

    
225
    self.assertEqual(set(["inst1a", "inst1b"]), set(new))
226
    self.assertEqual(set(["inst3c"]), set(prev))
227

    
228

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

    
255
    (online, perhost) = fn(mygroupnodes, "my", nodes)
256
    self.assertEqual(online, ["node%s" % i for i in range(20, 26)])
257
    self.assertEqual(set(perhost.keys()), set(online))
258

    
259
    self.assertEqual(perhost, {
260
      "node20": ["node10", "node2", "node50"],
261
      "node21": ["node11", "node3", "node50"],
262
      "node22": ["node10", "node5", "node50"],
263
      "node23": ["node11", "node2", "node50"],
264
      "node24": ["node10", "node3", "node50"],
265
      "node25": ["node11", "node5", "node50"],
266
      })
267

    
268
  def testSingleGroup(self):
269
    fn = cmdlib.LUClusterVerifyGroup._SelectSshCheckNodes
270
    nodes = [
271
      objects.Node(name="node1", group="default", offline=True),
272
      objects.Node(name="node2", group="default", offline=False),
273
      objects.Node(name="node3", group="default", offline=False),
274
      objects.Node(name="node4", group="default", offline=True),
275
      ]
276
    assert not utils.FindDuplicates(map(operator.attrgetter("name"), nodes))
277

    
278
    (online, perhost) = fn(nodes, "default", nodes)
279
    self.assertEqual(online, ["node2", "node3"])
280
    self.assertEqual(set(perhost.keys()), set(online))
281

    
282
    self.assertEqual(perhost, {
283
      "node2": [],
284
      "node3": [],
285
      })
286

    
287

    
288
class TestClusterVerifyFiles(unittest.TestCase):
289
  @staticmethod
290
  def _FakeErrorIf(errors, cond, ecode, item, msg, *args, **kwargs):
291
    assert ((ecode == constants.CV_ENODEFILECHECK and
292
             ht.TNonEmptyString(item)) or
293
            (ecode == constants.CV_ECLUSTERFILECHECK and
294
             item is None))
295

    
296
    if args:
297
      msg = msg % args
298

    
299
    if cond:
300
      errors.append((item, msg))
301

    
302
  _VerifyFiles = cmdlib.LUClusterVerifyGroup._VerifyFiles
303

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

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

    
397

    
398
class _FakeLU:
399
  def __init__(self, cfg=NotImplemented, proc=NotImplemented,
400
               rpc=NotImplemented):
401
    self.warning_log = []
402
    self.info_log = []
403
    self.cfg = cfg
404
    self.proc = proc
405
    self.rpc = rpc
406

    
407
  def LogWarning(self, text, *args):
408
    self.warning_log.append((text, args))
409

    
410
  def LogInfo(self, text, *args):
411
    self.info_log.append((text, args))
412

    
413

    
414
class TestLoadNodeEvacResult(unittest.TestCase):
415
  def testSuccess(self):
416
    for moved in [[], [
417
      ("inst20153.example.com", "grp2", ["nodeA4509", "nodeB2912"]),
418
      ]]:
419
      for early_release in [False, True]:
420
        for use_nodes in [False, True]:
421
          jobs = [
422
            [opcodes.OpInstanceReplaceDisks().__getstate__()],
423
            [opcodes.OpInstanceMigrate().__getstate__()],
424
            ]
425

    
426
          alloc_result = (moved, [], jobs)
427
          assert iallocator._NEVAC_RESULT(alloc_result)
428

    
429
          lu = _FakeLU()
430
          result = cmdlib._LoadNodeEvacResult(lu, alloc_result,
431
                                              early_release, use_nodes)
432

    
433
          if moved:
434
            (_, (info_args, )) = lu.info_log.pop(0)
435
            for (instname, instgroup, instnodes) in moved:
436
              self.assertTrue(instname in info_args)
437
              if use_nodes:
438
                for i in instnodes:
439
                  self.assertTrue(i in info_args)
440
              else:
441
                self.assertTrue(instgroup in info_args)
442

    
443
          self.assertFalse(lu.info_log)
444
          self.assertFalse(lu.warning_log)
445

    
446
          for op in itertools.chain(*result):
447
            if hasattr(op.__class__, "early_release"):
448
              self.assertEqual(op.early_release, early_release)
449
            else:
450
              self.assertFalse(hasattr(op, "early_release"))
451

    
452
  def testFailed(self):
453
    alloc_result = ([], [
454
      ("inst5191.example.com", "errormsg21178"),
455
      ], [])
456
    assert iallocator._NEVAC_RESULT(alloc_result)
457

    
458
    lu = _FakeLU()
459
    self.assertRaises(errors.OpExecError, cmdlib._LoadNodeEvacResult,
460
                      lu, alloc_result, False, False)
461
    self.assertFalse(lu.info_log)
462
    (_, (args, )) = lu.warning_log.pop(0)
463
    self.assertTrue("inst5191.example.com" in args)
464
    self.assertTrue("errormsg21178" in args)
465
    self.assertFalse(lu.warning_log)
466

    
467

    
468
class TestUpdateAndVerifySubDict(unittest.TestCase):
469
  def setUp(self):
470
    self.type_check = {
471
        "a": constants.VTYPE_INT,
472
        "b": constants.VTYPE_STRING,
473
        "c": constants.VTYPE_BOOL,
474
        "d": constants.VTYPE_STRING,
475
        }
476

    
477
  def test(self):
478
    old_test = {
479
      "foo": {
480
        "d": "blubb",
481
        "a": 321,
482
        },
483
      "baz": {
484
        "a": 678,
485
        "b": "678",
486
        "c": True,
487
        },
488
      }
489
    test = {
490
      "foo": {
491
        "a": 123,
492
        "b": "123",
493
        "c": True,
494
        },
495
      "bar": {
496
        "a": 321,
497
        "b": "321",
498
        "c": False,
499
        },
500
      }
501

    
502
    mv = {
503
      "foo": {
504
        "a": 123,
505
        "b": "123",
506
        "c": True,
507
        "d": "blubb"
508
        },
509
      "bar": {
510
        "a": 321,
511
        "b": "321",
512
        "c": False,
513
        },
514
      "baz": {
515
        "a": 678,
516
        "b": "678",
517
        "c": True,
518
        },
519
      }
520

    
521
    verified = cmdlib._UpdateAndVerifySubDict(old_test, test, self.type_check)
522
    self.assertEqual(verified, mv)
523

    
524
  def testWrong(self):
525
    test = {
526
      "foo": {
527
        "a": "blubb",
528
        "b": "123",
529
        "c": True,
530
        },
531
      "bar": {
532
        "a": 321,
533
        "b": "321",
534
        "c": False,
535
        },
536
      }
537

    
538
    self.assertRaises(errors.TypeEnforcementError,
539
                      cmdlib._UpdateAndVerifySubDict, {}, test, self.type_check)
540

    
541

    
542
class TestHvStateHelper(unittest.TestCase):
543
  def testWithoutOpData(self):
544
    self.assertEqual(cmdlib._MergeAndVerifyHvState(None, NotImplemented), None)
545

    
546
  def testWithoutOldData(self):
547
    new = {
548
      constants.HT_XEN_PVM: {
549
        constants.HVST_MEMORY_TOTAL: 4096,
550
        },
551
      }
552
    self.assertEqual(cmdlib._MergeAndVerifyHvState(new, None), new)
553

    
554
  def testWithWrongHv(self):
555
    new = {
556
      "i-dont-exist": {
557
        constants.HVST_MEMORY_TOTAL: 4096,
558
        },
559
      }
560
    self.assertRaises(errors.OpPrereqError, cmdlib._MergeAndVerifyHvState, new,
561
                      None)
562

    
563
class TestDiskStateHelper(unittest.TestCase):
564
  def testWithoutOpData(self):
565
    self.assertEqual(cmdlib._MergeAndVerifyDiskState(None, NotImplemented),
566
                     None)
567

    
568
  def testWithoutOldData(self):
569
    new = {
570
      constants.LD_LV: {
571
        "xenvg": {
572
          constants.DS_DISK_RESERVED: 1024,
573
          },
574
        },
575
      }
576
    self.assertEqual(cmdlib._MergeAndVerifyDiskState(new, None), new)
577

    
578
  def testWithWrongStorageType(self):
579
    new = {
580
      "i-dont-exist": {
581
        "xenvg": {
582
          constants.DS_DISK_RESERVED: 1024,
583
          },
584
        },
585
      }
586
    self.assertRaises(errors.OpPrereqError, cmdlib._MergeAndVerifyDiskState,
587
                      new, None)
588

    
589

    
590
class TestComputeMinMaxSpec(unittest.TestCase):
591
  def setUp(self):
592
    self.ipolicy = {
593
      constants.ISPECS_MAX: {
594
        constants.ISPEC_MEM_SIZE: 512,
595
        constants.ISPEC_DISK_SIZE: 1024,
596
        },
597
      constants.ISPECS_MIN: {
598
        constants.ISPEC_MEM_SIZE: 128,
599
        constants.ISPEC_DISK_COUNT: 1,
600
        },
601
      }
602

    
603
  def testNoneValue(self):
604
    self.assertTrue(cmdlib._ComputeMinMaxSpec(constants.ISPEC_MEM_SIZE, None,
605
                                              self.ipolicy, None) is None)
606

    
607
  def testAutoValue(self):
608
    self.assertTrue(cmdlib._ComputeMinMaxSpec(constants.ISPEC_MEM_SIZE, None,
609
                                              self.ipolicy,
610
                                              constants.VALUE_AUTO) is None)
611

    
612
  def testNotDefined(self):
613
    self.assertTrue(cmdlib._ComputeMinMaxSpec(constants.ISPEC_NIC_COUNT, None,
614
                                              self.ipolicy, 3) is None)
615

    
616
  def testNoMinDefined(self):
617
    self.assertTrue(cmdlib._ComputeMinMaxSpec(constants.ISPEC_DISK_SIZE, None,
618
                                              self.ipolicy, 128) is None)
619

    
620
  def testNoMaxDefined(self):
621
    self.assertTrue(cmdlib._ComputeMinMaxSpec(constants.ISPEC_DISK_COUNT, None,
622
                                                self.ipolicy, 16) is None)
623

    
624
  def testOutOfRange(self):
625
    for (name, val) in ((constants.ISPEC_MEM_SIZE, 64),
626
                        (constants.ISPEC_MEM_SIZE, 768),
627
                        (constants.ISPEC_DISK_SIZE, 4096),
628
                        (constants.ISPEC_DISK_COUNT, 0)):
629
      min_v = self.ipolicy[constants.ISPECS_MIN].get(name, val)
630
      max_v = self.ipolicy[constants.ISPECS_MAX].get(name, val)
631
      self.assertEqual(cmdlib._ComputeMinMaxSpec(name, None,
632
                                                 self.ipolicy, val),
633
                       "%s value %s is not in range [%s, %s]" %
634
                       (name, val,min_v, max_v))
635
      self.assertEqual(cmdlib._ComputeMinMaxSpec(name, "1",
636
                                                 self.ipolicy, val),
637
                       "%s/1 value %s is not in range [%s, %s]" %
638
                       (name, val,min_v, max_v))
639

    
640
  def test(self):
641
    for (name, val) in ((constants.ISPEC_MEM_SIZE, 256),
642
                        (constants.ISPEC_MEM_SIZE, 128),
643
                        (constants.ISPEC_MEM_SIZE, 512),
644
                        (constants.ISPEC_DISK_SIZE, 1024),
645
                        (constants.ISPEC_DISK_SIZE, 0),
646
                        (constants.ISPEC_DISK_COUNT, 1),
647
                        (constants.ISPEC_DISK_COUNT, 5)):
648
      self.assertTrue(cmdlib._ComputeMinMaxSpec(name, None, self.ipolicy, val)
649
                      is None)
650

    
651

    
652
def _ValidateComputeMinMaxSpec(name, *_):
653
  assert name in constants.ISPECS_PARAMETERS
654
  return None
655

    
656

    
657
class _SpecWrapper:
658
  def __init__(self, spec):
659
    self.spec = spec
660

    
661
  def ComputeMinMaxSpec(self, *args):
662
    return self.spec.pop(0)
663

    
664

    
665
class TestComputeIPolicySpecViolation(unittest.TestCase):
666
  def test(self):
667
    compute_fn = _ValidateComputeMinMaxSpec
668
    ret = cmdlib._ComputeIPolicySpecViolation(NotImplemented, 1024, 1, 1, 1,
669
                                              [1024], 1, _compute_fn=compute_fn)
670
    self.assertEqual(ret, [])
671

    
672
  def testInvalidArguments(self):
673
    self.assertRaises(AssertionError, cmdlib._ComputeIPolicySpecViolation,
674
                      NotImplemented, 1024, 1, 1, 1, [], 1)
675

    
676
  def testInvalidSpec(self):
677
    spec = _SpecWrapper([None, False, "foo", None, "bar", None])
678
    compute_fn = spec.ComputeMinMaxSpec
679
    ret = cmdlib._ComputeIPolicySpecViolation(NotImplemented, 1024, 1, 1, 1,
680
                                              [1024], 1, _compute_fn=compute_fn)
681
    self.assertEqual(ret, ["foo", "bar"])
682
    self.assertFalse(spec.spec)
683

    
684

    
685
class _StubComputeIPolicySpecViolation:
686
  def __init__(self, mem_size, cpu_count, disk_count, nic_count, disk_sizes,
687
               spindle_use):
688
    self.mem_size = mem_size
689
    self.cpu_count = cpu_count
690
    self.disk_count = disk_count
691
    self.nic_count = nic_count
692
    self.disk_sizes = disk_sizes
693
    self.spindle_use = spindle_use
694

    
695
  def __call__(self, _, mem_size, cpu_count, disk_count, nic_count, disk_sizes,
696
               spindle_use):
697
    assert self.mem_size == mem_size
698
    assert self.cpu_count == cpu_count
699
    assert self.disk_count == disk_count
700
    assert self.nic_count == nic_count
701
    assert self.disk_sizes == disk_sizes
702
    assert self.spindle_use == spindle_use
703

    
704
    return []
705

    
706

    
707
class TestComputeIPolicyInstanceViolation(unittest.TestCase):
708
  def test(self):
709
    beparams = {
710
      constants.BE_MAXMEM: 2048,
711
      constants.BE_VCPUS: 2,
712
      constants.BE_SPINDLE_USE: 4,
713
      }
714
    disks = [objects.Disk(size=512)]
715
    instance = objects.Instance(beparams=beparams, disks=disks, nics=[])
716
    stub = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512], 4)
717
    ret = cmdlib._ComputeIPolicyInstanceViolation(NotImplemented, instance,
718
                                                  _compute_fn=stub)
719
    self.assertEqual(ret, [])
720

    
721

    
722
class TestComputeIPolicyInstanceSpecViolation(unittest.TestCase):
723
  def test(self):
724
    ispec = {
725
      constants.ISPEC_MEM_SIZE: 2048,
726
      constants.ISPEC_CPU_COUNT: 2,
727
      constants.ISPEC_DISK_COUNT: 1,
728
      constants.ISPEC_DISK_SIZE: [512],
729
      constants.ISPEC_NIC_COUNT: 0,
730
      constants.ISPEC_SPINDLE_USE: 1,
731
      }
732
    stub = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512], 1)
733
    ret = cmdlib._ComputeIPolicyInstanceSpecViolation(NotImplemented, ispec,
734
                                                      _compute_fn=stub)
735
    self.assertEqual(ret, [])
736

    
737

    
738
class _CallRecorder:
739
  def __init__(self, return_value=None):
740
    self.called = False
741
    self.return_value = return_value
742

    
743
  def __call__(self, *args):
744
    self.called = True
745
    return self.return_value
746

    
747

    
748
class TestComputeIPolicyNodeViolation(unittest.TestCase):
749
  def setUp(self):
750
    self.recorder = _CallRecorder(return_value=[])
751

    
752
  def testSameGroup(self):
753
    ret = cmdlib._ComputeIPolicyNodeViolation(NotImplemented, NotImplemented,
754
                                              "foo", "foo",
755
                                              _compute_fn=self.recorder)
756
    self.assertFalse(self.recorder.called)
757
    self.assertEqual(ret, [])
758

    
759
  def testDifferentGroup(self):
760
    ret = cmdlib._ComputeIPolicyNodeViolation(NotImplemented, NotImplemented,
761
                                              "foo", "bar",
762
                                              _compute_fn=self.recorder)
763
    self.assertTrue(self.recorder.called)
764
    self.assertEqual(ret, [])
765

    
766

    
767
class _FakeConfigForTargetNodeIPolicy:
768
  def __init__(self, node_info=NotImplemented):
769
    self._node_info = node_info
770

    
771
  def GetNodeInfo(self, _):
772
    return self._node_info
773

    
774

    
775
class TestCheckTargetNodeIPolicy(unittest.TestCase):
776
  def setUp(self):
777
    self.instance = objects.Instance(primary_node="blubb")
778
    self.target_node = objects.Node(group="bar")
779
    node_info = objects.Node(group="foo")
780
    fake_cfg = _FakeConfigForTargetNodeIPolicy(node_info=node_info)
781
    self.lu = _FakeLU(cfg=fake_cfg)
782

    
783
  def testNoViolation(self):
784
    compute_recoder = _CallRecorder(return_value=[])
785
    cmdlib._CheckTargetNodeIPolicy(self.lu, NotImplemented, self.instance,
786
                                   self.target_node,
787
                                   _compute_fn=compute_recoder)
788
    self.assertTrue(compute_recoder.called)
789
    self.assertEqual(self.lu.warning_log, [])
790

    
791
  def testNoIgnore(self):
792
    compute_recoder = _CallRecorder(return_value=["mem_size not in range"])
793
    self.assertRaises(errors.OpPrereqError, cmdlib._CheckTargetNodeIPolicy,
794
                      self.lu, NotImplemented, self.instance, self.target_node,
795
                      _compute_fn=compute_recoder)
796
    self.assertTrue(compute_recoder.called)
797
    self.assertEqual(self.lu.warning_log, [])
798

    
799
  def testIgnoreViolation(self):
800
    compute_recoder = _CallRecorder(return_value=["mem_size not in range"])
801
    cmdlib._CheckTargetNodeIPolicy(self.lu, NotImplemented, self.instance,
802
                                   self.target_node, ignore=True,
803
                                   _compute_fn=compute_recoder)
804
    self.assertTrue(compute_recoder.called)
805
    msg = ("Instance does not meet target node group's (bar) instance policy:"
806
           " mem_size not in range")
807
    self.assertEqual(self.lu.warning_log, [(msg, ())])
808

    
809

    
810
class TestApplyContainerMods(unittest.TestCase):
811
  def testEmptyContainer(self):
812
    container = []
813
    chgdesc = []
814
    cmdlib.ApplyContainerMods("test", container, chgdesc, [], None, None, None)
815
    self.assertEqual(container, [])
816
    self.assertEqual(chgdesc, [])
817

    
818
  def testAdd(self):
819
    container = []
820
    chgdesc = []
821
    mods = cmdlib.PrepareContainerMods([
822
      (constants.DDM_ADD, -1, "Hello"),
823
      (constants.DDM_ADD, -1, "World"),
824
      (constants.DDM_ADD, 0, "Start"),
825
      (constants.DDM_ADD, -1, "End"),
826
      ], None)
827
    cmdlib.ApplyContainerMods("test", container, chgdesc, mods,
828
                              None, None, None)
829
    self.assertEqual(container, ["Start", "Hello", "World", "End"])
830
    self.assertEqual(chgdesc, [])
831

    
832
    mods = cmdlib.PrepareContainerMods([
833
      (constants.DDM_ADD, 0, "zero"),
834
      (constants.DDM_ADD, 3, "Added"),
835
      (constants.DDM_ADD, 5, "four"),
836
      (constants.DDM_ADD, 7, "xyz"),
837
      ], None)
838
    cmdlib.ApplyContainerMods("test", container, chgdesc, mods,
839
                              None, None, None)
840
    self.assertEqual(container,
841
                     ["zero", "Start", "Hello", "Added", "World", "four",
842
                      "End", "xyz"])
843
    self.assertEqual(chgdesc, [])
844

    
845
    for idx in [-2, len(container) + 1]:
846
      mods = cmdlib.PrepareContainerMods([
847
        (constants.DDM_ADD, idx, "error"),
848
        ], None)
849
      self.assertRaises(IndexError, cmdlib.ApplyContainerMods,
850
                        "test", container, None, mods, None, None, None)
851

    
852
  def testRemoveError(self):
853
    for idx in [0, 1, 2, 100, -1, -4]:
854
      mods = cmdlib.PrepareContainerMods([
855
        (constants.DDM_REMOVE, idx, None),
856
        ], None)
857
      self.assertRaises(IndexError, cmdlib.ApplyContainerMods,
858
                        "test", [], None, mods, None, None, None)
859

    
860
    mods = cmdlib.PrepareContainerMods([
861
      (constants.DDM_REMOVE, 0, object()),
862
      ], None)
863
    self.assertRaises(AssertionError, cmdlib.ApplyContainerMods,
864
                      "test", [""], None, mods, None, None, None)
865

    
866
  def testAddError(self):
867
    for idx in range(-100, -1) + [100]:
868
      mods = cmdlib.PrepareContainerMods([
869
        (constants.DDM_ADD, idx, None),
870
        ], None)
871
      self.assertRaises(IndexError, cmdlib.ApplyContainerMods,
872
                        "test", [], None, mods, None, None, None)
873

    
874
  def testRemove(self):
875
    container = ["item 1", "item 2"]
876
    mods = cmdlib.PrepareContainerMods([
877
      (constants.DDM_ADD, -1, "aaa"),
878
      (constants.DDM_REMOVE, -1, None),
879
      (constants.DDM_ADD, -1, "bbb"),
880
      ], None)
881
    chgdesc = []
882
    cmdlib.ApplyContainerMods("test", container, chgdesc, mods,
883
                              None, None, None)
884
    self.assertEqual(container, ["item 1", "item 2", "bbb"])
885
    self.assertEqual(chgdesc, [
886
      ("test/2", "remove"),
887
      ])
888

    
889
  def testModify(self):
890
    container = ["item 1", "item 2"]
891
    mods = cmdlib.PrepareContainerMods([
892
      (constants.DDM_MODIFY, -1, "a"),
893
      (constants.DDM_MODIFY, 0, "b"),
894
      (constants.DDM_MODIFY, 1, "c"),
895
      ], None)
896
    chgdesc = []
897
    cmdlib.ApplyContainerMods("test", container, chgdesc, mods,
898
                              None, None, None)
899
    self.assertEqual(container, ["item 1", "item 2"])
900
    self.assertEqual(chgdesc, [])
901

    
902
    for idx in [-2, len(container) + 1]:
903
      mods = cmdlib.PrepareContainerMods([
904
        (constants.DDM_MODIFY, idx, "error"),
905
        ], None)
906
      self.assertRaises(IndexError, cmdlib.ApplyContainerMods,
907
                        "test", container, None, mods, None, None, None)
908

    
909
  class _PrivateData:
910
    def __init__(self):
911
      self.data = None
912

    
913
  @staticmethod
914
  def _CreateTestFn(idx, params, private):
915
    private.data = ("add", idx, params)
916
    return ((100 * idx, params), [
917
      ("test/%s" % idx, hex(idx)),
918
      ])
919

    
920
  @staticmethod
921
  def _ModifyTestFn(idx, item, params, private):
922
    private.data = ("modify", idx, params)
923
    return [
924
      ("test/%s" % idx, "modify %s" % params),
925
      ]
926

    
927
  @staticmethod
928
  def _RemoveTestFn(idx, item, private):
929
    private.data = ("remove", idx, item)
930

    
931
  def testAddWithCreateFunction(self):
932
    container = []
933
    chgdesc = []
934
    mods = cmdlib.PrepareContainerMods([
935
      (constants.DDM_ADD, -1, "Hello"),
936
      (constants.DDM_ADD, -1, "World"),
937
      (constants.DDM_ADD, 0, "Start"),
938
      (constants.DDM_ADD, -1, "End"),
939
      (constants.DDM_REMOVE, 2, None),
940
      (constants.DDM_MODIFY, -1, "foobar"),
941
      (constants.DDM_REMOVE, 2, None),
942
      (constants.DDM_ADD, 1, "More"),
943
      ], self._PrivateData)
944
    cmdlib.ApplyContainerMods("test", container, chgdesc, mods,
945
      self._CreateTestFn, self._ModifyTestFn, self._RemoveTestFn)
946
    self.assertEqual(container, [
947
      (000, "Start"),
948
      (100, "More"),
949
      (000, "Hello"),
950
      ])
951
    self.assertEqual(chgdesc, [
952
      ("test/0", "0x0"),
953
      ("test/1", "0x1"),
954
      ("test/0", "0x0"),
955
      ("test/3", "0x3"),
956
      ("test/2", "remove"),
957
      ("test/2", "modify foobar"),
958
      ("test/2", "remove"),
959
      ("test/1", "0x1")
960
      ])
961
    self.assertTrue(compat.all(op == private.data[0]
962
                               for (op, _, _, private) in mods))
963
    self.assertEqual([private.data for (op, _, _, private) in mods], [
964
      ("add", 0, "Hello"),
965
      ("add", 1, "World"),
966
      ("add", 0, "Start"),
967
      ("add", 3, "End"),
968
      ("remove", 2, (100, "World")),
969
      ("modify", 2, "foobar"),
970
      ("remove", 2, (300, "End")),
971
      ("add", 1, "More"),
972
      ])
973

    
974

    
975
class _FakeConfigForGenDiskTemplate:
976
  def __init__(self):
977
    self._unique_id = itertools.count()
978
    self._drbd_minor = itertools.count(20)
979
    self._port = itertools.count(constants.FIRST_DRBD_PORT)
980
    self._secret = itertools.count()
981

    
982
  def GetVGName(self):
983
    return "testvg"
984

    
985
  def GenerateUniqueID(self, ec_id):
986
    return "ec%s-uq%s" % (ec_id, self._unique_id.next())
987

    
988
  def AllocateDRBDMinor(self, nodes, instance):
989
    return [self._drbd_minor.next()
990
            for _ in nodes]
991

    
992
  def AllocatePort(self):
993
    return self._port.next()
994

    
995
  def GenerateDRBDSecret(self, ec_id):
996
    return "ec%s-secret%s" % (ec_id, self._secret.next())
997

    
998
  def GetInstanceInfo(self, _):
999
    return "foobar"
1000

    
1001

    
1002
class _FakeProcForGenDiskTemplate:
1003
  def GetECId(self):
1004
    return 0
1005

    
1006

    
1007
class TestGenerateDiskTemplate(unittest.TestCase):
1008
  def setUp(self):
1009
    nodegroup = objects.NodeGroup(name="ng")
1010
    nodegroup.UpgradeConfig()
1011

    
1012
    cfg = _FakeConfigForGenDiskTemplate()
1013
    proc = _FakeProcForGenDiskTemplate()
1014

    
1015
    self.lu = _FakeLU(cfg=cfg, proc=proc)
1016
    self.nodegroup = nodegroup
1017

    
1018
  @staticmethod
1019
  def GetDiskParams():
1020
    return copy.deepcopy(constants.DISK_DT_DEFAULTS)
1021

    
1022
  def testWrongDiskTemplate(self):
1023
    gdt = cmdlib._GenerateDiskTemplate
1024
    disk_template = "##unknown##"
1025

    
1026
    assert disk_template not in constants.DISK_TEMPLATES
1027

    
1028
    self.assertRaises(errors.ProgrammerError, gdt, self.lu, disk_template,
1029
                      "inst26831.example.com", "node30113.example.com", [], [],
1030
                      NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1031
                      self.GetDiskParams())
1032

    
1033
  def testDiskless(self):
1034
    gdt = cmdlib._GenerateDiskTemplate
1035

    
1036
    result = gdt(self.lu, constants.DT_DISKLESS, "inst27734.example.com",
1037
                 "node30113.example.com", [], [],
1038
                 NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1039
                 self.GetDiskParams())
1040
    self.assertEqual(result, [])
1041

    
1042
  def _TestTrivialDisk(self, template, disk_info, base_index, exp_dev_type,
1043
                       file_storage_dir=NotImplemented,
1044
                       file_driver=NotImplemented,
1045
                       req_file_storage=NotImplemented,
1046
                       req_shr_file_storage=NotImplemented):
1047
    gdt = cmdlib._GenerateDiskTemplate
1048

    
1049
    map(lambda params: utils.ForceDictType(params,
1050
                                           constants.IDISK_PARAMS_TYPES),
1051
        disk_info)
1052

    
1053
    # Check if non-empty list of secondaries is rejected
1054
    self.assertRaises(errors.ProgrammerError, gdt, self.lu,
1055
                      template, "inst25088.example.com",
1056
                      "node185.example.com", ["node323.example.com"], [],
1057
                      NotImplemented, NotImplemented, base_index,
1058
                      self.lu.LogInfo, self.GetDiskParams(),
1059
                      _req_file_storage=req_file_storage,
1060
                      _req_shr_file_storage=req_shr_file_storage)
1061

    
1062
    result = gdt(self.lu, template, "inst21662.example.com",
1063
                 "node21741.example.com", [],
1064
                 disk_info, file_storage_dir, file_driver, base_index,
1065
                 self.lu.LogInfo, self.GetDiskParams(),
1066
                 _req_file_storage=req_file_storage,
1067
                 _req_shr_file_storage=req_shr_file_storage)
1068

    
1069
    for (idx, disk) in enumerate(result):
1070
      self.assertTrue(isinstance(disk, objects.Disk))
1071
      self.assertEqual(disk.dev_type, exp_dev_type)
1072
      self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
1073
      self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
1074
      self.assertTrue(disk.children is None)
1075

    
1076
    self._CheckIvNames(result, base_index, base_index + len(disk_info))
1077
    cmdlib._UpdateIvNames(base_index, result)
1078
    self._CheckIvNames(result, base_index, base_index + len(disk_info))
1079

    
1080
    return result
1081

    
1082
  def _CheckIvNames(self, disks, base_index, end_index):
1083
    self.assertEqual(map(operator.attrgetter("iv_name"), disks),
1084
                     ["disk/%s" % i for i in range(base_index, end_index)])
1085

    
1086
  def testPlain(self):
1087
    disk_info = [{
1088
      constants.IDISK_SIZE: 1024,
1089
      constants.IDISK_MODE: constants.DISK_RDWR,
1090
      }, {
1091
      constants.IDISK_SIZE: 4096,
1092
      constants.IDISK_VG: "othervg",
1093
      constants.IDISK_MODE: constants.DISK_RDWR,
1094
      }]
1095

    
1096
    result = self._TestTrivialDisk(constants.DT_PLAIN, disk_info, 3,
1097
                                   constants.LD_LV)
1098

    
1099
    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1100
      ("testvg", "ec0-uq0.disk3"),
1101
      ("othervg", "ec0-uq1.disk4"),
1102
      ])
1103

    
1104
  @staticmethod
1105
  def _AllowFileStorage():
1106
    pass
1107

    
1108
  @staticmethod
1109
  def _ForbidFileStorage():
1110
    raise errors.OpPrereqError("Disallowed in test")
1111

    
1112
  def testFile(self):
1113
    self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
1114
                      constants.DT_FILE, [], 0, NotImplemented,
1115
                      req_file_storage=self._ForbidFileStorage)
1116
    self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
1117
                      constants.DT_SHARED_FILE, [], 0, NotImplemented,
1118
                      req_shr_file_storage=self._ForbidFileStorage)
1119

    
1120
    for disk_template in [constants.DT_FILE, constants.DT_SHARED_FILE]:
1121
      disk_info = [{
1122
        constants.IDISK_SIZE: 80 * 1024,
1123
        constants.IDISK_MODE: constants.DISK_RDONLY,
1124
        }, {
1125
        constants.IDISK_SIZE: 4096,
1126
        constants.IDISK_MODE: constants.DISK_RDWR,
1127
        }, {
1128
        constants.IDISK_SIZE: 6 * 1024,
1129
        constants.IDISK_MODE: constants.DISK_RDWR,
1130
        }]
1131

    
1132
      result = self._TestTrivialDisk(disk_template, disk_info, 2,
1133
        constants.LD_FILE, file_storage_dir="/tmp",
1134
        file_driver=constants.FD_BLKTAP,
1135
        req_file_storage=self._AllowFileStorage,
1136
        req_shr_file_storage=self._AllowFileStorage)
1137

    
1138
      self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1139
        (constants.FD_BLKTAP, "/tmp/disk2"),
1140
        (constants.FD_BLKTAP, "/tmp/disk3"),
1141
        (constants.FD_BLKTAP, "/tmp/disk4"),
1142
        ])
1143

    
1144
  def testBlock(self):
1145
    disk_info = [{
1146
      constants.IDISK_SIZE: 8 * 1024,
1147
      constants.IDISK_MODE: constants.DISK_RDWR,
1148
      constants.IDISK_ADOPT: "/tmp/some/block/dev",
1149
      }]
1150

    
1151
    result = self._TestTrivialDisk(constants.DT_BLOCK, disk_info, 10,
1152
                                   constants.LD_BLOCKDEV)
1153

    
1154
    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1155
      (constants.BLOCKDEV_DRIVER_MANUAL, "/tmp/some/block/dev"),
1156
      ])
1157

    
1158
  def testRbd(self):
1159
    disk_info = [{
1160
      constants.IDISK_SIZE: 8 * 1024,
1161
      constants.IDISK_MODE: constants.DISK_RDONLY,
1162
      }, {
1163
      constants.IDISK_SIZE: 100 * 1024,
1164
      constants.IDISK_MODE: constants.DISK_RDWR,
1165
      }]
1166

    
1167
    result = self._TestTrivialDisk(constants.DT_RBD, disk_info, 0,
1168
                                   constants.LD_RBD)
1169

    
1170
    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1171
      ("rbd", "ec0-uq0.rbd.disk0"),
1172
      ("rbd", "ec0-uq1.rbd.disk1"),
1173
      ])
1174

    
1175
  def testDrbd8(self):
1176
    gdt = cmdlib._GenerateDiskTemplate
1177
    drbd8_defaults = constants.DISK_LD_DEFAULTS[constants.LD_DRBD8]
1178
    drbd8_default_metavg = drbd8_defaults[constants.LDP_DEFAULT_METAVG]
1179

    
1180
    disk_info = [{
1181
      constants.IDISK_SIZE: 1024,
1182
      constants.IDISK_MODE: constants.DISK_RDWR,
1183
      }, {
1184
      constants.IDISK_SIZE: 100 * 1024,
1185
      constants.IDISK_MODE: constants.DISK_RDONLY,
1186
      constants.IDISK_METAVG: "metavg",
1187
      }, {
1188
      constants.IDISK_SIZE: 4096,
1189
      constants.IDISK_MODE: constants.DISK_RDWR,
1190
      constants.IDISK_VG: "vgxyz",
1191
      },
1192
      ]
1193

    
1194
    exp_logical_ids = [[
1195
      (self.lu.cfg.GetVGName(), "ec0-uq0.disk0_data"),
1196
      (drbd8_default_metavg, "ec0-uq0.disk0_meta"),
1197
      ], [
1198
      (self.lu.cfg.GetVGName(), "ec0-uq1.disk1_data"),
1199
      ("metavg", "ec0-uq1.disk1_meta"),
1200
      ], [
1201
      ("vgxyz", "ec0-uq2.disk2_data"),
1202
      (drbd8_default_metavg, "ec0-uq2.disk2_meta"),
1203
      ]]
1204

    
1205
    assert len(exp_logical_ids) == len(disk_info)
1206

    
1207
    map(lambda params: utils.ForceDictType(params,
1208
                                           constants.IDISK_PARAMS_TYPES),
1209
        disk_info)
1210

    
1211
    # Check if empty list of secondaries is rejected
1212
    self.assertRaises(errors.ProgrammerError, gdt, self.lu, constants.DT_DRBD8,
1213
                      "inst827.example.com", "node1334.example.com", [],
1214
                      disk_info, NotImplemented, NotImplemented, 0,
1215
                      self.lu.LogInfo, self.GetDiskParams())
1216

    
1217
    result = gdt(self.lu, constants.DT_DRBD8, "inst827.example.com",
1218
                 "node1334.example.com", ["node12272.example.com"],
1219
                 disk_info, NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1220
                 self.GetDiskParams())
1221

    
1222
    for (idx, disk) in enumerate(result):
1223
      self.assertTrue(isinstance(disk, objects.Disk))
1224
      self.assertEqual(disk.dev_type, constants.LD_DRBD8)
1225
      self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
1226
      self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
1227

    
1228
      for child in disk.children:
1229
        self.assertTrue(isinstance(disk, objects.Disk))
1230
        self.assertEqual(child.dev_type, constants.LD_LV)
1231
        self.assertTrue(child.children is None)
1232

    
1233
      self.assertEqual(map(operator.attrgetter("logical_id"), disk.children),
1234
                       exp_logical_ids[idx])
1235

    
1236
      self.assertEqual(len(disk.children), 2)
1237
      self.assertEqual(disk.children[0].size, disk.size)
1238
      self.assertEqual(disk.children[1].size, constants.DRBD_META_SIZE)
1239

    
1240
    self._CheckIvNames(result, 0, len(disk_info))
1241
    cmdlib._UpdateIvNames(0, result)
1242
    self._CheckIvNames(result, 0, len(disk_info))
1243

    
1244
    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1245
      ("node1334.example.com", "node12272.example.com",
1246
       constants.FIRST_DRBD_PORT, 20, 21, "ec0-secret0"),
1247
      ("node1334.example.com", "node12272.example.com",
1248
       constants.FIRST_DRBD_PORT + 1, 22, 23, "ec0-secret1"),
1249
      ("node1334.example.com", "node12272.example.com",
1250
       constants.FIRST_DRBD_PORT + 2, 24, 25, "ec0-secret2"),
1251
      ])
1252

    
1253

    
1254
class _ConfigForDiskWipe:
1255
  def __init__(self, exp_node):
1256
    self._exp_node = exp_node
1257

    
1258
  def SetDiskID(self, device, node):
1259
    assert isinstance(device, objects.Disk)
1260
    assert node == self._exp_node
1261

    
1262

    
1263
class _RpcForDiskWipe:
1264
  def __init__(self, exp_node, pause_cb, wipe_cb):
1265
    self._exp_node = exp_node
1266
    self._pause_cb = pause_cb
1267
    self._wipe_cb = wipe_cb
1268

    
1269
  def call_blockdev_pause_resume_sync(self, node, disks, pause):
1270
    assert node == self._exp_node
1271
    return rpc.RpcResult(data=self._pause_cb(disks, pause))
1272

    
1273
  def call_blockdev_wipe(self, node, bdev, offset, size):
1274
    assert node == self._exp_node
1275
    return rpc.RpcResult(data=self._wipe_cb(bdev, offset, size))
1276

    
1277

    
1278
class _DiskPauseTracker:
1279
  def __init__(self):
1280
    self.history = []
1281

    
1282
  def __call__(self, (disks, instance), pause):
1283
    assert not (set(disks) - set(instance.disks))
1284

    
1285
    self.history.extend((i.logical_id, i.size, pause)
1286
                        for i in disks)
1287

    
1288
    return (True, [True] * len(disks))
1289

    
1290

    
1291
class _DiskWipeProgressTracker:
1292
  def __init__(self, start_offset):
1293
    self._start_offset = start_offset
1294
    self.progress = {}
1295

    
1296
  def __call__(self, (disk, _), offset, size):
1297
    assert isinstance(offset, (long, int))
1298
    assert isinstance(size, (long, int))
1299

    
1300
    max_chunk_size = (disk.size / 100.0 * constants.MIN_WIPE_CHUNK_PERCENT)
1301

    
1302
    assert offset >= self._start_offset
1303
    assert (offset + size) <= disk.size
1304

    
1305
    assert size > 0
1306
    assert size <= constants.MAX_WIPE_CHUNK
1307
    assert size <= max_chunk_size
1308

    
1309
    assert offset == self._start_offset or disk.logical_id in self.progress
1310

    
1311
    # Keep track of progress
1312
    cur_progress = self.progress.setdefault(disk.logical_id, self._start_offset)
1313

    
1314
    assert cur_progress == offset
1315

    
1316
    # Record progress
1317
    self.progress[disk.logical_id] += size
1318

    
1319
    return (True, None)
1320

    
1321

    
1322
class TestWipeDisks(unittest.TestCase):
1323
  def _FailingPauseCb(self, (disks, _), pause):
1324
    self.assertEqual(len(disks), 3)
1325
    self.assertTrue(pause)
1326
    # Simulate an RPC error
1327
    return (False, "error")
1328

    
1329
  def testPauseFailure(self):
1330
    node_name = "node1372.example.com"
1331

    
1332
    lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, self._FailingPauseCb,
1333
                                     NotImplemented),
1334
                 cfg=_ConfigForDiskWipe(node_name))
1335

    
1336
    disks = [
1337
      objects.Disk(dev_type=constants.LD_LV),
1338
      objects.Disk(dev_type=constants.LD_LV),
1339
      objects.Disk(dev_type=constants.LD_LV),
1340
      ]
1341

    
1342
    instance = objects.Instance(name="inst21201",
1343
                                primary_node=node_name,
1344
                                disk_template=constants.DT_PLAIN,
1345
                                disks=disks)
1346

    
1347
    self.assertRaises(errors.OpExecError, cmdlib._WipeDisks, lu, instance)
1348

    
1349
  def _FailingWipeCb(self, (disk, _), offset, size):
1350
    # This should only ever be called for the first disk
1351
    self.assertEqual(disk.logical_id, "disk0")
1352
    return (False, None)
1353

    
1354
  def testFailingWipe(self):
1355
    node_name = "node13445.example.com"
1356
    pt = _DiskPauseTracker()
1357

    
1358
    lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, pt, self._FailingWipeCb),
1359
                 cfg=_ConfigForDiskWipe(node_name))
1360

    
1361
    disks = [
1362
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk0",
1363
                   size=100 * 1024),
1364
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk1",
1365
                   size=500 * 1024),
1366
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk2", size=256),
1367
      ]
1368

    
1369
    instance = objects.Instance(name="inst562",
1370
                                primary_node=node_name,
1371
                                disk_template=constants.DT_PLAIN,
1372
                                disks=disks)
1373

    
1374
    try:
1375
      cmdlib._WipeDisks(lu, instance)
1376
    except errors.OpExecError, err:
1377
      self.assertTrue(str(err), "Could not wipe disk 0 at offset 0 ")
1378
    else:
1379
      self.fail("Did not raise exception")
1380

    
1381
    # Check if all disks were paused and resumed
1382
    self.assertEqual(pt.history, [
1383
      ("disk0", 100 * 1024, True),
1384
      ("disk1", 500 * 1024, True),
1385
      ("disk2", 256, True),
1386
      ("disk0", 100 * 1024, False),
1387
      ("disk1", 500 * 1024, False),
1388
      ("disk2", 256, False),
1389
      ])
1390

    
1391
  def _PrepareWipeTest(self, start_offset, disks):
1392
    node_name = "node-with-offset%s.example.com" % start_offset
1393
    pauset = _DiskPauseTracker()
1394
    progresst = _DiskWipeProgressTracker(start_offset)
1395

    
1396
    lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, pauset, progresst),
1397
                 cfg=_ConfigForDiskWipe(node_name))
1398

    
1399
    instance = objects.Instance(name="inst3560",
1400
                                primary_node=node_name,
1401
                                disk_template=constants.DT_PLAIN,
1402
                                disks=disks)
1403

    
1404
    return (lu, instance, pauset, progresst)
1405

    
1406
  def testNormalWipe(self):
1407
    disks = [
1408
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk0", size=1024),
1409
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk1",
1410
                   size=500 * 1024),
1411
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk2", size=128),
1412
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk3",
1413
                   size=constants.MAX_WIPE_CHUNK),
1414
      ]
1415

    
1416
    (lu, instance, pauset, progresst) = self._PrepareWipeTest(0, disks)
1417

    
1418
    cmdlib._WipeDisks(lu, instance)
1419

    
1420
    self.assertEqual(pauset.history, [
1421
      ("disk0", 1024, True),
1422
      ("disk1", 500 * 1024, True),
1423
      ("disk2", 128, True),
1424
      ("disk3", constants.MAX_WIPE_CHUNK, True),
1425
      ("disk0", 1024, False),
1426
      ("disk1", 500 * 1024, False),
1427
      ("disk2", 128, False),
1428
      ("disk3", constants.MAX_WIPE_CHUNK, False),
1429
      ])
1430

    
1431
    # Ensure the complete disk has been wiped
1432
    self.assertEqual(progresst.progress,
1433
                     dict((i.logical_id, i.size) for i in disks))
1434

    
1435
  def testWipeWithStartOffset(self):
1436
    for start_offset in [0, 280, 8895, 1563204]:
1437
      disks = [
1438
        objects.Disk(dev_type=constants.LD_LV, logical_id="disk0",
1439
                     size=128),
1440
        objects.Disk(dev_type=constants.LD_LV, logical_id="disk1",
1441
                     size=start_offset + (100 * 1024)),
1442
        ]
1443

    
1444
      (lu, instance, pauset, progresst) = \
1445
        self._PrepareWipeTest(start_offset, disks)
1446

    
1447
      # Test start offset with only one disk
1448
      cmdlib._WipeDisks(lu, instance,
1449
                        disks=[(1, disks[1], start_offset)])
1450

    
1451
      # Only the second disk may have been paused and wiped
1452
      self.assertEqual(pauset.history, [
1453
        ("disk1", start_offset + (100 * 1024), True),
1454
        ("disk1", start_offset + (100 * 1024), False),
1455
        ])
1456
      self.assertEqual(progresst.progress, {
1457
        "disk1": disks[1].size,
1458
        })
1459

    
1460

    
1461
class TestDiskSizeInBytesToMebibytes(unittest.TestCase):
1462
  def testLessThanOneMebibyte(self):
1463
    for i in [1, 2, 7, 512, 1000, 1023]:
1464
      lu = _FakeLU()
1465
      result = cmdlib._DiskSizeInBytesToMebibytes(lu, i)
1466
      self.assertEqual(result, 1)
1467
      self.assertEqual(len(lu.warning_log), 1)
1468
      self.assertEqual(len(lu.warning_log[0]), 2)
1469
      (_, (warnsize, )) = lu.warning_log[0]
1470
      self.assertEqual(warnsize, (1024 * 1024) - i)
1471

    
1472
  def testEven(self):
1473
    for i in [1, 2, 7, 512, 1000, 1023]:
1474
      lu = _FakeLU()
1475
      result = cmdlib._DiskSizeInBytesToMebibytes(lu, i * 1024 * 1024)
1476
      self.assertEqual(result, i)
1477
      self.assertFalse(lu.warning_log)
1478

    
1479
  def testLargeNumber(self):
1480
    for i in [1, 2, 7, 512, 1000, 1023, 2724, 12420]:
1481
      for j in [1, 2, 486, 326, 986, 1023]:
1482
        lu = _FakeLU()
1483
        size = (1024 * 1024 * i) + j
1484
        result = cmdlib._DiskSizeInBytesToMebibytes(lu, size)
1485
        self.assertEqual(result, i + 1, msg="Amount was not rounded up")
1486
        self.assertEqual(len(lu.warning_log), 1)
1487
        self.assertEqual(len(lu.warning_log[0]), 2)
1488
        (_, (warnsize, )) = lu.warning_log[0]
1489
        self.assertEqual(warnsize, (1024 * 1024) - j)
1490

    
1491

    
1492
class TestCopyLockList(unittest.TestCase):
1493
  def test(self):
1494
    self.assertEqual(cmdlib._CopyLockList([]), [])
1495
    self.assertEqual(cmdlib._CopyLockList(None), None)
1496
    self.assertEqual(cmdlib._CopyLockList(locking.ALL_SET), locking.ALL_SET)
1497

    
1498
    names = ["foo", "bar"]
1499
    output = cmdlib._CopyLockList(names)
1500
    self.assertEqual(names, output)
1501
    self.assertNotEqual(id(names), id(output), msg="List was not copied")
1502

    
1503

    
1504
class TestCheckOpportunisticLocking(unittest.TestCase):
1505
  class OpTest(opcodes.OpCode):
1506
    OP_PARAMS = [
1507
      opcodes._POpportunisticLocking,
1508
      opcodes._PIAllocFromDesc(""),
1509
      ]
1510

    
1511
  @classmethod
1512
  def _MakeOp(cls, **kwargs):
1513
    op = cls.OpTest(**kwargs)
1514
    op.Validate(True)
1515
    return op
1516

    
1517
  def testMissingAttributes(self):
1518
    self.assertRaises(AttributeError, cmdlib._CheckOpportunisticLocking,
1519
                      object())
1520

    
1521
  def testDefaults(self):
1522
    op = self._MakeOp()
1523
    cmdlib._CheckOpportunisticLocking(op)
1524

    
1525
  def test(self):
1526
    for iallocator in [None, "something", "other"]:
1527
      for opplock in [False, True]:
1528
        op = self._MakeOp(iallocator=iallocator, opportunistic_locking=opplock)
1529
        if opplock and not iallocator:
1530
          self.assertRaises(errors.OpPrereqError,
1531
                            cmdlib._CheckOpportunisticLocking, op)
1532
        else:
1533
          cmdlib._CheckOpportunisticLocking(op)
1534

    
1535

    
1536
class _OpTestVerifyErrors(opcodes.OpCode):
1537
  OP_PARAMS = [
1538
    opcodes._PDebugSimulateErrors,
1539
    opcodes._PErrorCodes,
1540
    opcodes._PIgnoreErrors,
1541
    ]
1542

    
1543

    
1544
class _LuTestVerifyErrors(cmdlib._VerifyErrors):
1545
  def __init__(self, **kwargs):
1546
    cmdlib._VerifyErrors.__init__(self)
1547
    self.op = _OpTestVerifyErrors(**kwargs)
1548
    self.op.Validate(True)
1549
    self.msglist = []
1550
    self._feedback_fn = self.msglist.append
1551
    self.bad = False
1552

    
1553
  def DispatchCallError(self, which, *args, **kwargs):
1554
    if which:
1555
      self._Error(*args, **kwargs)
1556
    else:
1557
      self._ErrorIf(True, *args, **kwargs)
1558

    
1559
  def CallErrorIf(self, c, *args, **kwargs):
1560
    self._ErrorIf(c, *args, **kwargs)
1561

    
1562

    
1563
class TestVerifyErrors(unittest.TestCase):
1564
  # Fake cluster-verify error code structures; we use two arbitary real error
1565
  # codes to pass validation of ignore_errors
1566
  (_, _ERR1ID, _) = constants.CV_ECLUSTERCFG
1567
  _NODESTR = "node"
1568
  _NODENAME = "mynode"
1569
  _ERR1CODE = (_NODESTR, _ERR1ID, "Error one")
1570
  (_, _ERR2ID, _) = constants.CV_ECLUSTERCERT
1571
  _INSTSTR = "instance"
1572
  _INSTNAME = "myinstance"
1573
  _ERR2CODE = (_INSTSTR, _ERR2ID, "Error two")
1574
  # Arguments used to call _Error() or _ErrorIf()
1575
  _ERR1ARGS = (_ERR1CODE, _NODENAME, "Error1 is %s", "an error")
1576
  _ERR2ARGS = (_ERR2CODE, _INSTNAME, "Error2 has no argument")
1577
  # Expected error messages
1578
  _ERR1MSG = _ERR1ARGS[2] % _ERR1ARGS[3]
1579
  _ERR2MSG = _ERR2ARGS[2]
1580

    
1581
  def testNoError(self):
1582
    lu = _LuTestVerifyErrors()
1583
    lu.CallErrorIf(False, self._ERR1CODE, *self._ERR1ARGS)
1584
    self.assertFalse(lu.bad)
1585
    self.assertFalse(lu.msglist)
1586

    
1587
  def _InitTest(self, **kwargs):
1588
    self.lu1 = _LuTestVerifyErrors(**kwargs)
1589
    self.lu2 = _LuTestVerifyErrors(**kwargs)
1590

    
1591
  def _CallError(self, *args, **kwargs):
1592
    # Check that _Error() and _ErrorIf() produce the same results
1593
    self.lu1.DispatchCallError(True, *args, **kwargs)
1594
    self.lu2.DispatchCallError(False, *args, **kwargs)
1595
    self.assertEqual(self.lu1.bad, self.lu2.bad)
1596
    self.assertEqual(self.lu1.msglist, self.lu2.msglist)
1597
    # Test-specific checks are made on one LU
1598
    return self.lu1
1599

    
1600
  def _checkMsgCommon(self, logstr, errmsg, itype, item, warning):
1601
    self.assertTrue(errmsg in logstr)
1602
    if warning:
1603
      self.assertTrue("WARNING" in logstr)
1604
    else:
1605
      self.assertTrue("ERROR" in logstr)
1606
    self.assertTrue(itype in logstr)
1607
    self.assertTrue(item in logstr)
1608

    
1609
  def _checkMsg1(self, logstr, warning=False):
1610
    self._checkMsgCommon(logstr, self._ERR1MSG, self._NODESTR,
1611
                         self._NODENAME, warning)
1612

    
1613
  def _checkMsg2(self, logstr, warning=False):
1614
    self._checkMsgCommon(logstr, self._ERR2MSG, self._INSTSTR,
1615
                         self._INSTNAME, warning)
1616

    
1617
  def testPlain(self):
1618
    self._InitTest()
1619
    lu = self._CallError(*self._ERR1ARGS)
1620
    self.assertTrue(lu.bad)
1621
    self.assertEqual(len(lu.msglist), 1)
1622
    self._checkMsg1(lu.msglist[0])
1623

    
1624
  def testMultiple(self):
1625
    self._InitTest()
1626
    self._CallError(*self._ERR1ARGS)
1627
    lu = self._CallError(*self._ERR2ARGS)
1628
    self.assertTrue(lu.bad)
1629
    self.assertEqual(len(lu.msglist), 2)
1630
    self._checkMsg1(lu.msglist[0])
1631
    self._checkMsg2(lu.msglist[1])
1632

    
1633
  def testIgnore(self):
1634
    self._InitTest(ignore_errors=[self._ERR1ID])
1635
    lu = self._CallError(*self._ERR1ARGS)
1636
    self.assertFalse(lu.bad)
1637
    self.assertEqual(len(lu.msglist), 1)
1638
    self._checkMsg1(lu.msglist[0], warning=True)
1639

    
1640
  def testWarning(self):
1641
    self._InitTest()
1642
    lu = self._CallError(*self._ERR1ARGS,
1643
                         code=_LuTestVerifyErrors.ETYPE_WARNING)
1644
    self.assertFalse(lu.bad)
1645
    self.assertEqual(len(lu.msglist), 1)
1646
    self._checkMsg1(lu.msglist[0], warning=True)
1647

    
1648
  def testWarning2(self):
1649
    self._InitTest()
1650
    self._CallError(*self._ERR1ARGS)
1651
    lu = self._CallError(*self._ERR2ARGS,
1652
                         code=_LuTestVerifyErrors.ETYPE_WARNING)
1653
    self.assertTrue(lu.bad)
1654
    self.assertEqual(len(lu.msglist), 2)
1655
    self._checkMsg1(lu.msglist[0])
1656
    self._checkMsg2(lu.msglist[1], warning=True)
1657

    
1658
  def testDebugSimulate(self):
1659
    lu = _LuTestVerifyErrors(debug_simulate_errors=True)
1660
    lu.CallErrorIf(False, *self._ERR1ARGS)
1661
    self.assertTrue(lu.bad)
1662
    self.assertEqual(len(lu.msglist), 1)
1663
    self._checkMsg1(lu.msglist[0])
1664

    
1665
  def testErrCodes(self):
1666
    self._InitTest(error_codes=True)
1667
    lu = self._CallError(*self._ERR1ARGS)
1668
    self.assertTrue(lu.bad)
1669
    self.assertEqual(len(lu.msglist), 1)
1670
    self._checkMsg1(lu.msglist[0])
1671
    self.assertTrue(self._ERR1ID in lu.msglist[0])
1672

    
1673

    
1674
if __name__ == "__main__":
1675
  testutils.GanetiTestProgram()