Statistics
| Branch: | Tag: | Revision:

root / test / py / cmdlib / instance_unittest.py @ 6bce7ba2

History | View | Annotate | Download (85.4 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.rpc import node as 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.example.com", "203.0.113.1")
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 testAutoIpNoNameCheck(self):
184
    op = self.CopyOpCode(self.diskless_op,
185
                         nics=[{
186
                           constants.INIC_IP: constants.VALUE_AUTO
187
                         }],
188
                         ip_check=False,
189
                         name_check=False)
190
    self.ExecOpCodeExpectOpPrereqError(
191
      op, "IP address set to auto but name checks have been skipped")
192

    
193
  def testAutoIp(self):
194
    op = self.CopyOpCode(self.diskless_op,
195
                         nics=[{
196
                           constants.INIC_IP: constants.VALUE_AUTO
197
                         }])
198
    self.ExecOpCode(op)
199

    
200
  def testPoolIpNoNetwork(self):
201
    op = self.CopyOpCode(self.diskless_op,
202
                         nics=[{
203
                           constants.INIC_IP: constants.NIC_IP_POOL
204
                         }])
205
    self.ExecOpCodeExpectOpPrereqError(
206
      op, "if ip=pool, parameter network must be passed too")
207

    
208
  def testValidIp(self):
209
    op = self.CopyOpCode(self.diskless_op,
210
                         nics=[{
211
                           constants.INIC_IP: "203.0.113.1"
212
                         }])
213
    self.ExecOpCode(op)
214

    
215
  def testRoutedNoIp(self):
216
    op = self.CopyOpCode(self.diskless_op,
217
                         nics=[{
218
                           constants.INIC_MODE: constants.NIC_MODE_ROUTED
219
                         }])
220
    self.ExecOpCodeExpectOpPrereqError(
221
      op, "Routed nic mode requires an ip address")
222

    
223
  def testValicMac(self):
224
    op = self.CopyOpCode(self.diskless_op,
225
                         nics=[{
226
                           constants.INIC_MAC: "f0:df:f4:a3:d1:cf"
227
                         }])
228
    self.ExecOpCode(op)
229

    
230
  def testValidNicParams(self):
231
    op = self.CopyOpCode(self.diskless_op,
232
                         nics=[{
233
                           constants.INIC_MODE: constants.NIC_MODE_BRIDGED,
234
                           constants.INIC_LINK: "br_mock"
235
                         }])
236
    self.ExecOpCode(op)
237

    
238
  def testValidNicParamsOpenVSwitch(self):
239
    op = self.CopyOpCode(self.diskless_op,
240
                         nics=[{
241
                           constants.INIC_MODE: constants.NIC_MODE_OVS,
242
                           constants.INIC_VLAN: "1"
243
                         }])
244
    self.ExecOpCode(op)
245

    
246
  def testNicNoneName(self):
247
    op = self.CopyOpCode(self.diskless_op,
248
                         nics=[{
249
                           constants.INIC_NAME: constants.VALUE_NONE
250
                         }])
251
    self.ExecOpCode(op)
252

    
253
  def testConflictingIP(self):
254
    op = self.CopyOpCode(self.diskless_op,
255
                         nics=[{
256
                           constants.INIC_IP: self.net.gateway[:-1] + "2"
257
                         }])
258
    self.ExecOpCodeExpectOpPrereqError(
259
      op, "The requested IP address .* belongs to network .*, but the target"
260
          " NIC does not.")
261

    
262
  def testVLanFormat(self):
263
    for vlan in [".pinky", ":bunny", ":1:pinky", "bunny"]:
264
      self.ResetMocks()
265
      op = self.CopyOpCode(self.diskless_op,
266
                           nics=[{
267
                             constants.INIC_VLAN: vlan
268
                           }])
269
      self.ExecOpCodeExpectOpPrereqError(
270
        op, "Specified VLAN parameter is invalid")
271

    
272
  def testPoolIp(self):
273
    op = self.CopyOpCode(self.diskless_op,
274
                         nics=[{
275
                           constants.INIC_IP: constants.NIC_IP_POOL,
276
                           constants.INIC_NETWORK: self.net.name
277
                         }])
278
    self.ExecOpCode(op)
279

    
280
  def testPoolIpUnconnectedNetwork(self):
281
    net = self.cfg.AddNewNetwork()
282
    op = self.CopyOpCode(self.diskless_op,
283
                         nics=[{
284
                           constants.INIC_IP: constants.NIC_IP_POOL,
285
                           constants.INIC_NETWORK: net.name
286
                         }])
287
    self.ExecOpCodeExpectOpPrereqError(
288
      op, "No netparams found for network .*.")
289

    
290
  def testIpNotInNetwork(self):
291
    op = self.CopyOpCode(self.diskless_op,
292
                         nics=[{
293
                           constants.INIC_IP: "203.0.113.1",
294
                           constants.INIC_NETWORK: self.net.name
295
                         }])
296
    self.ExecOpCodeExpectOpPrereqError(
297
      op, "IP address .* already in use or does not belong to network .*")
298

    
299
  def testMixAdoptAndNotAdopt(self):
300
    op = self.CopyOpCode(self.diskless_op,
301
                         disk_template=constants.DT_PLAIN,
302
                         disks=[{
303
                           constants.IDISK_ADOPT: "lv1"
304
                         }, {}])
305
    self.ExecOpCodeExpectOpPrereqError(
306
      op, "Either all disks are adopted or none is")
307

    
308
  def testMustAdoptWithoutAdopt(self):
309
    op = self.CopyOpCode(self.diskless_op,
310
                         disk_template=constants.DT_BLOCK,
311
                         disks=[{}])
312
    self.ExecOpCodeExpectOpPrereqError(
313
      op, "Disk template blockdev requires disk adoption, but no 'adopt'"
314
          " parameter given")
315

    
316
  def testDontAdoptWithAdopt(self):
317
    op = self.CopyOpCode(self.diskless_op,
318
                         disk_template=constants.DT_DRBD8,
319
                         disks=[{
320
                           constants.IDISK_ADOPT: "lv1"
321
                         }])
322
    self.ExecOpCodeExpectOpPrereqError(
323
      op, "Disk adoption is not supported for the 'drbd' disk template")
324

    
325
  def testAdoptWithIAllocator(self):
326
    op = self.CopyOpCode(self.diskless_op,
327
                         disk_template=constants.DT_PLAIN,
328
                         disks=[{
329
                           constants.IDISK_ADOPT: "lv1"
330
                         }],
331
                         iallocator="mock")
332
    self.ExecOpCodeExpectOpPrereqError(
333
      op, "Disk adoption not allowed with an iallocator script")
334

    
335
  def testAdoptWithImport(self):
336
    op = self.CopyOpCode(self.diskless_op,
337
                         disk_template=constants.DT_PLAIN,
338
                         disks=[{
339
                           constants.IDISK_ADOPT: "lv1"
340
                         }],
341
                         mode=constants.INSTANCE_IMPORT)
342
    self.ExecOpCodeExpectOpPrereqError(
343
      op, "Disk adoption not allowed for instance import")
344

    
345
  def testArgumentCombinations(self):
346
    op = self.CopyOpCode(self.diskless_op,
347
                         # start flag will be flipped
348
                         no_install=True,
349
                         start=True,
350
                         # no allowed combination
351
                         ip_check=True,
352
                         name_check=False)
353
    self.ExecOpCodeExpectOpPrereqError(
354
      op, "Cannot do IP address check without a name check")
355

    
356
  def testInvalidFileDriver(self):
357
    op = self.CopyOpCode(self.diskless_op,
358
                         file_driver="invalid_file_driver")
359
    self.ExecOpCodeExpectOpPrereqError(
360
      op, "Parameter 'OP_INSTANCE_CREATE.file_driver' fails validation")
361

    
362
  def testMissingSecondaryNode(self):
363
    op = self.CopyOpCode(self.diskless_op,
364
                         pnode=self.master.name,
365
                         disk_template=constants.DT_DRBD8)
366
    self.ExecOpCodeExpectOpPrereqError(
367
      op, "The networked disk templates need a mirror node")
368

    
369
  def testIgnoredSecondaryNode(self):
370
    op = self.CopyOpCode(self.diskless_op,
371
                         pnode=self.master.name,
372
                         snode=self.node1.name,
373
                         disk_template=constants.DT_PLAIN)
374
    try:
375
      self.ExecOpCode(op)
376
    except Exception:
377
      pass
378
    self.mcpu.assertLogContainsRegex(
379
      "Secondary node will be ignored on non-mirrored disk template")
380

    
381
  def testMissingOsType(self):
382
    op = self.CopyOpCode(self.diskless_op,
383
                         os_type=self.REMOVE)
384
    self.ExecOpCodeExpectOpPrereqError(op, "No guest OS specified")
385

    
386
  def testBlacklistedOs(self):
387
    self.cluster.blacklisted_os = [self.os_name_variant]
388
    op = self.CopyOpCode(self.diskless_op)
389
    self.ExecOpCodeExpectOpPrereqError(
390
      op, "Guest OS .* is not allowed for installation")
391

    
392
  def testMissingDiskTemplate(self):
393
    self.cluster.enabled_disk_templates = [constants.DT_DISKLESS]
394
    op = self.CopyOpCode(self.diskless_op,
395
                         disk_template=self.REMOVE)
396
    self.ExecOpCode(op)
397

    
398
  def testExistingInstance(self):
399
    inst = self.cfg.AddNewInstance()
400
    op = self.CopyOpCode(self.diskless_op,
401
                         instance_name=inst.name)
402
    self.ExecOpCodeExpectOpPrereqError(
403
      op, "Instance .* is already in the cluster")
404

    
405
  def testPlainInstance(self):
406
    op = self.CopyOpCode(self.plain_op)
407
    self.ExecOpCode(op)
408

    
409
  def testPlainIAllocator(self):
410
    op = self.CopyOpCode(self.plain_op,
411
                         pnode=self.REMOVE,
412
                         iallocator="mock")
413
    self.ExecOpCode(op)
414

    
415
  def testIAllocatorOpportunisticLocking(self):
416
    op = self.CopyOpCode(self.plain_op,
417
                         pnode=self.REMOVE,
418
                         iallocator="mock",
419
                         opportunistic_locking=True)
420
    self.ExecOpCode(op)
421

    
422
  def testFailingIAllocator(self):
423
    self.iallocator_cls.return_value.success = False
424
    op = self.CopyOpCode(self.plain_op,
425
                         pnode=self.REMOVE,
426
                         iallocator="mock")
427
    self.ExecOpCodeExpectOpPrereqError(
428
      op, "Can't compute nodes using iallocator")
429

    
430
  def testDrbdInstance(self):
431
    op = self.CopyOpCode(self.drbd_op)
432
    self.ExecOpCode(op)
433

    
434
  def testDrbdIAllocator(self):
435
    op = self.CopyOpCode(self.drbd_op,
436
                         pnode=self.REMOVE,
437
                         snode=self.REMOVE,
438
                         iallocator="mock")
439
    self.ExecOpCode(op)
440

    
441
  def testFileInstance(self):
442
    op = self.CopyOpCode(self.file_op)
443
    self.ExecOpCode(op)
444

    
445
  def testFileInstanceNoClusterStorage(self):
446
    self.cluster.file_storage_dir = None
447
    op = self.CopyOpCode(self.file_op)
448
    self.ExecOpCodeExpectOpPrereqError(
449
      op, "Cluster file storage dir for 'file' storage type not defined")
450

    
451
  def testFileInstanceAdditionalPath(self):
452
    op = self.CopyOpCode(self.file_op,
453
                         file_storage_dir="mock_dir")
454
    self.ExecOpCode(op)
455

    
456
  def testIdentifyDefaults(self):
457
    op = self.CopyOpCode(self.plain_op,
458
                         hvparams={
459
                           constants.HV_BOOT_ORDER: "cd"
460
                         },
461
                         beparams=constants.BEC_DEFAULTS.copy(),
462
                         nics=[{
463
                           constants.NIC_MODE: constants.NIC_MODE_BRIDGED
464
                         }],
465
                         osparams={
466
                           self.os_name_variant: {}
467
                         },
468
                         osparams_private={},
469
                         identify_defaults=True)
470
    self.ExecOpCode(op)
471

    
472
    inst = self.cfg.GetAllInstancesInfo().values()[0]
473
    self.assertEqual(0, len(inst.hvparams))
474
    self.assertEqual(0, len(inst.beparams))
475
    assert self.os_name_variant not in inst.osparams or \
476
            len(inst.osparams[self.os_name_variant]) == 0
477

    
478
  def testOfflineNode(self):
479
    self.node1.offline = True
480
    op = self.CopyOpCode(self.diskless_op,
481
                         pnode=self.node1.name)
482
    self.ExecOpCodeExpectOpPrereqError(op, "Cannot use offline primary node")
483

    
484
  def testDrainedNode(self):
485
    self.node1.drained = True
486
    op = self.CopyOpCode(self.diskless_op,
487
                         pnode=self.node1.name)
488
    self.ExecOpCodeExpectOpPrereqError(op, "Cannot use drained primary node")
489

    
490
  def testNonVmCapableNode(self):
491
    self.node1.vm_capable = False
492
    op = self.CopyOpCode(self.diskless_op,
493
                         pnode=self.node1.name)
494
    self.ExecOpCodeExpectOpPrereqError(
495
      op, "Cannot use non-vm_capable primary node")
496

    
497
  def testNonEnabledHypervisor(self):
498
    self.cluster.enabled_hypervisors = [constants.HT_XEN_HVM]
499
    op = self.CopyOpCode(self.diskless_op,
500
                         hypervisor=constants.HT_FAKE)
501
    self.ExecOpCodeExpectOpPrereqError(
502
      op, "Selected hypervisor .* not enabled in the cluster")
503

    
504
  def testAddTag(self):
505
    op = self.CopyOpCode(self.diskless_op,
506
                         tags=["tag"])
507
    self.ExecOpCode(op)
508

    
509
  def testInvalidTag(self):
510
    op = self.CopyOpCode(self.diskless_op,
511
                         tags=["too_long" * 20])
512
    self.ExecOpCodeExpectException(op, errors.TagError, "Tag too long")
513

    
514
  def testPingableInstanceName(self):
515
    self.netutils_mod.TcpPing.return_value = True
516
    op = self.CopyOpCode(self.diskless_op)
517
    self.ExecOpCodeExpectOpPrereqError(
518
      op, "IP .* of instance diskless.test.com already in use")
519

    
520
  def testPrimaryIsSecondaryNode(self):
521
    op = self.CopyOpCode(self.drbd_op,
522
                         snode=self.drbd_op.pnode)
523
    self.ExecOpCodeExpectOpPrereqError(
524
      op, "The secondary node cannot be the primary node")
525

    
526
  def testPrimarySecondaryDifferentNodeGroups(self):
527
    group = self.cfg.AddNewNodeGroup()
528
    self.node2.group = group.uuid
529
    op = self.CopyOpCode(self.drbd_op)
530
    self.ExecOpCode(op)
531
    self.mcpu.assertLogContainsRegex(
532
      "The primary and secondary nodes are in two different node groups")
533

    
534
  def testExclusiveStorageUnsupportedDiskTemplate(self):
535
    self.node1.ndparams[constants.ND_EXCLUSIVE_STORAGE] = True
536
    op = self.CopyOpCode(self.drbd_op)
537
    self.ExecOpCodeExpectOpPrereqError(
538
      op, "Disk template drbd not supported with exclusive storage")
539

    
540
  def testAdoptPlain(self):
541
    self.rpc.call_lv_list.return_value = \
542
      self.RpcResultsBuilder() \
543
        .AddSuccessfulNode(self.master, {
544
          "xenvg/mock_disk_1": (10000, None, False)
545
        }) \
546
        .Build()
547
    op = self.CopyOpCode(self.plain_op)
548
    op.disks[0].update({constants.IDISK_ADOPT: "mock_disk_1"})
549
    self.ExecOpCode(op)
550

    
551
  def testAdoptPlainMissingLv(self):
552
    self.rpc.call_lv_list.return_value = \
553
      self.RpcResultsBuilder() \
554
        .AddSuccessfulNode(self.master, {}) \
555
        .Build()
556
    op = self.CopyOpCode(self.plain_op)
557
    op.disks[0].update({constants.IDISK_ADOPT: "mock_disk_1"})
558
    self.ExecOpCodeExpectOpPrereqError(op, "Missing logical volume")
559

    
560
  def testAdoptPlainOnlineLv(self):
561
    self.rpc.call_lv_list.return_value = \
562
      self.RpcResultsBuilder() \
563
        .AddSuccessfulNode(self.master, {
564
          "xenvg/mock_disk_1": (10000, None, True)
565
        }) \
566
        .Build()
567
    op = self.CopyOpCode(self.plain_op)
568
    op.disks[0].update({constants.IDISK_ADOPT: "mock_disk_1"})
569
    self.ExecOpCodeExpectOpPrereqError(
570
      op, "Online logical volumes found, cannot adopt")
571

    
572
  def testAdoptBlock(self):
573
    self.rpc.call_bdev_sizes.return_value = \
574
      self.RpcResultsBuilder() \
575
        .AddSuccessfulNode(self.master, {
576
          "/dev/disk/block0": 10000
577
        }) \
578
        .Build()
579
    op = self.CopyOpCode(self.block_op)
580
    self.ExecOpCode(op)
581

    
582
  def testAdoptBlockDuplicateNames(self):
583
    op = self.CopyOpCode(self.block_op,
584
                         disks=[{
585
                           constants.IDISK_SIZE: 0,
586
                           constants.IDISK_ADOPT: "/dev/disk/block0"
587
                         }, {
588
                           constants.IDISK_SIZE: 0,
589
                           constants.IDISK_ADOPT: "/dev/disk/block0"
590
                         }])
591
    self.ExecOpCodeExpectOpPrereqError(
592
      op, "Duplicate disk names given for adoption")
593

    
594
  def testAdoptBlockInvalidNames(self):
595
    op = self.CopyOpCode(self.block_op,
596
                         disks=[{
597
                           constants.IDISK_SIZE: 0,
598
                           constants.IDISK_ADOPT: "/invalid/block0"
599
                         }])
600
    self.ExecOpCodeExpectOpPrereqError(
601
      op, "Device node.* lie outside .* and cannot be adopted")
602

    
603
  def testAdoptBlockMissingDisk(self):
604
    self.rpc.call_bdev_sizes.return_value = \
605
      self.RpcResultsBuilder() \
606
        .AddSuccessfulNode(self.master, {}) \
607
        .Build()
608
    op = self.CopyOpCode(self.block_op)
609
    self.ExecOpCodeExpectOpPrereqError(op, "Missing block device")
610

    
611
  def testNoWaitForSyncDrbd(self):
612
    op = self.CopyOpCode(self.drbd_op,
613
                         wait_for_sync=False)
614
    self.ExecOpCode(op)
615

    
616
  def testNoWaitForSyncPlain(self):
617
    op = self.CopyOpCode(self.plain_op,
618
                         wait_for_sync=False)
619
    self.ExecOpCode(op)
620

    
621
  def testImportPlainFromGivenSrcNode(self):
622
    exp_info = """
623
[export]
624
version=0
625
os=mock_os
626
[instance]
627
name=old_name.example.com
628
"""
629

    
630
    self.rpc.call_export_info.return_value = \
631
      self.RpcResultsBuilder() \
632
        .CreateSuccessfulNodeResult(self.master, exp_info)
633
    op = self.CopyOpCode(self.plain_op,
634
                         mode=constants.INSTANCE_IMPORT,
635
                         src_node=self.master.name)
636
    self.ExecOpCode(op)
637

    
638
  def testImportPlainWithoutSrcNodeNotFound(self):
639
    op = self.CopyOpCode(self.plain_op,
640
                         mode=constants.INSTANCE_IMPORT)
641
    self.ExecOpCodeExpectOpPrereqError(
642
      op, "No export found for relative path")
643

    
644
  def testImportPlainWithoutSrcNode(self):
645
    exp_info = """
646
[export]
647
version=0
648
os=mock_os
649
[instance]
650
name=old_name.example.com
651
"""
652

    
653
    self.rpc.call_export_list.return_value = \
654
      self.RpcResultsBuilder() \
655
        .AddSuccessfulNode(self.master, {"mock_path": {}}) \
656
        .Build()
657
    self.rpc.call_export_info.return_value = \
658
      self.RpcResultsBuilder() \
659
        .CreateSuccessfulNodeResult(self.master, exp_info)
660

    
661
    op = self.CopyOpCode(self.plain_op,
662
                         mode=constants.INSTANCE_IMPORT,
663
                         src_path="mock_path")
664
    self.ExecOpCode(op)
665

    
666
  def testImportPlainCorruptExportInfo(self):
667
    exp_info = ""
668
    self.rpc.call_export_info.return_value = \
669
      self.RpcResultsBuilder() \
670
        .CreateSuccessfulNodeResult(self.master, exp_info)
671
    op = self.CopyOpCode(self.plain_op,
672
                         mode=constants.INSTANCE_IMPORT,
673
                         src_node=self.master.name)
674
    self.ExecOpCodeExpectException(op, errors.ProgrammerError,
675
                                   "Corrupted export config")
676

    
677
  def testImportPlainWrongExportInfoVersion(self):
678
    exp_info = """
679
[export]
680
version=1
681
"""
682
    self.rpc.call_export_info.return_value = \
683
      self.RpcResultsBuilder() \
684
        .CreateSuccessfulNodeResult(self.master, exp_info)
685
    op = self.CopyOpCode(self.plain_op,
686
                         mode=constants.INSTANCE_IMPORT,
687
                         src_node=self.master.name)
688
    self.ExecOpCodeExpectOpPrereqError(op, "Wrong export version")
689

    
690
  def testImportPlainWithParametersAndImport(self):
691
    exp_info = """
692
[export]
693
version=0
694
os=mock_os
695
[instance]
696
name=old_name.example.com
697
disk0_size=1024
698
disk1_size=1500
699
disk1_dump=mock_path
700
nic0_mode=bridged
701
nic0_link=br_mock
702
nic0_mac=f6:ab:f4:45:d1:af
703
nic0_ip=192.0.2.1
704
tags=tag1 tag2
705
hypervisor=xen-hvm
706
[hypervisor]
707
boot_order=cd
708
[backend]
709
memory=1024
710
vcpus=8
711
[os]
712
param1=val1
713
"""
714

    
715
    self.rpc.call_export_info.return_value = \
716
      self.RpcResultsBuilder() \
717
        .CreateSuccessfulNodeResult(self.master, exp_info)
718
    self.rpc.call_import_start.return_value = \
719
      self.RpcResultsBuilder() \
720
        .CreateSuccessfulNodeResult(self.master, "daemon_name")
721
    self.rpc.call_impexp_status.return_value = \
722
      self.RpcResultsBuilder() \
723
        .CreateSuccessfulNodeResult(self.master,
724
                                    [
725
                                      objects.ImportExportStatus(exit_status=0)
726
                                    ])
727
    self.rpc.call_impexp_cleanup.return_value = \
728
      self.RpcResultsBuilder() \
729
        .CreateSuccessfulNodeResult(self.master, True)
730

    
731
    op = self.CopyOpCode(self.plain_op,
732
                         disks=[],
733
                         nics=[],
734
                         tags=[],
735
                         hypervisor=None,
736
                         hvparams={},
737
                         mode=constants.INSTANCE_IMPORT,
738
                         src_node=self.master.name)
739
    self.ExecOpCode(op)
740

    
741

    
742
class TestCheckOSVariant(CmdlibTestCase):
743
  def testNoVariantsSupported(self):
744
    os = self.cfg.CreateOs(supported_variants=[])
745
    self.assertRaises(errors.OpPrereqError, instance._CheckOSVariant,
746
                      os, "os+variant")
747

    
748
  def testNoVariantGiven(self):
749
    os = self.cfg.CreateOs(supported_variants=["default"])
750
    self.assertRaises(errors.OpPrereqError, instance._CheckOSVariant,
751
                      os, "os")
752

    
753
  def testWrongVariantGiven(self):
754
    os = self.cfg.CreateOs(supported_variants=["default"])
755
    self.assertRaises(errors.OpPrereqError, instance._CheckOSVariant,
756
                      os, "os+wrong_variant")
757

    
758
  def testOkWithVariant(self):
759
    os = self.cfg.CreateOs(supported_variants=["default"])
760
    instance._CheckOSVariant(os, "os+default")
761

    
762
  def testOkWithoutVariant(self):
763
    os = self.cfg.CreateOs(supported_variants=[])
764
    instance._CheckOSVariant(os, "os")
765

    
766

    
767
class TestCheckTargetNodeIPolicy(TestLUInstanceCreate):
768
  def setUp(self):
769
    super(TestCheckTargetNodeIPolicy, self).setUp()
770

    
771
    self.op = self.diskless_op
772

    
773
    self.instance = self.cfg.AddNewInstance()
774
    self.target_group = self.cfg.AddNewNodeGroup()
775
    self.target_node = self.cfg.AddNewNode(group=self.target_group)
776

    
777
  @withLockedLU
778
  def testNoViolation(self, lu):
779
    compute_recoder = mock.Mock(return_value=[])
780
    instance.CheckTargetNodeIPolicy(lu, NotImplemented, self.instance,
781
                                    self.target_node, NotImplemented,
782
                                    _compute_fn=compute_recoder)
783
    self.assertTrue(compute_recoder.called)
784
    self.mcpu.assertLogIsEmpty()
785

    
786
  @withLockedLU
787
  def testNoIgnore(self, lu):
788
    compute_recoder = mock.Mock(return_value=["mem_size not in range"])
789
    self.assertRaises(errors.OpPrereqError, instance.CheckTargetNodeIPolicy,
790
                      lu, NotImplemented, self.instance,
791
                      self.target_node, NotImplemented,
792
                      _compute_fn=compute_recoder)
793
    self.assertTrue(compute_recoder.called)
794
    self.mcpu.assertLogIsEmpty()
795

    
796
  @withLockedLU
797
  def testIgnoreViolation(self, lu):
798
    compute_recoder = mock.Mock(return_value=["mem_size not in range"])
799
    instance.CheckTargetNodeIPolicy(lu, NotImplemented, self.instance,
800
                                    self.target_node, NotImplemented,
801
                                    ignore=True, _compute_fn=compute_recoder)
802
    self.assertTrue(compute_recoder.called)
803
    msg = ("Instance does not meet target node group's .* instance policy:"
804
           " mem_size not in range")
805
    self.mcpu.assertLogContainsRegex(msg)
806

    
807

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

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

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

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

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

    
859
    mods = instance._PrepareContainerMods([
860
      (constants.DDM_REMOVE, 0, object()),
861
      ], None)
862
    self.assertRaises(AssertionError, instance._ApplyContainerMods,
863
                      "test", [""], None, mods, None, None, None)
864

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

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

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

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

    
908
  @staticmethod
909
  def _CreateTestFn(idx, params, private):
910
    private.data = ("add", idx, params)
911
    return ((100 * idx, params), [
912
      ("test/%s" % idx, hex(idx)),
913
      ])
914

    
915
  @staticmethod
916
  def _ModifyTestFn(idx, item, params, private):
917
    private.data = ("modify", idx, params)
918
    return [
919
      ("test/%s" % idx, "modify %s" % params),
920
      ]
921

    
922
  @staticmethod
923
  def _RemoveTestFn(idx, item, private):
924
    private.data = ("remove", idx, item)
925

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

    
970

    
971
class _FakeConfigForGenDiskTemplate(ConfigMock):
972
  def __init__(self):
973
    super(_FakeConfigForGenDiskTemplate, self).__init__()
974

    
975
    self._unique_id = itertools.count()
976
    self._drbd_minor = itertools.count(20)
977
    self._port = itertools.count(constants.FIRST_DRBD_PORT)
978
    self._secret = itertools.count()
979

    
980
  def GenerateUniqueID(self, ec_id):
981
    return "ec%s-uq%s" % (ec_id, self._unique_id.next())
982

    
983
  def AllocateDRBDMinor(self, nodes, instance):
984
    return [self._drbd_minor.next()
985
            for _ in nodes]
986

    
987
  def AllocatePort(self):
988
    return self._port.next()
989

    
990
  def GenerateDRBDSecret(self, ec_id):
991
    return "ec%s-secret%s" % (ec_id, self._secret.next())
992

    
993

    
994
class TestGenerateDiskTemplate(CmdlibTestCase):
995
  def setUp(self):
996
    super(TestGenerateDiskTemplate, self).setUp()
997

    
998
    self.cfg = _FakeConfigForGenDiskTemplate()
999
    self.cluster.enabled_disk_templates = list(constants.DISK_TEMPLATES)
1000

    
1001
    self.nodegroup = self.cfg.AddNewNodeGroup(name="ng")
1002

    
1003
    self.lu = self.GetMockLU()
1004

    
1005
  @staticmethod
1006
  def GetDiskParams():
1007
    return copy.deepcopy(constants.DISK_DT_DEFAULTS)
1008

    
1009
  def testWrongDiskTemplate(self):
1010
    gdt = instance.GenerateDiskTemplate
1011
    disk_template = "##unknown##"
1012

    
1013
    assert disk_template not in constants.DISK_TEMPLATES
1014

    
1015
    self.assertRaises(errors.OpPrereqError, gdt, self.lu, disk_template,
1016
                      "inst26831.example.com", "node30113.example.com", [], [],
1017
                      NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1018
                      self.GetDiskParams())
1019

    
1020
  def testDiskless(self):
1021
    gdt = instance.GenerateDiskTemplate
1022

    
1023
    result = gdt(self.lu, constants.DT_DISKLESS, "inst27734.example.com",
1024
                 "node30113.example.com", [], [],
1025
                 NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1026
                 self.GetDiskParams())
1027
    self.assertEqual(result, [])
1028

    
1029
  def _TestTrivialDisk(self, template, disk_info, base_index, exp_dev_type,
1030
                       file_storage_dir=NotImplemented,
1031
                       file_driver=NotImplemented):
1032
    gdt = instance.GenerateDiskTemplate
1033

    
1034
    map(lambda params: utils.ForceDictType(params,
1035
                                           constants.IDISK_PARAMS_TYPES),
1036
        disk_info)
1037

    
1038
    # Check if non-empty list of secondaries is rejected
1039
    self.assertRaises(errors.ProgrammerError, gdt, self.lu,
1040
                      template, "inst25088.example.com",
1041
                      "node185.example.com", ["node323.example.com"], [],
1042
                      NotImplemented, NotImplemented, base_index,
1043
                      self.lu.LogInfo, self.GetDiskParams())
1044

    
1045
    result = gdt(self.lu, template, "inst21662.example.com",
1046
                 "node21741.example.com", [],
1047
                 disk_info, file_storage_dir, file_driver, base_index,
1048
                 self.lu.LogInfo, self.GetDiskParams())
1049

    
1050
    for (idx, disk) in enumerate(result):
1051
      self.assertTrue(isinstance(disk, objects.Disk))
1052
      self.assertEqual(disk.dev_type, exp_dev_type)
1053
      self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
1054
      self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
1055
      self.assertTrue(disk.children is None)
1056

    
1057
    self._CheckIvNames(result, base_index, base_index + len(disk_info))
1058
    instance._UpdateIvNames(base_index, result)
1059
    self._CheckIvNames(result, base_index, base_index + len(disk_info))
1060

    
1061
    return result
1062

    
1063
  def _CheckIvNames(self, disks, base_index, end_index):
1064
    self.assertEqual(map(operator.attrgetter("iv_name"), disks),
1065
                     ["disk/%s" % i for i in range(base_index, end_index)])
1066

    
1067
  def testPlain(self):
1068
    disk_info = [{
1069
      constants.IDISK_SIZE: 1024,
1070
      constants.IDISK_MODE: constants.DISK_RDWR,
1071
      }, {
1072
      constants.IDISK_SIZE: 4096,
1073
      constants.IDISK_VG: "othervg",
1074
      constants.IDISK_MODE: constants.DISK_RDWR,
1075
      }]
1076

    
1077
    result = self._TestTrivialDisk(constants.DT_PLAIN, disk_info, 3,
1078
                                   constants.DT_PLAIN)
1079

    
1080
    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1081
      ("xenvg", "ec1-uq0.disk3"),
1082
      ("othervg", "ec1-uq1.disk4"),
1083
      ])
