Statistics
| Branch: | Tag: | Revision:

root / test / py / cmdlib / instance_unittest.py @ 57da0458

History | View | Annotate | Download (50.8 kB)

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

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

    
21

    
22
"""Tests for LUInstance*
23

24
"""
25

    
26
import copy
27
import itertools
28
import unittest
29
import mock
30
import operator
31

    
32
from ganeti import compat
33
from ganeti import constants
34
from ganeti import errors
35
from ganeti import ht
36
from ganeti import opcodes
37
from ganeti import objects
38
from ganeti import rpc
39
from ganeti import utils
40
from ganeti.cmdlib import instance
41

    
42
from cmdlib.cmdlib_unittest import _StubComputeIPolicySpecViolation, _FakeLU
43

    
44
from testsupport import *
45

    
46
import testutils
47

    
48

    
49
class TestComputeIPolicyInstanceSpecViolation(unittest.TestCase):
50
  def test(self):
51
    ispec = {
52
      constants.ISPEC_MEM_SIZE: 2048,
53
      constants.ISPEC_CPU_COUNT: 2,
54
      constants.ISPEC_DISK_COUNT: 1,
55
      constants.ISPEC_DISK_SIZE: [512],
56
      constants.ISPEC_NIC_COUNT: 0,
57
      constants.ISPEC_SPINDLE_USE: 1,
58
      }
59
    stub = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512], 1,
60
                                            constants.DT_PLAIN)
61
    ret = instance._ComputeIPolicyInstanceSpecViolation(NotImplemented, ispec,
62
                                                        constants.DT_PLAIN,
63
                                                        _compute_fn=stub)
64
    self.assertEqual(ret, [])
65

    
66

    
67
class TestLUInstanceCreate(CmdlibTestCase):
68
  def setUp(self):
69
    super(TestLUInstanceCreate, self).setUp()
70

    
71
    self.net = self.cfg.AddNewNetwork()
72
    self.cfg.ConnectNetworkToGroup(self.net, self.group)
73

    
74
    self.node1 = self.cfg.AddNewNode()
75
    self.node2 = self.cfg.AddNewNode()
76

    
77
    self.rpc.call_os_get.side_effect = \
78
      lambda node, _: self.RpcResultsBuilder() \
79
                        .CreateSuccessfulNodeResult(node, self.os)
80

    
81
    hv_info = ("bootid",
82
               [{
83
                 "type": constants.ST_LVM_VG,
84
                 "storage_free": 10000
85
               }],
86
               ({"memory_free": 10000}, ))
87
    self.rpc.call_node_info.return_value = \
88
      self.RpcResultsBuilder() \
89
        .AddSuccessfulNode(self.master, hv_info) \
90
        .AddSuccessfulNode(self.node1, hv_info) \
91
        .AddSuccessfulNode(self.node2, hv_info) \
92
        .Build()
93

    
94
    self.rpc.call_blockdev_getmirrorstatus.side_effect = \
95
      lambda node, _: self.RpcResultsBuilder() \
96
                        .CreateSuccessfulNodeResult(node, [])
97

    
98
    self.iallocator_cls.return_value.result = [self.node1.name, self.node2.name]
99

    
100
    self.diskless_op = opcodes.OpInstanceCreate(
101
      instance_name="diskless.test.com",
102
      pnode=self.master.name,
103
      disk_template=constants.DT_DISKLESS,
104
      mode=constants.INSTANCE_CREATE,
105
      nics=[{}],
106
      disks=[],
107
      os_type=self.os_name_variant)
108

    
109
    self.plain_op = opcodes.OpInstanceCreate(
110
      instance_name="plain.test.com",
111
      pnode=self.master.name,
112
      disk_template=constants.DT_PLAIN,
113
      mode=constants.INSTANCE_CREATE,
114
      nics=[{}],
115
      disks=[{
116
        constants.IDISK_SIZE: 1024
117
      }],
118
      os_type=self.os_name_variant)
119

    
120
    self.block_op = opcodes.OpInstanceCreate(
121
      instance_name="block.test.com",
122
      pnode=self.master.name,
123
      disk_template=constants.DT_BLOCK,
124
      mode=constants.INSTANCE_CREATE,
125
      nics=[{}],
126
      disks=[{
127
        constants.IDISK_SIZE: 1024,
128
        constants.IDISK_ADOPT: "/dev/disk/block0"
129
      }],
130
      os_type=self.os_name_variant)
131

    
132
    self.drbd_op = opcodes.OpInstanceCreate(
133
      instance_name="drbd.test.com",
134
      pnode=self.node1.name,
135
      snode=self.node2.name,
136
      disk_template=constants.DT_DRBD8,
137
      mode=constants.INSTANCE_CREATE,
138
      nics=[{}],
139
      disks=[{
140
        constants.IDISK_SIZE: 1024
141
      }],
142
      os_type=self.os_name_variant)
143

    
144
    self.file_op = opcodes.OpInstanceCreate(
145
      instance_name="file.test.com",
146
      pnode=self.node1.name,
147
      disk_template=constants.DT_FILE,
148
      mode=constants.INSTANCE_CREATE,
149
      nics=[{}],
150
      disks=[{
151
        constants.IDISK_SIZE: 1024
152
      }],
153
      os_type=self.os_name_variant)
154

    
155
  def testSimpleCreate(self):
156
    op = self.CopyOpCode(self.diskless_op)
157
    self.ExecOpCode(op)
158

    
159
  def testStrangeHostnameResolve(self):
160
    op = self.CopyOpCode(self.diskless_op)
161
    self.netutils_mod.GetHostname.return_value = \
162
      HostnameMock("random.host.com", "1.2.3.4")
163
    self.ExecOpCodeExpectOpPrereqError(
164
      op, "Resolved hostname .* does not look the same as given hostname")
165

    
166
  def testOpportunisticLockingNoIAllocator(self):
167
    op = self.CopyOpCode(self.diskless_op,
168
                         opportunistic_locking=True,
169
                         iallocator=None)
170
    self.ExecOpCodeExpectOpPrereqError(
171
      op, "Opportunistic locking is only available in combination with an"
172
          " instance allocator")
173

    
174
  def testNicWithNetAndMode(self):
175
    op = self.CopyOpCode(self.diskless_op,
176
                         nics=[{
177
                           constants.INIC_NETWORK: self.net.name,
178
                           constants.INIC_MODE: constants.NIC_MODE_BRIDGED
179
                         }])
180
    self.ExecOpCodeExpectOpPrereqError(
181
      op, "If network is given, no mode or link is allowed to be passed")
182

    
183
  def testVlanWithWrongMode(self):
184
    op = self.CopyOpCode(self.diskless_op,
185
                         nics=[{
186
                           constants.INIC_VLAN: ":1",
187
                           constants.INIC_MODE: constants.NIC_MODE_BRIDGED
188
                         }])
189
    self.ExecOpCodeExpectOpPrereqError(
190
      op, "VLAN is given, but network mode is not openvswitch")
191

    
192
  def testAutoIpNoNameCheck(self):
193
    op = self.CopyOpCode(self.diskless_op,
194
                         nics=[{
195
                           constants.INIC_IP: constants.VALUE_AUTO
196
                         }],
197
                         ip_check=False,
198
                         name_check=False)
199
    self.ExecOpCodeExpectOpPrereqError(
200
      op, "IP address set to auto but name checks have been skipped")
201

    
202
  def testAutoIp(self):
203
    op = self.CopyOpCode(self.diskless_op,
204
                         nics=[{
205
                           constants.INIC_IP: constants.VALUE_AUTO
206
                         }])
207
    self.ExecOpCode(op)
208

    
209
  def testPoolIpNoNetwork(self):
210
    op = self.CopyOpCode(self.diskless_op,
211
                         nics=[{
212
                           constants.INIC_IP: constants.NIC_IP_POOL
213
                         }])