1084

    
1085
  def testFile(self):
1086
    # anything != DT_FILE would do here
1087
    self.cluster.enabled_disk_templates = [constants.DT_PLAIN]
1088
    self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
1089
                      constants.DT_FILE, [], 0, NotImplemented)
1090
    self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
1091
                      constants.DT_SHARED_FILE, [], 0, NotImplemented)
1092

    
1093
    for disk_template in constants.DTS_FILEBASED:
1094
      disk_info = [{
1095
        constants.IDISK_SIZE: 80 * 1024,
1096
        constants.IDISK_MODE: constants.DISK_RDONLY,
1097
        }, {
1098
        constants.IDISK_SIZE: 4096,
1099
        constants.IDISK_MODE: constants.DISK_RDWR,
1100
        }, {
1101
        constants.IDISK_SIZE: 6 * 1024,
1102
        constants.IDISK_MODE: constants.DISK_RDWR,
1103
        }]
1104

    
1105
      self.cluster.enabled_disk_templates = [disk_template]
1106
      result = self._TestTrivialDisk(
1107
        disk_template, disk_info, 2, disk_template,
1108
        file_storage_dir="/tmp", file_driver=constants.FD_BLKTAP)
1109

    
1110
      if disk_template == constants.DT_GLUSTER:
1111
        # Here "inst21662.example.com" is actually the instance UUID, not its
1112
        # name, so while this result looks wrong, it is actually correct.
1113
        expected = [(constants.FD_BLKTAP,
1114
                     'ganeti/inst21662.example.com.%d' % x)
1115
                    for x in (2,3,4)]
1116
      else:
1117
        expected = [(constants.FD_BLKTAP,
1118
                     '/tmp/disk%d' % x)
1119
                    for x in (2,3,4)]
1120

    
1121
      self.assertEqual(map(operator.attrgetter("logical_id"), result),
1122
                       expected)
1123

    
1124
  def testBlock(self):
1125
    disk_info = [{
1126
      constants.IDISK_SIZE: 8 * 1024,
1127
      constants.IDISK_MODE: constants.DISK_RDWR,
1128
      constants.IDISK_ADOPT: "/tmp/some/block/dev",
1129
      }]
1130

    
1131
    result = self._TestTrivialDisk(constants.DT_BLOCK, disk_info, 10,
1132
                                   constants.DT_BLOCK)
1133

    
1134
    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1135
      (constants.BLOCKDEV_DRIVER_MANUAL, "/tmp/some/block/dev"),
1136
      ])
1137

    
1138
  def testRbd(self):
1139
    disk_info = [{
1140
      constants.IDISK_SIZE: 8 * 1024,
1141
      constants.IDISK_MODE: constants.DISK_RDONLY,
1142
      }, {
1143
      constants.IDISK_SIZE: 100 * 1024,
1144
      constants.IDISK_MODE: constants.DISK_RDWR,
1145
      }]