214
    self.ExecOpCodeExpectOpPrereqError(
215
      op, "if ip=pool, parameter network must be passed too")
216

    
217
  def testValidIp(self):
218
    op = self.CopyOpCode(self.diskless_op,
219
                         nics=[{
220
                           constants.INIC_IP: "1.2.3.4"
221
                         }])
222
    self.ExecOpCode(op)
223

    
224
  def testRoutedNoIp(self):
225
    op = self.CopyOpCode(self.diskless_op,
226
                         nics=[{
227
                           constants.INIC_MODE: constants.NIC_MODE_ROUTED
228
                         }])
229
    self.ExecOpCodeExpectOpPrereqError(
230
      op, "Routed nic mode requires an ip address")
231

    
232
  def testValicMac(self):
233
    op = self.CopyOpCode(self.diskless_op,
234
                         nics=[{
235
                           constants.INIC_MAC: "f0:df:f4:a3:d1:cf"
236
                         }])
237
    self.ExecOpCode(op)
238

    
239
  def testValidNicParams(self):
240
    op = self.CopyOpCode(self.diskless_op,
241
                         nics=[{
242
                           constants.INIC_MODE: constants.NIC_MODE_BRIDGED,
243
                           constants.INIC_LINK: "br_mock"
244
                         }])
245
    self.ExecOpCode(op)
246

    
247
  def testValidNicParamsOpenVSwitch(self):
248
    op = self.CopyOpCode(self.diskless_op,
249
                         nics=[{
250
                           constants.INIC_MODE: constants.NIC_MODE_OVS,
251
                           constants.INIC_VLAN: "1"
252
                         }])
253
    self.ExecOpCode(op)
254

    
255
  def testNicNoneName(self):
256
    op = self.CopyOpCode(self.diskless_op,
257
                         nics=[{
258
                           constants.INIC_NAME: constants.VALUE_NONE
259
                         }])
260
    self.ExecOpCode(op)
261

    
262
  def testConflictingIP(self):
263
    op = self.CopyOpCode(self.diskless_op,
264
                         nics=[{
265
                           constants.INIC_IP: self.net.gateway[:-1] + "2"
266
                         }])
267
    self.ExecOpCodeExpectOpPrereqError(
268
      op, "The requested IP address .* belongs to network .*, but the target"
269
          " NIC does not.")
270

    
271
  def testVLanFormat(self):
272
    for vlan in [".pinky", ":bunny", ":1:pinky", "bunny"]:
273
      self.ResetMocks()
274
      op = self.CopyOpCode(self.diskless_op,
275
                           nics=[{
276
                             constants.INIC_VLAN: vlan
277
                           }])
278
      self.ExecOpCodeExpectOpPrereqError(
279
        op, "Specified VLAN parameter is invalid")
280

    
281
  def testPoolIp(self):
282
    op = self.CopyOpCode(self.diskless_op,
283
                         nics=[{
284
                           constants.INIC_IP: constants.NIC_IP_POOL,
285
                           constants.INIC_NETWORK: self.net.name
286
                         }])
287
    self.ExecOpCode(op)
288

    
289
  def testPoolIpUnconnectedNetwork(self):
290
    net = self.cfg.AddNewNetwork()
291
    op = self.CopyOpCode(self.diskless_op,
292
                         nics=[{
293
                           constants.INIC_IP: constants.NIC_IP_POOL,
294
                           constants.INIC_NETWORK: net.name
295
                         }])
296
    self.ExecOpCodeExpectOpPrereqError(
297
      op, "No netparams found for network .*.")
298

    
299
  def testIpNotInNetwork(self):
300
    op = self.CopyOpCode(self.diskless_op,
301
                         nics=[{
302
                           constants.INIC_IP: "1.2.3.4",
303
                           constants.INIC_NETWORK: self.net.name
304
                         }])
305
    self.ExecOpCodeExpectOpPrereqError(
306
      op, "IP address .* already in use or does not belong to network .*")
307

    
308
  def testMixAdoptAndNotAdopt(self):
309
    op = self.CopyOpCode(self.diskless_op,
310
                         disk_template=constants.DT_PLAIN,
311
                         disks=[{
312
                           constants.IDISK_ADOPT: "lv1"
313
                         }, {}])
314
    self.ExecOpCodeExpectOpPrereqError(
315
      op, "Either all disks are adopted or none is")
316

    
317
  def testMustAdoptWithoutAdopt(self):
318
    op = self.CopyOpCode(self.diskless_op,
319
                         disk_template=constants.DT_BLOCK,
320
                         disks=[{}])
321
    self.ExecOpCodeExpectOpPrereqError(
322
      op, "Disk template blockdev requires disk adoption, but no 'adopt'"
323
          " parameter given")
324

    
325
  def testDontAdoptWithAdopt(self):
326
    op = self.CopyOpCode(self.diskless_op,
327
                         disk_template=constants.DT_DRBD8,
328
                         disks=[{
329
                           constants.IDISK_ADOPT: "lv1"
330
                         }])
331
    self.ExecOpCodeExpectOpPrereqError(
332
      op, "Disk adoption is not supported for the 'drbd' disk template")
333

    
334
  def testAdoptWithIAllocator(self):
335
    op = self.CopyOpCode(self.diskless_op,
336
                         disk_template=constants.DT_PLAIN,
337
                         disks=[{
338
                           constants.IDISK_ADOPT: "lv1"
339
                         }],
340
                         iallocator="mock")
341
    self.ExecOpCodeExpectOpPrereqError(
342
      op, "Disk adoption not allowed with an iallocator script")
343

    
344
  def testAdoptWithImport(self):
345
    op = self.CopyOpCode(self.diskless_op,
346
                         disk_template=constants.DT_PLAIN,
347
                         disks=[{
348
                           constants.IDISK_ADOPT: "lv1"
349
                         }],
350
                         mode=constants.INSTANCE_IMPORT)
351
    self.ExecOpCodeExpectOpPrereqError(
352
      op, "Disk adoption not allowed for instance import")
353

    
354
  def testArgumentCombinations(self):
355
    op = self.CopyOpCode(self.diskless_op,
356
                         # start flag will be flipped
357
                         no_install=True,
358
                         start=True,
359
                         # no allowed combination
360
                         ip_check=True,
361
                         name_check=False)
362
    self.ExecOpCodeExpectOpPrereqError(
363
      op, "Cannot do IP address check without a name check")
364

    
365
  def testInvalidFileDriver(self):
366
    op = self.CopyOpCode(self.diskless_op,
367
                         file_driver="invalid_file_driver")
368
    self.ExecOpCodeExpectOpPrereqError(
369
      op, "Parameter 'OP_INSTANCE_CREATE.file_driver' fails validation")
370

    
371
  def testMissingSecondaryNode(self):
372
    op = self.CopyOpCode(self.diskless_op,
373
                         pnode=self.master.name,
374
                         disk_template=constants.DT_DRBD8)
375
    self.ExecOpCodeExpectOpPrereqError(
376
      op, "The networked disk templates need a mirror node")
377

    
378
  def testIgnoredSecondaryNode(self):
379
    op = self.CopyOpCode(self.diskless_op,
380
                         pnode=self.master.name,
381
                         snode=self.node1.name,
382
                         disk_template=constants.DT_PLAIN)
383
    try:
384
      self.ExecOpCode(op)
385
    except Exception:
386
      pass
387
    self.mcpu.assertLogContainsRegex(
388
      "Secondary node will be ignored on non-mirrored disk template")
389

    
390
  def testMissingOsType(self):
391
    op = self.CopyOpCode(self.diskless_op,
392
                         os_type=self.REMOVE)
393
    self.ExecOpCodeExpectOpPrereqError(op, "No guest OS specified")
394

    
395
  def testBlacklistedOs(self):
396
    self.cluster.blacklisted_os = [self.os_name_variant]
397
    op = self.CopyOpCode(self.diskless_op)
398
    self.ExecOpCodeExpectOpPrereqError(
399
      op, "Guest OS .* is not allowed for installation")