1146

    
1147
    result = self._TestTrivialDisk(constants.DT_RBD, disk_info, 0,
1148
                                   constants.DT_RBD)
1149

    
1150
    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1151
      ("rbd", "ec1-uq0.rbd.disk0"),
1152
      ("rbd", "ec1-uq1.rbd.disk1"),
1153
      ])
1154

    
1155
  def testDrbd8(self):
1156
    gdt = instance.GenerateDiskTemplate
1157
    drbd8_defaults = constants.DISK_LD_DEFAULTS[constants.DT_DRBD8]
1158
    drbd8_default_metavg = drbd8_defaults[constants.LDP_DEFAULT_METAVG]
1159

    
1160
    disk_info = [{
1161
      constants.IDISK_SIZE: 1024,
1162
      constants.IDISK_MODE: constants.DISK_RDWR,
1163
      }, {
1164
      constants.IDISK_SIZE: 100 * 1024,
1165
      constants.IDISK_MODE: constants.DISK_RDONLY,
1166
      constants.IDISK_METAVG: "metavg",
1167
      }, {
1168
      constants.IDISK_SIZE: 4096,
1169
      constants.IDISK_MODE: constants.DISK_RDWR,
1170
      constants.IDISK_VG: "vgxyz",
1171
      },
1172
      ]
1173

    
1174
    exp_logical_ids = [
1175
      [
1176
        (self.lu.cfg.GetVGName(), "ec1-uq0.disk0_data"),
1177
        (drbd8_default_metavg, "ec1-uq0.disk0_meta"),
1178
      ], [
1179
        (self.lu.cfg.GetVGName(), "ec1-uq1.disk1_data"),
1180
        ("metavg", "ec1-uq1.disk1_meta"),
1181
      ], [
1182
        ("vgxyz", "ec1-uq2.disk2_data"),
1183
        (drbd8_default_metavg, "ec1-uq2.disk2_meta"),
1184
      ]]
1185

    
1186
    assert len(exp_logical_ids) == len(disk_info)
1187

    
1188
    map(lambda params: utils.ForceDictType(params,
1189
                                           constants.IDISK_PARAMS_TYPES),
1190
        disk_info)
1191

    
1192
    # Check if empty list of secondaries is rejected
1193
    self.assertRaises(errors.ProgrammerError, gdt, self.lu, constants.DT_DRBD8,
1194
                      "inst827.example.com", "node1334.example.com", [],
1195
                      disk_info, NotImplemented, NotImplemented, 0,
1196
                      self.lu.LogInfo, self.GetDiskParams())
1197

    
1198
    result = gdt(self.lu, constants.DT_DRBD8, "inst827.example.com",
1199
                 "node1334.example.com", ["node12272.example.com"],
1200
                 disk_info, NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1201
                 self.GetDiskParams())
1202

    
1203
    for (idx, disk) in enumerate(result):
1204
      self.assertTrue(isinstance(disk, objects.Disk))
1205
      self.assertEqual(disk.dev_type, constants.DT_DRBD8)
1206
      self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
1207
      self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
1208

    
1209
      for child in disk.children:
1210
        self.assertTrue(isinstance(disk, objects.Disk))
1211
        self.assertEqual(child.dev_type, constants.DT_PLAIN)
1212
        self.assertTrue(child.children is None)
1213

    
1214
      self.assertEqual(map(operator.attrgetter("logical_id"), disk.children),
1215
                       exp_logical_ids[idx])
1216

    
1217
      self.assertEqual(len(disk.children), 2)
1218
      self.assertEqual(disk.children[0].size, disk.size)
1219
      self.assertEqual(disk.children[1].size, constants.DRBD_META_SIZE)
1220

    
1221
    self._CheckIvNames(result, 0, len(disk_info))
1222
    instance._UpdateIvNames(0, result)
1223
    self._CheckIvNames(result, 0, len(disk_info))
1224

    
1225
    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1226
      ("node1334.example.com", "node12272.example.com",
1227
       constants.FIRST_DRBD_PORT, 20, 21, "ec1-secret0"),
1228
      ("node1334.example.com", "node12272.example.com",
1229
       constants.FIRST_DRBD_PORT + 1, 22, 23, "ec1-secret1"),
1230
      ("node1334.example.com", "node12272.example.com",
1231
       constants.FIRST_DRBD_PORT + 2, 24, 25, "ec1-secret2"),
1232
      ])
1233

    
1234

    
1235
class _DiskPauseTracker:
1236
  def __init__(self):
1237
    self.history = []
1238

    
1239
  def __call__(self, (disks, instance), pause):
1240
    assert not (set(disks) - set(instance.disks))
1241

    
1242
    self.history.extend((i.logical_id, i.size, pause)
1243
                        for i in disks)
1244

    
1245
    return (True, [True] * len(disks))
1246

    
1247

    
1248
class _ConfigForDiskWipe:
1249
  def __init__(self, exp_node_uuid):
1250
    self._exp_node_uuid = exp_node_uuid
1251

    
1252
  def GetNodeName(self, node_uuid):
1253
    assert node_uuid == self._exp_node_uuid
1254
    return "name.of.expected.node"
1255

    
1256

    
1257
class _RpcForDiskWipe:
1258
  def __init__(self, exp_node, pause_cb, wipe_cb):
1259
    self._exp_node = exp_node
1260
    self._pause_cb = pause_cb
1261
    self._wipe_cb = wipe_cb
1262

    
1263
  def call_blockdev_pause_resume_sync(self, node, disks, pause):
1264
    assert node == self._exp_node
1265
    return rpc.RpcResult(data=self._pause_cb(disks, pause))
1266

    
1267
  def call_blockdev_wipe(self, node, bdev, offset, size):
1268
    assert node == self._exp_node
1269
    return rpc.RpcResult(data=self._wipe_cb(bdev, offset, size))
1270

    
1271

    
1272
class _DiskWipeProgressTracker:
1273
  def __init__(self, start_offset):
1274
    self._start_offset = start_offset
1275
    self.progress = {}
1276

    
1277
  def __call__(self, (disk, _), offset, size):
1278
    assert isinstance(offset, (long, int))
1279
    assert isinstance(size, (long, int))
1280

    
1281
    max_chunk_size = (disk.size / 100.0 * constants.MIN_WIPE_CHUNK_PERCENT)
1282

    
1283
    assert offset >= self._start_offset
1284
    assert (offset + size) <= disk.size
1285

    
1286
    assert size > 0
1287
    assert size <= constants.MAX_WIPE_CHUNK
1288
    assert size <= max_chunk_size
1289

    
1290
    assert offset == self._start_offset or disk.logical_id in self.progress
1291

    
1292
    # Keep track of progress
1293
    cur_progress = self.progress.setdefault(disk.logical_id, self._start_offset)
1294

    
1295
    assert cur_progress == offset
1296

    
1297
    # Record progress
1298
    self.progress[disk.logical_id] += size
1299

    
1300
    return (True, None)
1301

    
1302

    
1303
class TestWipeDisks(unittest.TestCase):
1304
  def _FailingPauseCb(self, (disks, _), pause):
1305
    self.assertEqual(len(disks), 3)
1306
    self.assertTrue(pause)
1307
    # Simulate an RPC error
1308
    return (False, "error")
1309

    
1310
  def testPauseFailure(self):
1311
    node_name = "node1372.example.com"
1312

    
1313
    lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, self._FailingPauseCb,
1314
                                     NotImplemented),
1315
                 cfg=_ConfigForDiskWipe(node_name))
1316

    
1317
    disks = [
1318
      objects.Disk(dev_type=constants.DT_PLAIN),
1319
      objects.Disk(dev_type=constants.DT_PLAIN),
1320
      objects.Disk(dev_type=constants.DT_PLAIN),
1321
      ]
1322

    
1323
    inst = objects.Instance(name="inst21201",
1324
                            primary_node=node_name,
1325
                            disk_template=constants.DT_PLAIN,
1326
                            disks=disks)
1327

    
1328
    self.assertRaises(errors.OpExecError, instance.WipeDisks, lu, inst)
1329

    
1330
  def _FailingWipeCb(self, (disk, _), offset, size):
1331
    # This should only ever be called for the first disk
1332
    self.assertEqual(disk.logical_id, "disk0")
1333
    return (False, None)
1334

    
1335
  def testFailingWipe(self):
1336
    node_uuid = "node13445-uuid"
1337
    pt = _DiskPauseTracker()
1338

    
1339
    lu = _FakeLU(rpc=_RpcForDiskWipe(node_uuid, pt, self._FailingWipeCb),
1340
                 cfg=_ConfigForDiskWipe(node_uuid))
1341

    
1342
    disks = [
1343
      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk0",
1344
                   size=100 * 1024),
1345
      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk1",
1346
                   size=500 * 1024),
1347
      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk2", size=256),
1348
      ]
1349

    
1350
    inst = objects.Instance(name="inst562",
1351
                            primary_node=node_uuid,
1352
                            disk_template=constants.DT_PLAIN,
1353
                            disks=disks)
1354

    
1355
    try:
1356
      instance.WipeDisks(lu, inst)
1357
    except errors.OpExecError, err:
1358
      self.assertTrue(str(err), "Could not wipe disk 0 at offset 0 ")
1359
    else:
1360
      self.fail("Did not raise exception")
1361

    
1362
    # Check if all disks were paused and resumed
1363
    self.assertEqual(pt.history, [
1364
      ("disk0", 100 * 1024, True),
1365
      ("disk1", 500 * 1024, True),
1366
      ("disk2", 256, True),
1367
      ("disk0", 100 * 1024, False),
1368
      ("disk1", 500 * 1024, False),
1369
      ("disk2", 256, False),
1370
      ])
1371

    
1372
  def _PrepareWipeTest(self, start_offset, disks):
1373
    node_name = "node-with-offset%s.example.com" % start_offset
1374
    pauset = _DiskPauseTracker()
1375
    progresst = _DiskWipeProgressTracker(start_offset)
1376

    
1377
    lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, pauset, progresst),
1378
                 cfg=_ConfigForDiskWipe(node_name))
1379

    
1380
    instance = objects.Instance(name="inst3560",
1381
                                primary_node=node_name,
1382
                                disk_template=constants.DT_PLAIN,
1383
                                disks=disks)
1384

    
1385
    return (lu, instance, pauset, progresst)
1386

    
1387
  def testNormalWipe(self):
1388
    disks = [
1389
      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk0", size=1024),
1390
      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk1",
1391
                   size=500 * 1024),
1392
      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk2", size=128),
1393
      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk3",
1394
                   size=constants.MAX_WIPE_CHUNK),
1395
      ]
1396

    
1397
    (lu, inst, pauset, progresst) = self._PrepareWipeTest(0, disks)
1398

    
1399
    instance.WipeDisks(lu, inst)
1400

    
1401
    self.assertEqual(pauset.history, [
1402
      ("disk0", 1024, True),
1403
      ("disk1", 500 * 1024, True),
1404
      ("disk2", 128, True),
1405
      ("disk3", constants.MAX_WIPE_CHUNK, True),
1406
      ("disk0", 1024, False),
1407
      ("disk1", 500 * 1024, False),
1408
      ("disk2", 128, False),
1409
      ("disk3", constants.MAX_WIPE_CHUNK, False),
1410
      ])
1411

    
1412
    # Ensure the complete disk has been wiped
1413
    self.assertEqual(progresst.progress,
1414
                     dict((i.logical_id, i.size) for i in disks))
1415

    
1416
  def testWipeWithStartOffset(self):
1417
    for start_offset in [0, 280, 8895, 1563204]:
1418
      disks = [
1419
        objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk0",
1420
                     size=128),
1421
        objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk1",
1422
                     size=start_offset + (100 * 1024)),
1423
        ]
1424

    
1425
      (lu, inst, pauset, progresst) = \
1426
        self._PrepareWipeTest(start_offset, disks)
1427

    
1428
      # Test start offset with only one disk
1429
      instance.WipeDisks(lu, inst,
1430
                         disks=[(1, disks[1], start_offset)])
1431

    
1432
      # Only the second disk may have been paused and wiped
1433
      self.assertEqual(pauset.history, [
1434
        ("disk1", start_offset + (100 * 1024), True),
1435
        ("disk1", start_offset + (100 * 1024), False),
1436
        ])
1437
      self.assertEqual(progresst.progress, {
1438
        "disk1": disks[1].size,
1439
        })
1440

    
1441

    
1442
class TestCheckOpportunisticLocking(unittest.TestCase):
1443
  class OpTest(opcodes.OpCode):
1444
    OP_PARAMS = [
1445
      ("opportunistic_locking", False, ht.TBool, None),
1446
      ("iallocator", None, ht.TMaybe(ht.TNonEmptyString), "")
1447
      ]
1448

    
1449
  @classmethod
1450
  def _MakeOp(cls, **kwargs):
1451
    op = cls.OpTest(**kwargs)
1452
    op.Validate(True)
1453
    return op
1454

    
1455
  def testMissingAttributes(self):
1456
    self.assertRaises(AttributeError, instance._CheckOpportunisticLocking,
1457
                      object())
1458

    
1459
  def testDefaults(self):
1460
    op = self._MakeOp()
1461
    instance._CheckOpportunisticLocking(op)
1462

    
1463
  def test(self):
1464
    for iallocator in [None, "something", "other"]:
1465
      for opplock in [False, True]:
1466
        op = self._MakeOp(iallocator=iallocator,
1467
                          opportunistic_locking=opplock)
1468
        if opplock and not iallocator:
1469
          self.assertRaises(errors.OpPrereqError,
1470
                            instance._CheckOpportunisticLocking, op)
1471
        else:
1472
          instance._CheckOpportunisticLocking(op)
1473

    
1474

    
1475
class TestLUInstanceRemove(CmdlibTestCase):
1476
  def testRemoveMissingInstance(self):
1477
    op = opcodes.OpInstanceRemove(instance_name="missing.inst")
1478
    self.ExecOpCodeExpectOpPrereqError(op, "Instance 'missing.inst' not known")
1479

    
1480
  def testRemoveInst(self):
1481
    inst = self.cfg.AddNewInstance(disks=[])
1482
    op = opcodes.OpInstanceRemove(instance_name=inst.name)
1483
    self.ExecOpCode(op)
1484

    
1485

    
1486
class TestLUInstanceMove(CmdlibTestCase):
1487
  def setUp(self):
1488
    super(TestLUInstanceMove, self).setUp()
1489

    
1490
    self.node = self.cfg.AddNewNode()
1491

    
1492
    self.rpc.call_blockdev_assemble.return_value = \
1493
      self.RpcResultsBuilder() \
1494
        .CreateSuccessfulNodeResult(self.node, ("/dev/mocked_path",
1495
                                    "/var/run/ganeti/instance-disks/mocked_d"))
1496
    self.rpc.call_blockdev_remove.return_value = \
1497
      self.RpcResultsBuilder() \
1498
        .CreateSuccessfulNodeResult(self.master, "")
1499

    
1500
    def ImportStart(node_uuid, opt, inst, component, args):
1501
      return self.RpcResultsBuilder() \
1502
               .CreateSuccessfulNodeResult(node_uuid,
1503
                                           "deamon_on_%s" % node_uuid)
1504
    self.rpc.call_import_start.side_effect = ImportStart
1505

    
1506
    def ImpExpStatus(node_uuid, name):
1507
      return self.RpcResultsBuilder() \
1508
               .CreateSuccessfulNodeResult(node_uuid,
1509
                                           [objects.ImportExportStatus(
1510
                                             exit_status=0
1511
                                           )])
1512
    self.rpc.call_impexp_status.side_effect = ImpExpStatus
1513

    
1514
    def ImpExpCleanup(node_uuid, name):
1515
      return self.RpcResultsBuilder() \
1516
               .CreateSuccessfulNodeResult(node_uuid)
1517
    self.rpc.call_impexp_cleanup.side_effect = ImpExpCleanup
1518

    
1519
  def testMissingInstance(self):
1520
    op = opcodes.OpInstanceMove(instance_name="missing.inst",
1521
                                target_node=self.node.name)
1522
    self.ExecOpCodeExpectOpPrereqError(op, "Instance 'missing.inst' not known")
1523

    
1524
  def testUncopyableDiskTemplate(self):
1525
    inst = self.cfg.AddNewInstance(disk_template=constants.DT_SHARED_FILE)
1526
    op = opcodes.OpInstanceMove(instance_name=inst.name,
1527
                                target_node=self.node.name)
1528
    self.ExecOpCodeExpectOpPrereqError(
1529
      op, "Disk template sharedfile not suitable for copying")
1530

    
1531
  def testAlreadyOnTargetNode(self):
1532
    inst = self.cfg.AddNewInstance()
1533
    op = opcodes.OpInstanceMove(instance_name=inst.name,
1534
                                target_node=self.master.name)
1535
    self.ExecOpCodeExpectOpPrereqError(
1536
      op, "Instance .* is already on the node .*")
1537

    
1538
  def testMoveStoppedInstance(self):
1539
    inst = self.cfg.AddNewInstance()
1540
    op = opcodes.OpInstanceMove(instance_name=inst.name,
1541
                                target_node=self.node.name)
1542
    self.ExecOpCode(op)
1543

    
1544
  def testMoveRunningInstance(self):
1545
    self.rpc.call_node_info.return_value = \
1546
      self.RpcResultsBuilder() \
1547
        .AddSuccessfulNode(self.node,
1548
                           (NotImplemented, NotImplemented,
1549
                            ({"memory_free": 10000}, ))) \
1550
        .Build()
1551
    self.rpc.call_instance_start.return_value = \
1552
      self.RpcResultsBuilder() \
1553
        .CreateSuccessfulNodeResult(self.node, "")
1554

    
1555
    inst = self.cfg.AddNewInstance(admin_state=constants.ADMINST_UP)
1556
    op = opcodes.OpInstanceMove(instance_name=inst.name,
1557
                                target_node=self.node.name)
1558
    self.ExecOpCode(op)
1559

    
1560
  def testMoveFailingStartInstance(self):
1561
    self.rpc.call_node_info.return_value = \
1562
      self.RpcResultsBuilder() \
1563
        .AddSuccessfulNode(self.node,
1564
                           (NotImplemented, NotImplemented,
1565
                            ({"memory_free": 10000}, ))) \
1566
        .Build()
1567
    self.rpc.call_instance_start.return_value = \
1568
      self.RpcResultsBuilder() \
1569
        .CreateFailedNodeResult(self.node)
1570

    
1571
    inst = self.cfg.AddNewInstance(admin_state=constants.ADMINST_UP)
1572
    op = opcodes.OpInstanceMove(instance_name=inst.name,
1573
                                target_node=self.node.name)
1574
    self.ExecOpCodeExpectOpExecError(
1575
      op, "Could not start instance .* on node .*")
1576

    
1577
  def testMoveFailingImpExpDaemonExitCode(self):
1578
    inst = self.cfg.AddNewInstance()
1579
    self.rpc.call_impexp_status.side_effect = None
1580
    self.rpc.call_impexp_status.return_value = \
1581
      self.RpcResultsBuilder() \
1582
        .CreateSuccessfulNodeResult(self.node,
1583
                                    [objects.ImportExportStatus(
1584
                                      exit_status=1,
1585
                                      recent_output=["mock output"]
1586
                                    )])
1587
    op = opcodes.OpInstanceMove(instance_name=inst.name,
1588
                                target_node=self.node.name)
1589
    self.ExecOpCodeExpectOpExecError(op, "Errors during disk copy")
1590

    
1591
  def testMoveFailingStartImpExpDaemon(self):
1592
    inst = self.cfg.AddNewInstance()
1593
    self.rpc.call_import_start.side_effect = None
1594
    self.rpc.call_import_start.return_value = \
1595
      self.RpcResultsBuilder() \
1596
        .CreateFailedNodeResult(self.node)
1597
    op = opcodes.OpInstanceMove(instance_name=inst.name,
1598
                                target_node=self.node.name)
1599
    self.ExecOpCodeExpectOpExecError(op, "Errors during disk copy")
1600

    
1601

    
1602
class TestLUInstanceRename(CmdlibTestCase):
1603
  def setUp(self):
1604
    super(TestLUInstanceRename, self).setUp()
1605

    
1606
    self.inst = self.cfg.AddNewInstance()
1607

    
1608
    self.op = opcodes.OpInstanceRename(instance_name=self.inst.name,
1609
                                       new_name="new_name.example.com")
1610

    
1611
  def testIpCheckWithoutNameCheck(self):
1612
    op = self.CopyOpCode(self.op,
1613
                         ip_check=True,
1614
                         name_check=False)
1615
    self.ExecOpCodeExpectOpPrereqError(
1616
      op, "IP address check requires a name check")
1617

    
1618
  def testIpAlreadyInUse(self):
1619
    self.netutils_mod.TcpPing.return_value = True
1620
    op = self.CopyOpCode(self.op)
1621
    self.ExecOpCodeExpectOpPrereqError(
1622
      op, "IP .* of instance .* already in use")
1623

    
1624
  def testExistingInstanceName(self):
1625
    self.cfg.AddNewInstance(name="new_name.example.com")
1626
    op = self.CopyOpCode(self.op)
1627
    self.ExecOpCodeExpectOpPrereqError(
1628
      op, "Instance .* is already in the cluster")
1629

    
1630
  def testFileInstance(self):
1631
    self.rpc.call_blockdev_assemble.return_value = \
1632
      self.RpcResultsBuilder() \
1633
        .CreateSuccessfulNodeResult(self.master, (None, None))
1634
    self.rpc.call_blockdev_shutdown.return_value = \
1635
      self.RpcResultsBuilder() \
1636
        .CreateSuccessfulNodeResult(self.master, (None, None))
1637

    
1638
    inst = self.cfg.AddNewInstance(disk_template=constants.DT_FILE)
1639
    op = self.CopyOpCode(self.op,
1640
                         instance_name=inst.name)
1641
    self.ExecOpCode(op)
1642

    
1643

    
1644
class TestLUInstanceMultiAlloc(CmdlibTestCase):
1645
  def setUp(self):
1646
    super(TestLUInstanceMultiAlloc, self).setUp()
1647

    
1648
    self.inst_op = opcodes.OpInstanceCreate(instance_name="inst.example.com",
1649
                                            disk_template=constants.DT_DRBD8,
1650
                                            disks=[],
1651
                                            nics=[],
1652
                                            os_type="mock_os",
1653
                                            hypervisor=constants.HT_XEN_HVM,
1654
                                            mode=constants.INSTANCE_CREATE)