400

    
401
  def testMissingDiskTemplate(self):
402
    self.cluster.enabled_disk_templates = [constants.DT_DISKLESS]
403
    op = self.CopyOpCode(self.diskless_op,
404
                         disk_template=self.REMOVE)
405
    self.ExecOpCode(op)
406

    
407
  def testExistingInstance(self):
408
    inst = self.cfg.AddNewInstance()
409
    op = self.CopyOpCode(self.diskless_op,
410
                         instance_name=inst.name)
411
    self.ExecOpCodeExpectOpPrereqError(
412
      op, "Instance .* is already in the cluster")
413

    
414
  def testPlainInstance(self):
415
    op = self.CopyOpCode(self.plain_op)
416
    self.ExecOpCode(op)
417

    
418
  def testPlainIAllocator(self):
419
    op = self.CopyOpCode(self.plain_op,
420
                         pnode=self.REMOVE,
421
                         iallocator="mock")
422
    self.ExecOpCode(op)
423

    
424
  def testIAllocatorOpportunisticLocking(self):
425
    op = self.CopyOpCode(self.plain_op,
426
                         pnode=self.REMOVE,
427
                         iallocator="mock",
428
                         opportunistic_locking=True)
429
    self.ExecOpCode(op)
430

    
431
  def testFailingIAllocator(self):
432
    self.iallocator_cls.return_value.success = False
433
    op = self.CopyOpCode(self.plain_op,
434
                         pnode=self.REMOVE,
435
                         iallocator="mock")
436
    self.ExecOpCodeExpectOpPrereqError(
437
      op, "Can't compute nodes using iallocator")
438

    
439
  def testDrbdInstance(self):
440
    op = self.CopyOpCode(self.drbd_op)
441
    self.ExecOpCode(op)
442

    
443
  def testDrbdIAllocator(self):
444
    op = self.CopyOpCode(self.drbd_op,
445
                         pnode=self.REMOVE,
446
                         snode=self.REMOVE,
447
                         iallocator="mock")
448
    self.ExecOpCode(op)
449

    
450
  def testFileInstance(self):
451
    op = self.CopyOpCode(self.file_op)
452
    self.ExecOpCode(op)
453

    
454
  def testFileInstanceNoClusterStorage(self):
455
    self.cluster.file_storage_dir = None
456
    op = self.CopyOpCode(self.file_op)
457
    self.ExecOpCodeExpectOpPrereqError(
458
      op, "Cluster file storage dir not defined")
459

    
460
  def testFileInstanceAdditionalPath(self):
461
    op = self.CopyOpCode(self.file_op,
462
                         file_storage_dir="mock_dir")
463
    self.ExecOpCode(op)
464

    
465
  def testIdentifyDefaults(self):
466
    op = self.CopyOpCode(self.plain_op,
467
                         hvparams={
468
                           constants.HV_BOOT_ORDER: "cd"
469
                         },
470
                         beparams=constants.BEC_DEFAULTS.copy(),
471
                         nics=[{
472
                           constants.NIC_MODE: constants.NIC_MODE_BRIDGED
473
                         }],
474
                         osparams={
475
                           self.os_name_variant: {}
476
                         },
477
                         identify_defaults=True)
478
    self.ExecOpCode(op)
479

    
480
    inst = self.cfg.GetAllInstancesInfo().values()[0]
481
    self.assertEqual(0, len(inst.hvparams))
482
    self.assertEqual(0, len(inst.beparams))
483
    assert self.os_name_variant not in inst.osparams or \
484
            len(inst.osparams[self.os_name_variant]) == 0
485

    
486
  def testOfflineNode(self):
487
    self.node1.offline = True
488
    op = self.CopyOpCode(self.diskless_op,
489
                         pnode=self.node1.name)
490
    self.ExecOpCodeExpectOpPrereqError(op, "Cannot use offline primary node")
491

    
492
  def testDrainedNode(self):
493
    self.node1.drained = True
494
    op = self.CopyOpCode(self.diskless_op,
495
                         pnode=self.node1.name)
496
    self.ExecOpCodeExpectOpPrereqError(op, "Cannot use drained primary node")
497

    
498
  def testNonVmCapableNode(self):
499
    self.node1.vm_capable = False
500
    op = self.CopyOpCode(self.diskless_op,
501
                         pnode=self.node1.name)
502
    self.ExecOpCodeExpectOpPrereqError(
503
      op, "Cannot use non-vm_capable primary node")
504

    
505
  def testNonEnabledHypervisor(self):
506
    self.cluster.enabled_hypervisors = [constants.HT_XEN_HVM]
507
    op = self.CopyOpCode(self.diskless_op,
508
                         hypervisor=constants.HT_FAKE)
509
    self.ExecOpCodeExpectOpPrereqError(
510
      op, "Selected hypervisor .* not enabled in the cluster")
511

    
512
  def testAddTag(self):
513
    op = self.CopyOpCode(self.diskless_op,
514
                         tags=["tag"])
515
    self.ExecOpCode(op)
516

    
517
  def testInvalidTag(self):
518
    op = self.CopyOpCode(self.diskless_op,
519
                         tags=["too_long" * 20])
520
    self.ExecOpCodeExpectException(op, errors.TagError, "Tag too long")
521

    
522
  def testPingableInstanceName(self):
523
    self.netutils_mod.TcpPing.return_value = True
524
    op = self.CopyOpCode(self.diskless_op)
525
    self.ExecOpCodeExpectOpPrereqError(
526
      op, "IP .* of instance diskless.test.com already in use")
527

    
528
  def testPrimaryIsSecondaryNode(self):
529
    op = self.CopyOpCode(self.drbd_op,
530
                         snode=self.drbd_op.pnode)
531
    self.ExecOpCodeExpectOpPrereqError(
532
      op, "The secondary node cannot be the primary node")
533

    
534
  def testPrimarySecondaryDifferentNodeGroups(self):
535
    group = self.cfg.AddNewNodeGroup()
536
    self.node2.group = group.uuid
537
    op = self.CopyOpCode(self.drbd_op)
538
    self.ExecOpCode(op)
539
    self.mcpu.assertLogContainsRegex(
540
      "The primary and secondary nodes are in two different node groups")
541

    
542
  def testExclusiveStorageUnsupportedDiskTemplate(self):
543
    self.node1.ndparams[constants.ND_EXCLUSIVE_STORAGE] = True
544
    op = self.CopyOpCode(self.drbd_op)
545
    self.ExecOpCodeExpectOpPrereqError(
546
      op, "Disk template drbd not supported with exclusive storage")
547

    
548
  def testAdoptPlain(self):
549
    self.rpc.call_lv_list.return_value = \
550
      self.RpcResultsBuilder() \
551
        .AddSuccessfulNode(self.master, {
552
          "xenvg/mock_disk_1": (10000, None, False)
553
        }) \
554
        .Build()
555
    op = self.CopyOpCode(self.plain_op)
556
    op.disks[0].update({constants.IDISK_ADOPT: "mock_disk_1"})
557
    self.ExecOpCode(op)
558

    
559
  def testAdoptPlainMissingLv(self):
560
    self.rpc.call_lv_list.return_value = \
561
      self.RpcResultsBuilder() \
562
        .AddSuccessfulNode(self.master, {}) \
563
        .Build()
564
    op = self.CopyOpCode(self.plain_op)
565
    op.disks[0].update({constants.IDISK_ADOPT: "mock_disk_1"})
566
    self.ExecOpCodeExpectOpPrereqError(op, "Missing logical volume")
567

    
568
  def testAdoptPlainOnlineLv(self):
569
    self.rpc.call_lv_list.return_value = \
570
      self.RpcResultsBuilder() \
571
        .AddSuccessfulNode(self.master, {
572
          "xenvg/mock_disk_1": (10000, None, True)
573
        }) \
574
        .Build()