1655

    
1656
  def testInstanceWithIAllocator(self):
1657
    inst = self.CopyOpCode(self.inst_op,
1658
                           iallocator="mock")
1659
    op = opcodes.OpInstanceMultiAlloc(instances=[inst])
1660
    self.ExecOpCodeExpectOpPrereqError(
1661
      op, "iallocator are not allowed to be set on instance objects")
1662

    
1663
  def testOnlySomeNodesGiven(self):
1664
    inst1 = self.CopyOpCode(self.inst_op,
1665
                            pnode=self.master.name)
1666
    inst2 = self.CopyOpCode(self.inst_op)
1667
    op = opcodes.OpInstanceMultiAlloc(instances=[inst1, inst2])
1668
    self.ExecOpCodeExpectOpPrereqError(
1669
      op, "There are instance objects providing pnode/snode while others"
1670
          " do not")
1671

    
1672
  def testMissingIAllocator(self):
1673
    self.cluster.default_iallocator = None
1674
    inst = self.CopyOpCode(self.inst_op)
1675
    op = opcodes.OpInstanceMultiAlloc(instances=[inst])
1676
    self.ExecOpCodeExpectOpPrereqError(
1677
      op, "No iallocator or nodes on the instances given and no cluster-wide"
1678
          " default iallocator found")
1679

    
1680
  def testDuplicateInstanceNames(self):
1681
    inst1 = self.CopyOpCode(self.inst_op)
1682
    inst2 = self.CopyOpCode(self.inst_op)
1683
    op = opcodes.OpInstanceMultiAlloc(instances=[inst1, inst2])
1684
    self.ExecOpCodeExpectOpPrereqError(
1685
      op, "There are duplicate instance names")
1686

    
1687
  def testWithGivenNodes(self):
1688
    snode = self.cfg.AddNewNode()
1689
    inst = self.CopyOpCode(self.inst_op,
1690
                           pnode=self.master.name,
1691
                           snode=snode.name)
1692
    op = opcodes.OpInstanceMultiAlloc(instances=[inst])
1693
    self.ExecOpCode(op)
1694

    
1695
  def testDryRun(self):
1696
    snode = self.cfg.AddNewNode()
1697
    inst = self.CopyOpCode(self.inst_op,
1698
                           pnode=self.master.name,
1699
                           snode=snode.name)
1700
    op = opcodes.OpInstanceMultiAlloc(instances=[inst],
1701
                                      dry_run=True)
1702
    self.ExecOpCode(op)
1703

    
1704
  def testWithIAllocator(self):
1705
    snode = self.cfg.AddNewNode()
1706
    self.iallocator_cls.return_value.result = \
1707
      ([("inst.example.com", [self.master.name, snode.name])], [])
1708

    
1709
    inst = self.CopyOpCode(self.inst_op)
1710
    op = opcodes.OpInstanceMultiAlloc(instances=[inst],
1711
                                      iallocator="mock_ialloc")
1712
    self.ExecOpCode(op)
1713

    
1714
  def testWithIAllocatorOpportunisticLocking(self):
1715
    snode = self.cfg.AddNewNode()
1716
    self.iallocator_cls.return_value.result = \
1717
      ([("inst.example.com", [self.master.name, snode.name])], [])
1718

    
1719
    inst = self.CopyOpCode(self.inst_op)
1720
    op = opcodes.OpInstanceMultiAlloc(instances=[inst],
1721
                                      iallocator="mock_ialloc",
1722
                                      opportunistic_locking=True)
1723
    self.ExecOpCode(op)
1724

    
1725
  def testFailingIAllocator(self):
1726
    self.iallocator_cls.return_value.success = False
1727

    
1728
    inst = self.CopyOpCode(self.inst_op)
1729
    op = opcodes.OpInstanceMultiAlloc(instances=[inst],
1730
                                      iallocator="mock_ialloc")
1731
    self.ExecOpCodeExpectOpPrereqError(
1732
      op, "Can't compute nodes using iallocator")
1733

    
1734

    
1735
class TestLUInstanceSetParams(CmdlibTestCase):
1736
  def setUp(self):
1737
    super(TestLUInstanceSetParams, self).setUp()
1738

    
1739
    self.inst = self.cfg.AddNewInstance()
1740
    self.op = opcodes.OpInstanceSetParams(instance_name=self.inst.name)
1741

    
1742
    self.running_inst = \
1743
      self.cfg.AddNewInstance(admin_state=constants.ADMINST_UP)
1744
    self.running_op = \
1745
      opcodes.OpInstanceSetParams(instance_name=self.running_inst.name)
1746

    
1747
    self.snode = self.cfg.AddNewNode()
1748

    
1749
    self.mocked_storage_type = constants.ST_LVM_VG
1750
    self.mocked_storage_free = 10000
1751
    self.mocked_master_cpu_total = 16
1752
    self.mocked_master_memory_free = 2048
1753
    self.mocked_snode_cpu_total = 16
1754
    self.mocked_snode_memory_free = 512
1755

    
1756
    self.mocked_running_inst_memory = 1024
1757
    self.mocked_running_inst_vcpus = 8
1758
    self.mocked_running_inst_state = "running"
1759
    self.mocked_running_inst_time = 10938474
1760

    
1761
    bootid = "mock_bootid"
1762
    storage_info = [
1763
      {
1764
        "type": self.mocked_storage_type,
1765
        "storage_free": self.mocked_storage_free
1766
      }
1767
    ]
1768
    hv_info_master = {
1769
      "cpu_total": self.mocked_master_cpu_total,
1770
      "memory_free": self.mocked_master_memory_free
1771
    }
1772
    hv_info_snode = {
1773
      "cpu_total": self.mocked_snode_cpu_total,
1774
      "memory_free": self.mocked_snode_memory_free
1775
    }
1776

    
1777
    self.rpc.call_node_info.return_value = \
1778
      self.RpcResultsBuilder() \
1779
        .AddSuccessfulNode(self.master,
1780
                           (bootid, storage_info, (hv_info_master, ))) \
1781
        .AddSuccessfulNode(self.snode,
1782
                           (bootid, storage_info, (hv_info_snode, ))) \
1783
        .Build()
1784

    
1785
    def _InstanceInfo(_, instance, __, ___):
1786
      if instance == self.inst.name:
1787
        return self.RpcResultsBuilder() \
1788
          .CreateSuccessfulNodeResult(self.master, None)
1789
      elif instance == self.running_inst.name:
1790
        return self.RpcResultsBuilder() \
1791
          .CreateSuccessfulNodeResult(
1792
            self.master, {
1793
              "memory": self.mocked_running_inst_memory,
1794
              "vcpus": self.mocked_running_inst_vcpus,
1795
              "state": self.mocked_running_inst_state,
1796
              "time": self.mocked_running_inst_time
1797
            })
1798
      else:
1799
        raise AssertionError()
1800
    self.rpc.call_instance_info.side_effect = _InstanceInfo
1801

    
1802
    self.rpc.call_bridges_exist.return_value = \
1803
      self.RpcResultsBuilder() \
1804
        .CreateSuccessfulNodeResult(self.master, True)
1805

    
1806
    self.rpc.call_blockdev_getmirrorstatus.side_effect = \
1807
      lambda node, _: self.RpcResultsBuilder() \
1808
                        .CreateSuccessfulNodeResult(node, [])
1809

    
1810
    self.rpc.call_blockdev_shutdown.side_effect = \
1811
      lambda node, _: self.RpcResultsBuilder() \
1812
                        .CreateSuccessfulNodeResult(node, [])
1813

    
1814
  def testNoChanges(self):
1815
    op = self.CopyOpCode(self.op)
1816
    self.ExecOpCodeExpectOpPrereqError(op, "No changes submitted")
1817

    
1818
  def testGlobalHvparams(self):
1819
    op = self.CopyOpCode(self.op,
1820
                         hvparams={constants.HV_MIGRATION_PORT: 1234})
1821
    self.ExecOpCodeExpectOpPrereqError(
1822
      op, "hypervisor parameters are global and cannot be customized")
1823

    
1824
  def testHvparams(self):
1825
    op = self.CopyOpCode(self.op,
1826
                         hvparams={constants.HV_BOOT_ORDER: "cd"})
1827
    self.ExecOpCode(op)
1828

    
1829
  def testDisksAndDiskTemplate(self):
1830
    op = self.CopyOpCode(self.op,
1831
                         disk_template=constants.DT_PLAIN,
1832
                         disks=[[constants.DDM_ADD, -1, {}]])
1833
    self.ExecOpCodeExpectOpPrereqError(
1834
      op, "Disk template conversion and other disk changes not supported at"
1835
          " the same time")
1836

    
1837
  def testDiskTemplateToMirroredNoRemoteNode(self):
1838
    op = self.CopyOpCode(self.op,
1839
                         disk_template=constants.DT_DRBD8)
1840
    self.ExecOpCodeExpectOpPrereqError(
1841
      op, "Changing the disk template to a mirrored one requires specifying"
1842
          " a secondary node")
1843

    
1844
  def testPrimaryNodeToOldPrimaryNode(self):
1845
    op = self.CopyOpCode(self.op,
1846
                         pnode=self.master.name)
1847
    self.ExecOpCode(op)
1848

    
1849
  def testPrimaryNodeChange(self):
1850
    node = self.cfg.AddNewNode()
1851
    op = self.CopyOpCode(self.op,
1852
                         pnode=node.name)
1853
    self.ExecOpCode(op)
1854

    
1855
  def testPrimaryNodeChangeRunningInstance(self):
1856
    node = self.cfg.AddNewNode()
1857
    op = self.CopyOpCode(self.running_op,
1858
                         pnode=node.name)
1859
    self.ExecOpCodeExpectOpPrereqError(op, "Instance is still running")
1860

    
1861
  def testOsChange(self):
1862
    os = self.cfg.CreateOs(supported_variants=[])
1863
    self.rpc.call_os_get.return_value = \
1864
      self.RpcResultsBuilder() \
1865
        .CreateSuccessfulNodeResult(self.master, os)
1866
    op = self.CopyOpCode(self.op,
1867
                         os_name=os.name)
1868
    self.ExecOpCode(op)
1869

    
1870
  def testVCpuChange(self):
1871
    op = self.CopyOpCode(self.op,
1872
                         beparams={
1873
                           constants.BE_VCPUS: 4
1874
                         })
1875
    self.ExecOpCode(op)
1876

    
1877
  def testWrongCpuMask(self):
1878
    op = self.CopyOpCode(self.op,
1879
                         beparams={
1880
                           constants.BE_VCPUS: 4
1881
                         },
1882
                         hvparams={
1883
                           constants.HV_CPU_MASK: "1,2:3,4"
1884
                         })
1885
    self.ExecOpCodeExpectOpPrereqError(
1886
      op, "Number of vCPUs .* does not match the CPU mask .*")
1887

    
1888
  def testCorrectCpuMask(self):
1889
    op = self.CopyOpCode(self.op,
1890
                         beparams={
1891
                           constants.BE_VCPUS: 4
1892
                         },
1893
                         hvparams={
1894
                           constants.HV_CPU_MASK: "1,2:3,4:all:1,4"
1895
                         })
1896
    self.ExecOpCode(op)
1897

    
1898
  def testOsParams(self):
1899
    op = self.CopyOpCode(self.op,
1900
                         osparams={
1901
                           self.os.supported_parameters[0]: "test_param_val"
1902
                         })
1903
    self.ExecOpCode(op)
1904

    
1905
  def testIncreaseMemoryTooMuch(self):
1906
    op = self.CopyOpCode(self.running_op,
1907
                         beparams={
1908
                           constants.BE_MAXMEM:
1909
                             self.mocked_master_memory_free * 2
1910
                         })
1911
    self.ExecOpCodeExpectOpPrereqError(
1912
      op, "This change will prevent the instance from starting")
1913

    
1914
  def testIncreaseMemory(self):
1915
    op = self.CopyOpCode(self.running_op,
1916
                         beparams={
1917
                           constants.BE_MAXMEM: self.mocked_master_memory_free
1918
                         })
1919
    self.ExecOpCode(op)
1920

    
1921
  def testIncreaseMemoryTooMuchForSecondary(self):
1922
    inst = self.cfg.AddNewInstance(admin_state=constants.ADMINST_UP,
1923
                                   disk_template=constants.DT_DRBD8,
1924
                                   secondary_node=self.snode)
1925
    self.rpc.call_instance_info.side_effect = [
1926
      self.RpcResultsBuilder()
1927
        .CreateSuccessfulNodeResult(self.master,
1928
                                    {
1929
                                      "memory":
1930
                                        self.mocked_snode_memory_free * 2,
1931
                                      "vcpus": self.mocked_running_inst_vcpus,
1932
                                      "state": self.mocked_running_inst_state,
1933
                                      "time": self.mocked_running_inst_time
1934
                                    })]
1935

    
1936
    op = self.CopyOpCode(self.op,
1937
                         instance_name=inst.name,
1938
                         beparams={
1939
                           constants.BE_MAXMEM:
1940
                             self.mocked_snode_memory_free * 2,
1941
                           constants.BE_AUTO_BALANCE: True
1942
                         })
1943
    self.ExecOpCodeExpectOpPrereqError(
1944
      op, "This change will prevent the instance from failover to its"
1945
          " secondary node")
1946

    
1947
  def testInvalidRuntimeMemory(self):
1948
    op = self.CopyOpCode(self.running_op,
1949
                         runtime_mem=self.mocked_master_memory_free * 2)
1950
    self.ExecOpCodeExpectOpPrereqError(
1951
      op, "Instance .* must have memory between .* and .* of memory")
1952

    
1953
  def testIncreaseRuntimeMemory(self):
1954
    op = self.CopyOpCode(self.running_op,
1955
                         runtime_mem=self.mocked_master_memory_free,
1956
                         beparams={
1957
                           constants.BE_MAXMEM: self.mocked_master_memory_free
1958
                         })
1959
    self.ExecOpCode(op)
1960

    
1961
  def testAddNicWithPoolIpNoNetwork(self):
1962
    op = self.CopyOpCode(self.op,
1963
                         nics=[(constants.DDM_ADD, -1,
1964
                                {
1965
                                  constants.INIC_IP: constants.NIC_IP_POOL
1966
                                })])
1967
    self.ExecOpCodeExpectOpPrereqError(
1968
      op, "If ip=pool, parameter network cannot be none")
1969

    
1970
  def testAddNicWithPoolIp(self):
1971
    net = self.cfg.AddNewNetwork()
1972
    self.cfg.ConnectNetworkToGroup(net, self.group)
1973
    op = self.CopyOpCode(self.op,
1974
                         nics=[(constants.DDM_ADD, -1,
1975
                                {
1976
                                  constants.INIC_IP: constants.NIC_IP_POOL,
1977
                                  constants.INIC_NETWORK: net.name
1978
                                })])
1979
    self.ExecOpCode(op)
1980

    
1981
  def testAddNicWithInvalidIp(self):
1982
    op = self.CopyOpCode(self.op,
1983
                         nics=[(constants.DDM_ADD, -1,
1984
                                {
1985
                                  constants.INIC_IP: "invalid"
1986
                                })])
1987
    self.ExecOpCodeExpectOpPrereqError(
1988
      op, "Invalid IP address")
1989

    
1990
  def testAddNic(self):
1991
    op = self.CopyOpCode(self.op,
1992
                         nics=[(constants.DDM_ADD, -1, {})])
1993
    self.ExecOpCode(op)
1994

    
1995
  def testNoHotplugSupport(self):
1996
    op = self.CopyOpCode(self.op,
1997
                         nics=[(constants.DDM_ADD, -1, {})],
1998
                         hotplug=True)
1999
    self.rpc.call_hotplug_supported.return_value = \
2000
      self.RpcResultsBuilder() \
2001
        .CreateFailedNodeResult(self.master)
2002
    self.ExecOpCodeExpectOpPrereqError(op, "Hotplug is not possible")
2003
    self.assertTrue(self.rpc.call_hotplug_supported.called)
2004

    
2005
  def testHotplugIfPossible(self):
2006
    op = self.CopyOpCode(self.op,
2007
                         nics=[(constants.DDM_ADD, -1, {})],
2008
                         hotplug_if_possible=True)
2009
    self.rpc.call_hotplug_supported.return_value = \
2010
      self.RpcResultsBuilder() \
2011
        .CreateFailedNodeResult(self.master)
2012
    self.ExecOpCode(op)
2013
    self.assertTrue(self.rpc.call_hotplug_supported.called)
2014
    self.assertFalse(self.rpc.call_hotplug_device.called)
2015

    
2016
  def testHotAddNic(self):
2017
    op = self.CopyOpCode(self.op,
2018
                         nics=[(constants.DDM_ADD, -1, {})],
2019
                         hotplug=True)
2020
    self.rpc.call_hotplug_supported.return_value = \
2021
      self.RpcResultsBuilder() \
2022
        .CreateSuccessfulNodeResult(self.master)
2023
    self.ExecOpCode(op)
2024
    self.assertTrue(self.rpc.call_hotplug_supported.called)
2025
    self.assertTrue(self.rpc.call_hotplug_device.called)
2026

    
2027
  def testAddNicWithIp(self):
2028
    op = self.CopyOpCode(self.op,
2029
                         nics=[(constants.DDM_ADD, -1,
2030
                                {
2031
                                  constants.INIC_IP: "2.3.1.4"
2032
                                })])
2033
    self.ExecOpCode(op)
2034

    
2035
  def testModifyNicRoutedWithoutIp(self):
2036
    op = self.CopyOpCode(self.op,
2037
                         nics=[(constants.DDM_MODIFY, 0,
2038
                                {
2039
                                  constants.INIC_MODE: constants.NIC_MODE_ROUTED
2040
                                })])
2041
    self.ExecOpCodeExpectOpPrereqError(
2042
      op, "Cannot set the NIC IP address to None on a routed NIC")
2043

    
2044
  def testModifyNicSetMac(self):
2045
    op = self.CopyOpCode(self.op,
2046
                         nics=[(constants.DDM_MODIFY, 0,
2047
                                {
2048
                                  constants.INIC_MAC: "0a:12:95:15:bf:75"
2049
                                })])
2050
    self.ExecOpCode(op)
2051

    
2052
  def testModifyNicWithPoolIpNoNetwork(self):
2053
    op = self.CopyOpCode(self.op,
2054
                         nics=[(constants.DDM_MODIFY, -1,
2055
                                {
2056
                                  constants.INIC_IP: constants.NIC_IP_POOL
2057
                                })])
2058
    self.ExecOpCodeExpectOpPrereqError(
2059
      op, "ip=pool, but no network found")
2060

    
2061
  def testModifyNicSetNet(self):
2062
    old_net = self.cfg.AddNewNetwork()
2063
    self.cfg.ConnectNetworkToGroup(old_net, self.group)
2064
    inst = self.cfg.AddNewInstance(nics=[
2065
      self.cfg.CreateNic(network=old_net,
2066
                         ip="198.51.100.2")])
2067

    
2068
    new_net = self.cfg.AddNewNetwork(mac_prefix="be")
2069
    self.cfg.ConnectNetworkToGroup(new_net, self.group)
2070
    op = self.CopyOpCode(self.op,
2071
                         instance_name=inst.name,
2072
                         nics=[(constants.DDM_MODIFY, 0,
2073
                                {
2074
                                  constants.INIC_NETWORK: new_net.name
2075
                                })])
2076
    self.ExecOpCode(op)
2077

    
2078
  def testModifyNicSetLinkWhileConnected(self):
2079
    old_net = self.cfg.AddNewNetwork()
2080
    self.cfg.ConnectNetworkToGroup(old_net, self.group)
2081
    inst = self.cfg.AddNewInstance(nics=[
2082
      self.cfg.CreateNic(network=old_net)])
2083

    
2084
    op = self.CopyOpCode(self.op,
2085
                         instance_name=inst.name,
2086
                         nics=[(constants.DDM_MODIFY, 0,
2087
                                {
2088
                                  constants.INIC_LINK: "mock_link"
2089
                                })])
2090
    self.ExecOpCodeExpectOpPrereqError(
2091
      op, "Not allowed to change link or mode of a NIC that is connected"
2092
          " to a network")
2093

    
2094
  def testModifyNicSetNetAndIp(self):
2095
    net = self.cfg.AddNewNetwork(mac_prefix="be", network="123.123.123.0/24")
2096
    self.cfg.ConnectNetworkToGroup(net, self.group)
2097
    op = self.CopyOpCode(self.op,
2098
                         nics=[(constants.DDM_MODIFY, 0,
2099
                                {
2100
                                  constants.INIC_NETWORK: net.name,
2101
                                  constants.INIC_IP: "123.123.123.1"
2102
                                })])
2103
    self.ExecOpCode(op)
2104

    
2105
  def testModifyNic(self):
2106
    op = self.CopyOpCode(self.op,
2107
                         nics=[(constants.DDM_MODIFY, 0, {})])
2108
    self.ExecOpCode(op)
2109

    
2110
  def testHotModifyNic(self):
2111
    op = self.CopyOpCode(self.op,
2112
                         nics=[(constants.DDM_MODIFY, 0, {})],
2113
                         hotplug=True)
2114
    self.rpc.call_hotplug_supported.return_value = \
2115
      self.RpcResultsBuilder() \
2116
        .CreateSuccessfulNodeResult(self.master)
2117
    self.ExecOpCode(op)
2118
    self.assertTrue(self.rpc.call_hotplug_supported.called)
2119
    self.assertTrue(self.rpc.call_hotplug_device.called)
2120

    
2121
  def testRemoveLastNic(self):
2122
    op = self.CopyOpCode(self.op,
2123
                         nics=[(constants.DDM_REMOVE, 0, {})])
2124
    self.ExecOpCodeExpectOpPrereqError(
2125
      op, "violates policy")
2126

    
2127
  def testRemoveNic(self):
2128
    inst = self.cfg.AddNewInstance(nics=[self.cfg.CreateNic(),
2129
                                         self.cfg.CreateNic()])
2130
    op = self.CopyOpCode(self.op,
2131
                         instance_name=inst.name,
2132
                         nics=[(constants.DDM_REMOVE, 0, {})])
2133
    self.ExecOpCode(op)
2134

    
2135
  def testHotRemoveNic(self):
2136
    inst = self.cfg.AddNewInstance(nics=[self.cfg.CreateNic(),
2137
                                         self.cfg.CreateNic()])
2138
    op = self.CopyOpCode(self.op,
2139
                         instance_name=inst.name,
2140
                         nics=[(constants.DDM_REMOVE, 0, {})],
2141
                         hotplug=True)
2142
    self.rpc.call_hotplug_supported.return_value = \
2143
      self.RpcResultsBuilder() \
2144
        .CreateSuccessfulNodeResult(self.master)
2145
    self.ExecOpCode(op)
2146
    self.assertTrue(self.rpc.call_hotplug_supported.called)
2147
    self.assertTrue(self.rpc.call_hotplug_device.called)