575
    op = self.CopyOpCode(self.plain_op)
576
    op.disks[0].update({constants.IDISK_ADOPT: "mock_disk_1"})
577
    self.ExecOpCodeExpectOpPrereqError(
578
      op, "Online logical volumes found, cannot adopt")
579

    
580
  def testAdoptBlock(self):
581
    self.rpc.call_bdev_sizes.return_value = \
582
      self.RpcResultsBuilder() \
583
        .AddSuccessfulNode(self.master, {
584
          "/dev/disk/block0": 10000
585
        }) \
586
        .Build()
587
    op = self.CopyOpCode(self.block_op)
588
    self.ExecOpCode(op)
589

    
590
  def testAdoptBlockDuplicateNames(self):
591
    op = self.CopyOpCode(self.block_op,
592
                         disks=[{
593
                           constants.IDISK_SIZE: 0,
594
                           constants.IDISK_ADOPT: "/dev/disk/block0"
595
                         }, {
596
                           constants.IDISK_SIZE: 0,
597
                           constants.IDISK_ADOPT: "/dev/disk/block0"
598
                         }])
599
    self.ExecOpCodeExpectOpPrereqError(
600
      op, "Duplicate disk names given for adoption")
601

    
602
  def testAdoptBlockInvalidNames(self):
603
    op = self.CopyOpCode(self.block_op,
604
                         disks=[{
605
                           constants.IDISK_SIZE: 0,
606
                           constants.IDISK_ADOPT: "/invalid/block0"
607
                         }])
608
    self.ExecOpCodeExpectOpPrereqError(
609
      op, "Device node.* lie outside .* and cannot be adopted")
610

    
611
  def testAdoptBlockMissingDisk(self):
612
    self.rpc.call_bdev_sizes.return_value = \
613
      self.RpcResultsBuilder() \
614
        .AddSuccessfulNode(self.master, {}) \
615
        .Build()
616
    op = self.CopyOpCode(self.block_op)
617
    self.ExecOpCodeExpectOpPrereqError(op, "Missing block device")
618

    
619
  def testNoWaitForSyncDrbd(self):
620
    op = self.CopyOpCode(self.drbd_op,
621
                         wait_for_sync=False)
622
    self.ExecOpCode(op)
623

    
624
  def testNoWaitForSyncPlain(self):
625
    op = self.CopyOpCode(self.plain_op,
626
                         wait_for_sync=False)
627
    self.ExecOpCode(op)
628

    
629
class TestCheckOSVariant(CmdlibTestCase):
630
  def testNoVariantsSupported(self):
631
    os = self.cfg.CreateOs(supported_variants=[])
632
    self.assertRaises(errors.OpPrereqError, instance._CheckOSVariant,
633
                      os, "os+variant")
634

    
635
  def testNoVariantGiven(self):
636
    os = self.cfg.CreateOs(supported_variants=["default"])
637
    self.assertRaises(errors.OpPrereqError, instance._CheckOSVariant,
638
                      os, "os")
639

    
640
  def testWrongVariantGiven(self):
641
    os = self.cfg.CreateOs(supported_variants=["default"])
642
    self.assertRaises(errors.OpPrereqError, instance._CheckOSVariant,
643
                      os, "os+wrong_variant")
644

    
645
  def testOkWithVariant(self):
646
    os = self.cfg.CreateOs(supported_variants=["default"])
647
    instance._CheckOSVariant(os, "os+default")
648

    
649
  def testOkWithoutVariant(self):
650
    os = self.cfg.CreateOs(supported_variants=[])
651
    instance._CheckOSVariant(os, "os")
652

    
653

    
654
class TestCheckTargetNodeIPolicy(TestLUInstanceCreate):
655
  def setUp(self):
656
    super(TestCheckTargetNodeIPolicy, self).setUp()
657

    
658
    self.op = self.diskless_op
659

    
660
    self.instance = self.cfg.AddNewInstance()
661
    self.target_group = self.cfg.AddNewNodeGroup()
662
    self.target_node = self.cfg.AddNewNode(group=self.target_group)
663

    
664
  @withLockedLU
665
  def testNoViolation(self, lu):
666
    compute_recoder = mock.Mock(return_value=[])
667
    instance.CheckTargetNodeIPolicy(lu, NotImplemented, self.instance,
668
                                    self.target_node, NotImplemented,
669
                                    _compute_fn=compute_recoder)
670
    self.assertTrue(compute_recoder.called)
671
    self.mcpu.assertLogIsEmpty()
672

    
673
  @withLockedLU
674
  def testNoIgnore(self, lu):
675
    compute_recoder = mock.Mock(return_value=["mem_size not in range"])
676
    self.assertRaises(errors.OpPrereqError, instance.CheckTargetNodeIPolicy,
677
                      lu, NotImplemented, self.instance,
678
                      self.target_node, NotImplemented,
679
                      _compute_fn=compute_recoder)
680
    self.assertTrue(compute_recoder.called)
681
    self.mcpu.assertLogIsEmpty()
682

    
683
  @withLockedLU
684
  def testIgnoreViolation(self, lu):
685
    compute_recoder = mock.Mock(return_value=["mem_size not in range"])
686
    instance.CheckTargetNodeIPolicy(lu, NotImplemented, self.instance,
687
                                    self.target_node, NotImplemented,
688
                                    ignore=True, _compute_fn=compute_recoder)
689
    self.assertTrue(compute_recoder.called)
690
    msg = ("Instance does not meet target node group's .* instance policy:"
691
           " mem_size not in range")
692
    self.mcpu.assertLogContainsRegex(msg)
693

    
694

    
695
class TestApplyContainerMods(unittest.TestCase):
696
  def testEmptyContainer(self):
697
    container = []
698
    chgdesc = []
699
    instance._ApplyContainerMods("test", container, chgdesc, [], None, None,
700
                                 None)
701
    self.assertEqual(container, [])
702
    self.assertEqual(chgdesc, [])
703

    
704
  def testAdd(self):
705
    container = []
706
    chgdesc = []
707
    mods = instance._PrepareContainerMods([
708
      (constants.DDM_ADD, -1, "Hello"),
709
      (constants.DDM_ADD, -1, "World"),
710
      (constants.DDM_ADD, 0, "Start"),
711
      (constants.DDM_ADD, -1, "End"),
712
      ], None)
713
    instance._ApplyContainerMods("test", container, chgdesc, mods,
714
                                 None, None, None)
715
    self.assertEqual(container, ["Start", "Hello", "World", "End"])
716
    self.assertEqual(chgdesc, [])
717

    
718
    mods = instance._PrepareContainerMods([
719
      (constants.DDM_ADD, 0, "zero"),
720
      (constants.DDM_ADD, 3, "Added"),
721
      (constants.DDM_ADD, 5, "four"),
722
      (constants.DDM_ADD, 7, "xyz"),
723
      ], None)
724
    instance._ApplyContainerMods("test", container, chgdesc, mods,
725
                                 None, None, None)
726
    self.assertEqual(container,
727
                     ["zero", "Start", "Hello", "Added", "World", "four",
728
                      "End", "xyz"])
729
    self.assertEqual(chgdesc, [])
730

    
731
    for idx in [-2, len(container) + 1]:
732
      mods = instance._PrepareContainerMods([
733
        (constants.DDM_ADD, idx, "error"),
734
        ], None)
735
      self.assertRaises(IndexError, instance._ApplyContainerMods,
736
                        "test", container, None, mods, None, None, None)
737

    
738
  def testRemoveError(self):
739
    for idx in [0, 1, 2, 100, -1, -4]:
740
      mods = instance._PrepareContainerMods([
741
        (constants.DDM_REMOVE, idx, None),
742
        ], None)
743
      self.assertRaises(IndexError, instance._ApplyContainerMods,
744
                        "test", [], None, mods, None, None, None)
745

    
746
    mods = instance._PrepareContainerMods([
747
      (constants.DDM_REMOVE, 0, object()),
748
      ], None)