2148

    
2149
  def testSetOffline(self):
2150
    op = self.CopyOpCode(self.op,
2151
                         offline=True)
2152
    self.ExecOpCode(op)
2153

    
2154
  def testUnsetOffline(self):
2155
    op = self.CopyOpCode(self.op,
2156
                         offline=False)
2157
    self.ExecOpCode(op)
2158

    
2159
  def testAddDiskInvalidMode(self):
2160
    op = self.CopyOpCode(self.op,
2161
                         disks=[[constants.DDM_ADD, -1,
2162
                                 {
2163
                                   constants.IDISK_MODE: "invalid"
2164
                                 }]])
2165
    self.ExecOpCodeExpectOpPrereqError(
2166
      op, "Invalid disk access mode 'invalid'")
2167

    
2168
  def testAddDiskMissingSize(self):
2169
    op = self.CopyOpCode(self.op,
2170
                         disks=[[constants.DDM_ADD, -1, {}]])
2171
    self.ExecOpCodeExpectOpPrereqError(
2172
      op, "Required disk parameter 'size' missing")
2173

    
2174
  def testAddDiskInvalidSize(self):
2175
    op = self.CopyOpCode(self.op,
2176
                         disks=[[constants.DDM_ADD, -1,
2177
                                 {
2178
                                   constants.IDISK_SIZE: "invalid"
2179
                                 }]])
2180
    self.ExecOpCodeExpectException(
2181
      op, errors.TypeEnforcementError, "is not a valid size")
2182

    
2183
  def testAddDiskRunningInstanceNoWaitForSync(self):
2184
    op = self.CopyOpCode(self.running_op,
2185
                         disks=[[constants.DDM_ADD, -1,
2186
                                 {
2187
                                   constants.IDISK_SIZE: 1024
2188
                                 }]],
2189
                         wait_for_sync=False)
2190
    self.ExecOpCodeExpectOpPrereqError(
2191
      op, "Can't add a disk to an instance with activated disks"
2192
          " and --no-wait-for-sync given.")
2193

    
2194
  def testAddDiskDownInstance(self):
2195
    op = self.CopyOpCode(self.op,
2196
                         disks=[[constants.DDM_ADD, -1,
2197
                                 {
2198
                                   constants.IDISK_SIZE: 1024
2199
                                 }]])
2200
    self.ExecOpCode(op)
2201

    
2202
    self.assertTrue(self.rpc.call_blockdev_shutdown.called)
2203

    
2204
  def testAddDiskRunningInstance(self):
2205
    op = self.CopyOpCode(self.running_op,
2206
                         disks=[[constants.DDM_ADD, -1,
2207
                                 {
2208
                                   constants.IDISK_SIZE: 1024
2209
                                 }]])
2210
    self.ExecOpCode(op)
2211

    
2212
    self.assertFalse(self.rpc.call_blockdev_shutdown.called)
2213

    
2214
  def testAddDiskNoneName(self):
2215
    op = self.CopyOpCode(self.op,
2216
                         disks=[[constants.DDM_ADD, -1,
2217
                                 {
2218
                                   constants.IDISK_SIZE: 1024,
2219
                                   constants.IDISK_NAME: constants.VALUE_NONE
2220
                                 }]])
2221
    self.ExecOpCode(op)
2222

    
2223
  def testHotAddDisk(self):
2224
    self.rpc.call_blockdev_assemble.return_value = \
2225
      self.RpcResultsBuilder() \
2226
        .CreateSuccessfulNodeResult(self.master, ("/dev/mocked_path",
2227
                                    "/var/run/ganeti/instance-disks/mocked_d"))
2228
    op = self.CopyOpCode(self.op,
2229
                         disks=[[constants.DDM_ADD, -1,
2230
                                 {
2231
                                   constants.IDISK_SIZE: 1024,
2232
                                 }]],
2233
                         hotplug=True)
2234
    self.rpc.call_hotplug_supported.return_value = \
2235
      self.RpcResultsBuilder() \
2236
        .CreateSuccessfulNodeResult(self.master)
2237
    self.ExecOpCode(op)
2238
    self.assertTrue(self.rpc.call_hotplug_supported.called)
2239
    self.assertTrue(self.rpc.call_blockdev_create.called)
2240
    self.assertTrue(self.rpc.call_blockdev_assemble.called)
2241
    self.assertTrue(self.rpc.call_hotplug_device.called)
2242

    
2243
  def testHotRemoveDisk(self):
2244
    inst = self.cfg.AddNewInstance(disks=[self.cfg.CreateDisk(),
2245
                                          self.cfg.CreateDisk()])
2246
    op = self.CopyOpCode(self.op,
2247
                         instance_name=inst.name,
2248
                         disks=[[constants.DDM_REMOVE, -1,
2249
                                 {}]],
2250
                         hotplug=True)
2251
    self.rpc.call_hotplug_supported.return_value = \
2252
      self.RpcResultsBuilder() \
2253
        .CreateSuccessfulNodeResult(self.master)
2254
    self.ExecOpCode(op)
2255
    self.assertTrue(self.rpc.call_hotplug_supported.called)
2256
    self.assertTrue(self.rpc.call_hotplug_device.called)
2257
    self.assertTrue(self.rpc.call_blockdev_shutdown.called)
2258
    self.assertTrue(self.rpc.call_blockdev_remove.called)
2259

    
2260
  def testModifyDiskWithSize(self):
2261
    op = self.CopyOpCode(self.op,
2262
                         disks=[[constants.DDM_MODIFY, 0,
2263
                                 {
2264
                                   constants.IDISK_SIZE: 1024
2265
                                 }]])
2266
    self.ExecOpCodeExpectOpPrereqError(
2267
      op, "Disk size change not possible, use grow-disk")
2268

    
2269
  def testModifyDiskWithRandomParams(self):
2270
    op = self.CopyOpCode(self.op,
2271
                         disks=[[constants.DDM_MODIFY, 0,
2272
                                 {
2273
                                   constants.IDISK_METAVG: "new_meta_vg",
2274
                                   constants.IDISK_MODE: "invalid",
2275
                                   constants.IDISK_NAME: "new_name"
2276
                                 }]])
2277
    self.ExecOpCodeExpectException(op, errors.TypeEnforcementError,
2278
                                   "Unknown parameter 'metavg'")
2279

    
2280
  def testModifyDiskUnsetName(self):
2281
    op = self.CopyOpCode(self.op,
2282
                         disks=[[constants.DDM_MODIFY, 0,
2283
                                  {
2284
                                    constants.IDISK_NAME: constants.VALUE_NONE
2285
                                  }]])
2286
    self.ExecOpCode(op)
2287

    
2288
  def testSetOldDiskTemplate(self):
2289
    op = self.CopyOpCode(self.op,
2290
                         disk_template=self.inst.disk_template)
2291
    self.ExecOpCodeExpectOpPrereqError(
2292
      op, "Instance already has disk template")
2293

    
2294
  def testSetDisabledDiskTemplate(self):
2295
    self.cfg.SetEnabledDiskTemplates([self.inst.disk_template])
2296
    op = self.CopyOpCode(self.op,
2297
                         disk_template=constants.DT_EXT)
2298
    self.ExecOpCodeExpectOpPrereqError(
2299
      op, "Disk template .* is not enabled for this cluster")
2300

    
2301
  def testInvalidDiskTemplateConversion(self):
2302
    op = self.CopyOpCode(self.op,
2303
                         disk_template=constants.DT_EXT)
2304
    self.ExecOpCodeExpectOpPrereqError(
2305
      op, "Unsupported disk template conversion from .* to .*")
2306

    
2307
  def testConvertToDRBDWithSecondarySameAsPrimary(self):
2308
    op = self.CopyOpCode(self.op,
2309
                         disk_template=constants.DT_DRBD8,
2310
                         remote_node=self.master.name)
2311
    self.ExecOpCodeExpectOpPrereqError(
2312
      op, "Given new secondary node .* is the same as the primary node"
2313
          " of the instance")
2314

    
2315
  def testConvertPlainToDRBD(self):
2316
    self.rpc.call_blockdev_shutdown.return_value = \
2317
      self.RpcResultsBuilder() \
2318
        .CreateSuccessfulNodeResult(self.master, True)
2319
    self.rpc.call_blockdev_getmirrorstatus.return_value = \
2320
      self.RpcResultsBuilder() \
2321
        .CreateSuccessfulNodeResult(self.master, [objects.BlockDevStatus()])
2322

    
2323
    op = self.CopyOpCode(self.op,
2324
                         disk_template=constants.DT_DRBD8,
2325
                         remote_node=self.snode.name)
2326
    self.ExecOpCode(op)
2327

    
2328
  def testConvertDRBDToPlain(self):
2329
    self.inst.disks = [self.cfg.CreateDisk(dev_type=constants.DT_DRBD8,
2330
                                           primary_node=self.master,
2331
                                           secondary_node=self.snode)]
2332
    self.inst.disk_template = constants.DT_DRBD8
2333
    self.rpc.call_blockdev_shutdown.return_value = \
2334
      self.RpcResultsBuilder() \
2335
        .CreateSuccessfulNodeResult(self.master, True)
2336
    self.rpc.call_blockdev_remove.return_value = \
2337
      self.RpcResultsBuilder() \
2338
        .CreateSuccessfulNodeResult(self.master)
2339
    self.rpc.call_blockdev_getmirrorstatus.return_value = \
2340
      self.RpcResultsBuilder() \
2341
        .CreateSuccessfulNodeResult(self.master, [objects.BlockDevStatus()])
2342

    
2343
    op = self.CopyOpCode(self.op,
2344
                         disk_template=constants.DT_PLAIN)
2345
    self.ExecOpCode(op)
2346

    
2347

    
2348
class TestLUInstanceChangeGroup(CmdlibTestCase):
2349
  def setUp(self):
2350
    super(TestLUInstanceChangeGroup, self).setUp()
2351

    
2352
    self.group2 = self.cfg.AddNewNodeGroup()
2353
    self.node2 = self.cfg.AddNewNode(group=self.group2)
2354
    self.inst = self.cfg.AddNewInstance()
2355
    self.op = opcodes.OpInstanceChangeGroup(instance_name=self.inst.name)
2356

    
2357
  def testTargetGroupIsInstanceGroup(self):
2358
    op = self.CopyOpCode(self.op,
2359
                         target_groups=[self.group.name])
2360
    self.ExecOpCodeExpectOpPrereqError(
2361
      op, "Can't use group\(s\) .* as targets, they are used by the"
2362
          " instance .*")
2363

    
2364
  def testNoTargetGroups(self):
2365
    inst = self.cfg.AddNewInstance(disk_template=constants.DT_DRBD8,
2366
                                   primary_node=self.master,
2367
                                   secondary_node=self.node2)
2368
    op = self.CopyOpCode(self.op,
2369
                         instance_name=inst.name)
2370
    self.ExecOpCodeExpectOpPrereqError(
2371
      op, "There are no possible target groups")
2372

    
2373
  def testFailingIAllocator(self):
2374
    self.iallocator_cls.return_value.success = False
2375
    op = self.CopyOpCode(self.op)
2376

    
2377
    self.ExecOpCodeExpectOpPrereqError(
2378
      op, "Can't compute solution for changing group of instance .*"
2379
          " using iallocator .*")
2380

    
2381
  def testChangeGroup(self):
2382
    self.iallocator_cls.return_value.success = True
2383
    self.iallocator_cls.return_value.result = ([], [], [])
2384
    op = self.CopyOpCode(self.op)
2385

    
2386
    self.ExecOpCode(op)
2387

    
2388

    
2389
if __name__ == "__main__":
2390
  testutils.GanetiTestProgram()