749
    self.assertRaises(AssertionError, instance._ApplyContainerMods,
750
                      "test", [""], None, mods, None, None, None)
751

    
752
  def testAddError(self):
753
    for idx in range(-100, -1) + [100]:
754
      mods = instance._PrepareContainerMods([
755
        (constants.DDM_ADD, idx, None),
756
        ], None)
757
      self.assertRaises(IndexError, instance._ApplyContainerMods,
758
                        "test", [], None, mods, None, None, None)
759

    
760
  def testRemove(self):
761
    container = ["item 1", "item 2"]
762
    mods = instance._PrepareContainerMods([
763
      (constants.DDM_ADD, -1, "aaa"),
764
      (constants.DDM_REMOVE, -1, None),
765
      (constants.DDM_ADD, -1, "bbb"),
766
      ], None)
767
    chgdesc = []
768
    instance._ApplyContainerMods("test", container, chgdesc, mods,
769
                                 None, None, None)
770
    self.assertEqual(container, ["item 1", "item 2", "bbb"])
771
    self.assertEqual(chgdesc, [
772
      ("test/2", "remove"),
773
      ])
774

    
775
  def testModify(self):
776
    container = ["item 1", "item 2"]
777
    mods = instance._PrepareContainerMods([
778
      (constants.DDM_MODIFY, -1, "a"),
779
      (constants.DDM_MODIFY, 0, "b"),
780
      (constants.DDM_MODIFY, 1, "c"),
781
      ], None)
782
    chgdesc = []
783
    instance._ApplyContainerMods("test", container, chgdesc, mods,
784
                                 None, None, None)
785
    self.assertEqual(container, ["item 1", "item 2"])
786
    self.assertEqual(chgdesc, [])
787

    
788
    for idx in [-2, len(container) + 1]:
789
      mods = instance._PrepareContainerMods([
790
        (constants.DDM_MODIFY, idx, "error"),
791
        ], None)
792
      self.assertRaises(IndexError, instance._ApplyContainerMods,
793
                        "test", container, None, mods, None, None, None)
794

    
795
  @staticmethod
796
  def _CreateTestFn(idx, params, private):
797
    private.data = ("add", idx, params)
798
    return ((100 * idx, params), [
799
      ("test/%s" % idx, hex(idx)),
800
      ])
801

    
802
  @staticmethod
803
  def _ModifyTestFn(idx, item, params, private):
804
    private.data = ("modify", idx, params)
805
    return [
806
      ("test/%s" % idx, "modify %s" % params),
807
      ]
808

    
809
  @staticmethod
810
  def _RemoveTestFn(idx, item, private):
811
    private.data = ("remove", idx, item)
812

    
813
  def testAddWithCreateFunction(self):
814
    container = []
815
    chgdesc = []
816
    mods = instance._PrepareContainerMods([
817
      (constants.DDM_ADD, -1, "Hello"),
818
      (constants.DDM_ADD, -1, "World"),
819
      (constants.DDM_ADD, 0, "Start"),
820
      (constants.DDM_ADD, -1, "End"),
821
      (constants.DDM_REMOVE, 2, None),
822
      (constants.DDM_MODIFY, -1, "foobar"),
823
      (constants.DDM_REMOVE, 2, None),
824
      (constants.DDM_ADD, 1, "More"),
825
      ], mock.Mock)
826
    instance._ApplyContainerMods("test", container, chgdesc, mods,
827
                                 self._CreateTestFn, self._ModifyTestFn,
828
                                 self._RemoveTestFn)
829
    self.assertEqual(container, [
830
      (000, "Start"),
831
      (100, "More"),
832
      (000, "Hello"),
833
      ])
834
    self.assertEqual(chgdesc, [
835
      ("test/0", "0x0"),
836
      ("test/1", "0x1"),
837
      ("test/0", "0x0"),
838
      ("test/3", "0x3"),
839
      ("test/2", "remove"),
840
      ("test/2", "modify foobar"),
841
      ("test/2", "remove"),
842
      ("test/1", "0x1")
843
      ])
844
    self.assertTrue(compat.all(op == private.data[0]
845
                               for (op, _, _, private) in mods))
846
    self.assertEqual([private.data for (op, _, _, private) in mods], [
847
      ("add", 0, "Hello"),
848
      ("add", 1, "World"),
849
      ("add", 0, "Start"),
850
      ("add", 3, "End"),
851
      ("remove", 2, (100, "World")),
852
      ("modify", 2, "foobar"),
853
      ("remove", 2, (300, "End")),
854
      ("add", 1, "More"),
855
      ])
856

    
857

    
858
class _FakeConfigForGenDiskTemplate(ConfigMock):
859
  def __init__(self):
860
    super(_FakeConfigForGenDiskTemplate, self).__init__()
861

    
862
    self._unique_id = itertools.count()
863
    self._drbd_minor = itertools.count(20)
864
    self._port = itertools.count(constants.FIRST_DRBD_PORT)
865
    self._secret = itertools.count()
866

    
867
  def GenerateUniqueID(self, ec_id):
868
    return "ec%s-uq%s" % (ec_id, self._unique_id.next())
869

    
870
  def AllocateDRBDMinor(self, nodes, instance):
871
    return [self._drbd_minor.next()
872
            for _ in nodes]
873

    
874
  def AllocatePort(self):
875
    return self._port.next()
876

    
877
  def GenerateDRBDSecret(self, ec_id):
878
    return "ec%s-secret%s" % (ec_id, self._secret.next())
879

    
880

    
881
class TestGenerateDiskTemplate(CmdlibTestCase):
882
  def setUp(self):
883
    super(TestGenerateDiskTemplate, self).setUp()
884

    
885
    self.cfg = _FakeConfigForGenDiskTemplate()
886
    self.cluster.enabled_disk_templates = list(constants.DISK_TEMPLATES)
887

    
888
    self.nodegroup = self.cfg.AddNewNodeGroup(name="ng")
889

    
890
    self.lu = self.GetMockLU()
891

    
892
  @staticmethod
893
  def GetDiskParams():
894
    return copy.deepcopy(constants.DISK_DT_DEFAULTS)
895

    
896
  def testWrongDiskTemplate(self):
897
    gdt = instance.GenerateDiskTemplate
898
    disk_template = "##unknown##"
899

    
900
    assert disk_template not in constants.DISK_TEMPLATES
901

    
902
    self.assertRaises(errors.OpPrereqError, gdt, self.lu, disk_template,
903
                      "inst26831.example.com", "node30113.example.com", [], [],
904
                      NotImplemented, NotImplemented, 0, self.lu.LogInfo,
905
                      self.GetDiskParams())
906

    
907
  def testDiskless(self):
908
    gdt = instance.GenerateDiskTemplate
909

    
910
    result = gdt(self.lu, constants.DT_DISKLESS, "inst27734.example.com",
911
                 "node30113.example.com", [], [],
912
                 NotImplemented, NotImplemented, 0, self.lu.LogInfo,
913
                 self.GetDiskParams())
914
    self.assertEqual(result, [])
915

    
916
  def _TestTrivialDisk(self, template, disk_info, base_index, exp_dev_type,
917
                       file_storage_dir=NotImplemented,
918
                       file_driver=NotImplemented):
919
    gdt = instance.GenerateDiskTemplate
920

    
921
    map(lambda params: utils.ForceDictType(params,
922
                                           constants.IDISK_PARAMS_TYPES),
923
        disk_info)
924

    
925
    # Check if non-empty list of secondaries is rejected
926
    self.assertRaises(errors.ProgrammerError, gdt, self.lu,
927
                      template, "inst25088.example.com",
928
                      "node185.example.com", ["node323.example.com"], [],
929
                      NotImplemented, NotImplemented, base_index,
930
                      self.lu.LogInfo, self.GetDiskParams())
931

    
932
    result = gdt(self.lu, template, "inst21662.example.com",
933
                 "node21741.example.com", [],
934
                 disk_info, file_storage_dir, file_driver, base_index,
935
                 self.lu.LogInfo, self.GetDiskParams())
936

    
937
    for (idx, disk) in enumerate(result):
938
      self.assertTrue(isinstance(disk, objects.Disk))
939
      self.assertEqual(disk.dev_type, exp_dev_type)
940
      self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
941
      self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
942
      self.assertTrue(disk.children is None)
943

    
944
    self._CheckIvNames(result, base_index, base_index + len(disk_info))
945
    instance._UpdateIvNames(base_index, result)
946
    self._CheckIvNames(result, base_index, base_index + len(disk_info))
947

    
948
    return result
949

    
950
  def _CheckIvNames(self, disks, base_index, end_index):
951
    self.assertEqual(map(operator.attrgetter("iv_name"), disks),
952
                     ["disk/%s" % i for i in range(base_index, end_index)])
953

    
954
  def testPlain(self):
955
    disk_info = [{
956
      constants.IDISK_SIZE: 1024,
957
      constants.IDISK_MODE: constants.DISK_RDWR,
958
      }, {
959
      constants.IDISK_SIZE: 4096,
960
      constants.IDISK_VG: "othervg",
961
      constants.IDISK_MODE: constants.DISK_RDWR,
962
      }]
963

    
964
    result = self._TestTrivialDisk(constants.DT_PLAIN, disk_info, 3,
965
                                   constants.LD_LV)
966

    
967
    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
968
      ("xenvg", "ec1-uq0.disk3"),
969
      ("othervg", "ec1-uq1.disk4"),
970
      ])
971

    
972
  def testFile(self):
973
    # anything != DT_FILE would do here
974
    self.cluster.enabled_disk_templates = [constants.DT_PLAIN]
975
    self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
976
                      constants.DT_FILE, [], 0, NotImplemented)
977
    self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
978
                      constants.DT_SHARED_FILE, [], 0, NotImplemented)
979

    
980
    for disk_template in [constants.DT_FILE, constants.DT_SHARED_FILE]:
981
      disk_info = [{
982
        constants.IDISK_SIZE: 80 * 1024,
983
        constants.IDISK_MODE: constants.DISK_RDONLY,
984
        }, {
985
        constants.IDISK_SIZE: 4096,
986
        constants.IDISK_MODE: constants.DISK_RDWR,
987
        }, {
988
        constants.IDISK_SIZE: 6 * 1024,
989
        constants.IDISK_MODE: constants.DISK_RDWR,
990
        }]
991

    
992
      self.cluster.enabled_disk_templates = [disk_template]
993
      result = self._TestTrivialDisk(
994
        disk_template, disk_info, 2, constants.LD_FILE,
995
        file_storage_dir="/tmp", file_driver=constants.FD_BLKTAP)
996

    
997
      self.assertEqual(map(operator.attrgetter("logical_id"), result), [
998
        (constants.FD_BLKTAP, "/tmp/disk2"),
999
        (constants.FD_BLKTAP, "/tmp/disk3"),
1000
        (constants.FD_BLKTAP, "/tmp/disk4"),
1001
        ])
1002

    
1003
  def testBlock(self):
1004
    disk_info = [{
1005
      constants.IDISK_SIZE: 8 * 1024,
1006
      constants.IDISK_MODE: constants.DISK_RDWR,
1007
      constants.IDISK_ADOPT: "/tmp/some/block/dev",
1008
      }]
1009

    
1010
    result = self._TestTrivialDisk(constants.DT_BLOCK, disk_info, 10,
1011
                                   constants.LD_BLOCKDEV)
1012

    
1013
    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1014
      (constants.BLOCKDEV_DRIVER_MANUAL, "/tmp/some/block/dev"),
1015
      ])
1016

    
1017
  def testRbd(self):
1018
    disk_info = [{
1019
      constants.IDISK_SIZE: 8 * 1024,
1020
      constants.IDISK_MODE: constants.DISK_RDONLY,
1021
      }, {
1022
      constants.IDISK_SIZE: 100 * 1024,
1023
      constants.IDISK_MODE: constants.DISK_RDWR,
1024
      }]
1025

    
1026
    result = self._TestTrivialDisk(constants.DT_RBD, disk_info, 0,
1027
                                   constants.LD_RBD)
1028

    
1029
    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1030
      ("rbd", "ec1-uq0.rbd.disk0"),
1031
      ("rbd", "ec1-uq1.rbd.disk1"),
1032
      ])
1033

    
1034
  def testDrbd8(self):
1035
    gdt = instance.GenerateDiskTemplate
1036
    drbd8_defaults = constants.DISK_LD_DEFAULTS[constants.LD_DRBD8]
1037
    drbd8_default_metavg = drbd8_defaults[constants.LDP_DEFAULT_METAVG]
1038

    
1039
    disk_info = [{
1040
      constants.IDISK_SIZE: 1024,
1041
      constants.IDISK_MODE: constants.DISK_RDWR,
1042
      }, {
1043
      constants.IDISK_SIZE: 100 * 1024,
1044
      constants.IDISK_MODE: constants.DISK_RDONLY,
1045
      constants.IDISK_METAVG: "metavg",
1046
      }, {
1047
      constants.IDISK_SIZE: 4096,
1048
      constants.IDISK_MODE: constants.DISK_RDWR,
1049
      constants.IDISK_VG: "vgxyz",
1050
      },
1051
      ]
1052

    
1053
    exp_logical_ids = [
1054
      [
1055
        (self.lu.cfg.GetVGName(), "ec1-uq0.disk0_data"),
1056
        (drbd8_default_metavg, "ec1-uq0.disk0_meta"),
1057
      ], [
1058
        (self.lu.cfg.GetVGName(), "ec1-uq1.disk1_data"),
1059
        ("metavg", "ec1-uq1.disk1_meta"),
1060
      ], [
1061
        ("vgxyz", "ec1-uq2.disk2_data"),
1062
        (drbd8_default_metavg, "ec1-uq2.disk2_meta"),
1063
      ]]
1064

    
1065
    assert len(exp_logical_ids) == len(disk_info)
1066

    
1067
    map(lambda params: utils.ForceDictType(params,
1068
                                           constants.IDISK_PARAMS_TYPES),
1069
        disk_info)
1070

    
1071
    # Check if empty list of secondaries is rejected
1072
    self.assertRaises(errors.ProgrammerError, gdt, self.lu, constants.DT_DRBD8,
1073
                      "inst827.example.com", "node1334.example.com", [],
1074
                      disk_info, NotImplemented, NotImplemented, 0,
1075
                      self.lu.LogInfo, self.GetDiskParams())
1076

    
1077
    result = gdt(self.lu, constants.DT_DRBD8, "inst827.example.com",
1078
                 "node1334.example.com", ["node12272.example.com"],
1079
                 disk_info, NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1080
                 self.GetDiskParams())
1081

    
1082
    for (idx, disk) in enumerate(result):
1083
      self.assertTrue(isinstance(disk, objects.Disk))
1084
      self.assertEqual(disk.dev_type, constants.LD_DRBD8)
1085
      self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
1086
      self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
1087

    
1088
      for child in disk.children:
1089
        self.assertTrue(isinstance(disk, objects.Disk))
1090
        self.assertEqual(child.dev_type, constants.LD_LV)
1091
        self.assertTrue(child.children is None)
1092

    
1093
      self.assertEqual(map(operator.attrgetter("logical_id"), disk.children),
1094
                       exp_logical_ids[idx])
1095

    
1096
      self.assertEqual(len(disk.children), 2)
1097
      self.assertEqual(disk.children[0].size, disk.size)
1098
      self.assertEqual(disk.children[1].size, constants.DRBD_META_SIZE)
1099

    
1100
    self._CheckIvNames(result, 0, len(disk_info))
1101
    instance._UpdateIvNames(0, result)
1102
    self._CheckIvNames(result, 0, len(disk_info))
1103

    
1104
    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1105
      ("node1334.example.com", "node12272.example.com",
1106
       constants.FIRST_DRBD_PORT, 20, 21, "ec1-secret0"),
1107
      ("node1334.example.com", "node12272.example.com",
1108
       constants.FIRST_DRBD_PORT + 1, 22, 23, "ec1-secret1"),
1109
      ("node1334.example.com", "node12272.example.com",
1110
       constants.FIRST_DRBD_PORT + 2, 24, 25, "ec1-secret2"),
1111
      ])
1112

    
1113

    
1114
class _DiskPauseTracker:
1115
  def __init__(self):
1116
    self.history = []
1117

    
1118
  def __call__(self, (disks, instance), pause):
1119
    assert not (set(disks) - set(instance.disks))
1120

    
1121
    self.history.extend((i.logical_id, i.size, pause)
1122
                        for i in disks)
1123

    
1124
    return (True, [True] * len(disks))
1125

    
1126

    
1127
class _ConfigForDiskWipe:
1128
  def __init__(self, exp_node_uuid):
1129
    self._exp_node_uuid = exp_node_uuid
1130

    
1131
  def SetDiskID(self, device, node_uuid):
1132
    assert isinstance(device, objects.Disk)
1133
    assert node_uuid == self._exp_node_uuid
1134

    
1135
  def GetNodeName(self, node_uuid):
1136
    assert node_uuid == self._exp_node_uuid
1137
    return "name.of.expected.node"
1138

    
1139

    
1140
class _RpcForDiskWipe:
1141
  def __init__(self, exp_node, pause_cb, wipe_cb):
1142
    self._exp_node = exp_node
1143
    self._pause_cb = pause_cb
1144
    self._wipe_cb = wipe_cb
1145

    
1146
  def call_blockdev_pause_resume_sync(self, node, disks, pause):
1147
    assert node == self._exp_node
1148
    return rpc.RpcResult(data=self._pause_cb(disks, pause))
1149

    
1150
  def call_blockdev_wipe(self, node, bdev, offset, size):
1151
    assert node == self._exp_node
1152
    return rpc.RpcResult(data=self._wipe_cb(bdev, offset, size))
1153

    
1154

    
1155
class _DiskWipeProgressTracker:
1156
  def __init__(self, start_offset):
1157
    self._start_offset = start_offset
1158
    self.progress = {}
1159

    
1160
  def __call__(self, (disk, _), offset, size):
1161
    assert isinstance(offset, (long, int))
1162
    assert isinstance(size, (long, int))
1163

    
1164
    max_chunk_size = (disk.size / 100.0 * constants.MIN_WIPE_CHUNK_PERCENT)
1165

    
1166
    assert offset >= self._start_offset
1167
    assert (offset + size) <= disk.size
1168

    
1169
    assert size > 0
1170
    assert size <= constants.MAX_WIPE_CHUNK
1171
    assert size <= max_chunk_size
1172

    
1173
    assert offset == self._start_offset or disk.logical_id in self.progress
1174

    
1175
    # Keep track of progress
1176
    cur_progress = self.progress.setdefault(disk.logical_id, self._start_offset)
1177

    
1178
    assert cur_progress == offset
1179

    
1180
    # Record progress
1181
    self.progress[disk.logical_id] += size
1182

    
1183
    return (True, None)
1184

    
1185

    
1186
class TestWipeDisks(unittest.TestCase):
1187
  def _FailingPauseCb(self, (disks, _), pause):
1188
    self.assertEqual(len(disks), 3)
1189
    self.assertTrue(pause)
1190
    # Simulate an RPC error
1191
    return (False, "error")
1192

    
1193
  def testPauseFailure(self):
1194
    node_name = "node1372.example.com"
1195

    
1196
    lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, self._FailingPauseCb,
1197
                                     NotImplemented),
1198
                 cfg=_ConfigForDiskWipe(node_name))
1199

    
1200
    disks = [
1201
      objects.Disk(dev_type=constants.LD_LV),
1202
      objects.Disk(dev_type=constants.LD_LV),
1203
      objects.Disk(dev_type=constants.LD_LV),
1204
      ]
1205

    
1206
    inst = objects.Instance(name="inst21201",
1207
                            primary_node=node_name,
1208
                            disk_template=constants.DT_PLAIN,
1209
                            disks=disks)
1210

    
1211
    self.assertRaises(errors.OpExecError, instance.WipeDisks, lu, inst)
1212

    
1213
  def _FailingWipeCb(self, (disk, _), offset, size):
1214
    # This should only ever be called for the first disk
1215
    self.assertEqual(disk.logical_id, "disk0")
1216
    return (False, None)
1217

    
1218
  def testFailingWipe(self):
1219
    node_uuid = "node13445-uuid"
1220
    pt = _DiskPauseTracker()
1221

    
1222
    lu = _FakeLU(rpc=_RpcForDiskWipe(node_uuid, pt, self._FailingWipeCb),
1223
                 cfg=_ConfigForDiskWipe(node_uuid))
1224

    
1225
    disks = [
1226
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk0",
1227
                   size=100 * 1024),
1228
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk1",
1229
                   size=500 * 1024),
1230
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk2", size=256),
1231
      ]
1232

    
1233
    inst = objects.Instance(name="inst562",
1234
                            primary_node=node_uuid,
1235
                            disk_template=constants.DT_PLAIN,
1236
                            disks=disks)
1237

    
1238
    try:
1239
      instance.WipeDisks(lu, inst)
1240
    except errors.OpExecError, err:
1241
      self.assertTrue(str(err), "Could not wipe disk 0 at offset 0 ")
1242
    else:
1243
      self.fail("Did not raise exception")
1244

    
1245
    # Check if all disks were paused and resumed
1246
    self.assertEqual(pt.history, [
1247
      ("disk0", 100 * 1024, True),
1248
      ("disk1", 500 * 1024, True),
1249
      ("disk2", 256, True),
1250
      ("disk0", 100 * 1024, False),
1251
      ("disk1", 500 * 1024, False),
1252
      ("disk2", 256, False),
1253
      ])
1254

    
1255
  def _PrepareWipeTest(self, start_offset, disks):
1256
    node_name = "node-with-offset%s.example.com" % start_offset
1257
    pauset = _DiskPauseTracker()
1258
    progresst = _DiskWipeProgressTracker(start_offset)
1259

    
1260
    lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, pauset, progresst),
1261
                 cfg=_ConfigForDiskWipe(node_name))
1262

    
1263
    instance = objects.Instance(name="inst3560",
1264
                                primary_node=node_name,
1265
                                disk_template=constants.DT_PLAIN,
1266
                                disks=disks)
1267

    
1268
    return (lu, instance, pauset, progresst)
1269

    
1270
  def testNormalWipe(self):
1271
    disks = [
1272
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk0", size=1024),
1273
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk1",
1274
                   size=500 * 1024),
1275
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk2", size=128),
1276
      objects.Disk(dev_type=constants.LD_LV, logical_id="disk3",
1277
                   size=constants.MAX_WIPE_CHUNK),
1278
      ]
1279

    
1280
    (lu, inst, pauset, progresst) = self._PrepareWipeTest(0, disks)
1281

    
1282
    instance.WipeDisks(lu, inst)
1283

    
1284
    self.assertEqual(pauset.history, [
1285
      ("disk0", 1024, True),
1286
      ("disk1", 500 * 1024, True),
1287
      ("disk2", 128, True),
1288
      ("disk3", constants.MAX_WIPE_CHUNK, True),
1289
      ("disk0", 1024, False),
1290
      ("disk1", 500 * 1024, False),
1291
      ("disk2", 128, False),
1292
      ("disk3", constants.MAX_WIPE_CHUNK, False),
1293
      ])
1294

    
1295
    # Ensure the complete disk has been wiped
1296
    self.assertEqual(progresst.progress,
1297
                     dict((i.logical_id, i.size) for i in disks))
1298

    
1299
  def testWipeWithStartOffset(self):
1300
    for start_offset in [0, 280, 8895, 1563204]:
1301
      disks = [
1302
        objects.Disk(dev_type=constants.LD_LV, logical_id="disk0",
1303
                     size=128),
1304
        objects.Disk(dev_type=constants.LD_LV, logical_id="disk1",
1305
                     size=start_offset + (100 * 1024)),
1306
        ]
1307

    
1308
      (lu, inst, pauset, progresst) = \
1309
        self._PrepareWipeTest(start_offset, disks)
1310

    
1311
      # Test start offset with only one disk
1312
      instance.WipeDisks(lu, inst,
1313
                         disks=[(1, disks[1], start_offset)])
1314

    
1315
      # Only the second disk may have been paused and wiped
1316
      self.assertEqual(pauset.history, [
1317
        ("disk1", start_offset + (100 * 1024), True),
1318
        ("disk1", start_offset + (100 * 1024), False),
1319
        ])
1320
      self.assertEqual(progresst.progress, {
1321
        "disk1": disks[1].size,
1322
        })
1323

    
1324

    
1325
class TestCheckOpportunisticLocking(unittest.TestCase):
1326
  class OpTest(opcodes.OpCode):
1327
    OP_PARAMS = [
1328
      ("opportunistic_locking", False, ht.TBool, None),
1329
      ("iallocator", None, ht.TMaybe(ht.TNonEmptyString), "")
1330
      ]
1331

    
1332
  @classmethod
1333
  def _MakeOp(cls, **kwargs):
1334
    op = cls.OpTest(**kwargs)
1335
    op.Validate(True)
1336
    return op
1337

    
1338
  def testMissingAttributes(self):
1339
    self.assertRaises(AttributeError, instance._CheckOpportunisticLocking,
1340
                      object())
1341

    
1342
  def testDefaults(self):
1343
    op = self._MakeOp()
1344
    instance._CheckOpportunisticLocking(op)
1345

    
1346
  def test(self):
1347
    for iallocator in [None, "something", "other"]:
1348
      for opplock in [False, True]:
1349
        op = self._MakeOp(iallocator=iallocator,
1350
                          opportunistic_locking=opplock)
1351
        if opplock and not iallocator:
1352
          self.assertRaises(errors.OpPrereqError,
1353
                            instance._CheckOpportunisticLocking, op)
1354
        else:
1355
          instance._CheckOpportunisticLocking(op)
1356

    
1357

    
1358
class TestLUInstanceRemove(CmdlibTestCase):
1359
  def testRemoveMissingInstance(self):
1360
    op = opcodes.OpInstanceRemove(instance_name="missing.inst")
1361
    self.ExecOpCodeExpectOpPrereqError(op, "Instance 'missing.inst' not known")
1362

    
1363
  def testRemoveInst(self):
1364
    inst = self.cfg.AddNewInstance(disks=[])
1365
    op = opcodes.OpInstanceRemove(instance_name=inst.name)
1366
    self.ExecOpCode(op)
1367

    
1368

    
1369
class TestLUInstanceMove(CmdlibTestCase):
1370
  def setUp(self):
1371
    super(TestLUInstanceMove, self).setUp()
1372

    
1373
    self.node = self.cfg.AddNewNode()
1374

    
1375
    self.rpc.call_blockdev_assemble.return_value = \
1376
      self.RpcResultsBuilder() \
1377
        .CreateSuccessfulNodeResult(self.node, "/dev/mocked_path")
1378
    self.rpc.call_blockdev_export.return_value = \
1379
      self.RpcResultsBuilder() \
1380
        .CreateSuccessfulNodeResult(self.master, "")
1381
    self.rpc.call_blockdev_remove.return_value = \
1382
      self.RpcResultsBuilder() \
1383
        .CreateSuccessfulNodeResult(self.master, "")
1384

    
1385
  def testMissingInstance(self):
1386
    op = opcodes.OpInstanceMove(instance_name="missing.inst",
1387
                                target_node=self.node.name)
1388
    self.ExecOpCodeExpectOpPrereqError(op, "Instance 'missing.inst' not known")
1389

    
1390
  def testUncopyableDiskTemplate(self):
1391
    inst = self.cfg.AddNewInstance(disk_template=constants.DT_SHARED_FILE)
1392
    op = opcodes.OpInstanceMove(instance_name=inst.name,
1393
                                target_node=self.node.name)
1394
    self.ExecOpCodeExpectOpPrereqError(
1395
      op, "Disk template sharedfile not suitable for copying")
1396

    
1397
  def testAlreadyOnTargetNode(self):
1398
    inst = self.cfg.AddNewInstance()
1399
    op = opcodes.OpInstanceMove(instance_name=inst.name,
1400
                                target_node=self.master.name)
1401
    self.ExecOpCodeExpectOpPrereqError(
1402
      op, "Instance .* is already on the node .*")
1403

    
1404
  def testMoveStoppedInstance(self):
1405
    inst = self.cfg.AddNewInstance()
1406
    op = opcodes.OpInstanceMove(instance_name=inst.name,
1407
                                target_node=self.node.name)
1408
    self.ExecOpCode(op)
1409

    
1410
  def testMoveRunningInstance(self):
1411
    self.rpc.call_node_info.return_value = \
1412
      self.RpcResultsBuilder() \
1413
        .AddSuccessfulNode(self.node,
1414
                           (NotImplemented, NotImplemented,
1415
                            ({"memory_free": 10000}, ))) \
1416
        .Build()
1417
    self.rpc.call_instance_start.return_value = \
1418
      self.RpcResultsBuilder() \
1419
        .CreateSuccessfulNodeResult(self.node, "")
1420

    
1421
    inst = self.cfg.AddNewInstance(admin_state=constants.ADMINST_UP)
1422
    op = opcodes.OpInstanceMove(instance_name=inst.name,
1423
                                target_node=self.node.name)
1424
    self.ExecOpCode(op)
1425

    
1426
  def testMoveFailingStart(self):
1427
    self.rpc.call_node_info.return_value = \
1428
      self.RpcResultsBuilder() \
1429
        .AddSuccessfulNode(self.node,
1430
                           (NotImplemented, NotImplemented,
1431
                            ({"memory_free": 10000}, ))) \
1432
        .Build()
1433
    self.rpc.call_instance_start.return_value = \
1434
      self.RpcResultsBuilder() \
1435
        .CreateFailedNodeResult(self.node)
1436

    
1437
    inst = self.cfg.AddNewInstance(admin_state=constants.ADMINST_UP)
1438
    op = opcodes.OpInstanceMove(instance_name=inst.name,
1439
                                target_node=self.node.name)
1440
    self.ExecOpCodeExpectOpExecError(
1441
      op, "Could not start instance .* on node .*")
1442

    
1443
  def testMoveFailingBlockdevAssemble(self):
1444
    inst = self.cfg.AddNewInstance()
1445
    self.rpc.call_blockdev_assemble.return_value = \
1446
      self.RpcResultsBuilder() \
1447
        .CreateFailedNodeResult(self.node)
1448
    op = opcodes.OpInstanceMove(instance_name=inst.name,
1449
                                target_node=self.node.name)
1450
    self.ExecOpCodeExpectOpExecError(op, "Errors during disk copy")
1451

    
1452
  def testMoveFailingBlockdevExport(self):
1453
    inst = self.cfg.AddNewInstance()
1454
    self.rpc.call_blockdev_export.return_value = \
1455
      self.RpcResultsBuilder() \
1456
        .CreateFailedNodeResult(self.node)
1457
    op = opcodes.OpInstanceMove(instance_name=inst.name,
1458
                                target_node=self.node.name)
1459
    self.ExecOpCodeExpectOpExecError(op, "Errors during disk copy")
1460

    
1461

    
1462
if __name__ == "__main__":
1463
  testutils.GanetiTestProgram()