Statistics
| Branch: | Tag: | Revision:

root / test / py / cmdlib / instance_unittest.py @ 11414807

History | View | Annotate | Download (79.2 kB)

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

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

    
21

    
22
"""Tests for LUInstance*
23

24
"""
25

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

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

    
42
from cmdlib.cmdlib_unittest import _StubComputeIPolicySpecViolation, _FakeLU
43

    
44
from testsupport import *
45

    
46
import testutils
47

    
48

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

    
66

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

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

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

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

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

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

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

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

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

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

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

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

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

    
159
  def testStrangeHostnameResolve(self):
160
    op = self.CopyOpCode(self.diskless_op)
161
    self.netutils_mod.GetHostname.return_value = \
162
      HostnameMock("random.host.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 testVlanWithWrongMode(self):
184
    op = self.CopyOpCode(self.diskless_op,
185
                         nics=[{
186
                           constants.INIC_VLAN: ":1",
187
                           constants.INIC_MODE: constants.NIC_MODE_BRIDGED
188
                         }])
189
    self.ExecOpCodeExpectOpPrereqError(
190
      op, "VLAN is given, but network mode is not openvswitch")
191

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
629
  def testImportPlainFromGivenSrcNode(self):
630
    exp_info = """
631
[export]
632
version=0
633
os=mock_os
634
[instance]
635
name=old_name.example.com
636
"""
637

    
638
    self.rpc.call_export_info.return_value = \
639
      self.RpcResultsBuilder() \
640
        .CreateSuccessfulNodeResult(self.master, exp_info)
641
    op = self.CopyOpCode(self.plain_op,
642
                         mode=constants.INSTANCE_IMPORT,
643
                         src_node=self.master.name)
644
    self.ExecOpCode(op)
645

    
646
  def testImportPlainWithoutSrcNodeNotFound(self):
647
    op = self.CopyOpCode(self.plain_op,
648
                         mode=constants.INSTANCE_IMPORT)
649
    self.ExecOpCodeExpectOpPrereqError(
650
      op, "No export found for relative path")
651

    
652
  def testImportPlainWithoutSrcNode(self):
653
    exp_info = """
654
[export]
655
version=0
656
os=mock_os
657
[instance]
658
name=old_name.example.com
659
"""
660

    
661
    self.rpc.call_export_list.return_value = \
662
      self.RpcResultsBuilder() \
663
        .AddSuccessfulNode(self.master, {"mock_path": {}}) \
664
        .Build()
665
    self.rpc.call_export_info.return_value = \
666
      self.RpcResultsBuilder() \
667
        .CreateSuccessfulNodeResult(self.master, exp_info)
668

    
669
    op = self.CopyOpCode(self.plain_op,
670
                         mode=constants.INSTANCE_IMPORT,
671
                         src_path="mock_path")
672
    self.ExecOpCode(op)
673

    
674
  def testImportPlainCorruptExportInfo(self):
675
    exp_info = ""
676
    self.rpc.call_export_info.return_value = \
677
      self.RpcResultsBuilder() \
678
        .CreateSuccessfulNodeResult(self.master, exp_info)
679
    op = self.CopyOpCode(self.plain_op,
680
                         mode=constants.INSTANCE_IMPORT,
681
                         src_node=self.master.name)
682
    self.ExecOpCodeExpectException(op, errors.ProgrammerError,
683
                                   "Corrupted export config")
684

    
685
  def testImportPlainWrongExportInfoVersion(self):
686
    exp_info = """
687
[export]
688
version=1
689
"""
690
    self.rpc.call_export_info.return_value = \
691
      self.RpcResultsBuilder() \
692
        .CreateSuccessfulNodeResult(self.master, exp_info)
693
    op = self.CopyOpCode(self.plain_op,
694
                         mode=constants.INSTANCE_IMPORT,
695
                         src_node=self.master.name)
696
    self.ExecOpCodeExpectOpPrereqError(op, "Wrong export version")
697

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

    
723
    self.rpc.call_export_info.return_value = \
724
      self.RpcResultsBuilder() \
725
        .CreateSuccessfulNodeResult(self.master, exp_info)
726
    self.rpc.call_import_start.return_value = \
727
      self.RpcResultsBuilder() \
728
        .CreateSuccessfulNodeResult(self.master, "daemon_name")
729
    self.rpc.call_impexp_status.return_value = \
730
      self.RpcResultsBuilder() \
731
        .CreateSuccessfulNodeResult(self.master,
732
                                    [
733
                                      objects.ImportExportStatus(exit_status=0)
734
                                    ])
735
    self.rpc.call_impexp_cleanup.return_value = \
736
      self.RpcResultsBuilder() \
737
        .CreateSuccessfulNodeResult(self.master, True)
738

    
739
    op = self.CopyOpCode(self.plain_op,
740
                         disks=[],
741
                         nics=[],
742
                         tags=[],
743
                         hypervisor=None,
744
                         hvparams={},
745
                         mode=constants.INSTANCE_IMPORT,
746
                         src_node=self.master.name)
747
    self.ExecOpCode(op)
748

    
749

    
750
class TestCheckOSVariant(CmdlibTestCase):
751
  def testNoVariantsSupported(self):
752
    os = self.cfg.CreateOs(supported_variants=[])
753
    self.assertRaises(errors.OpPrereqError, instance._CheckOSVariant,
754
                      os, "os+variant")
755

    
756
  def testNoVariantGiven(self):
757
    os = self.cfg.CreateOs(supported_variants=["default"])
758
    self.assertRaises(errors.OpPrereqError, instance._CheckOSVariant,
759
                      os, "os")
760

    
761
  def testWrongVariantGiven(self):
762
    os = self.cfg.CreateOs(supported_variants=["default"])
763
    self.assertRaises(errors.OpPrereqError, instance._CheckOSVariant,
764
                      os, "os+wrong_variant")
765

    
766
  def testOkWithVariant(self):
767
    os = self.cfg.CreateOs(supported_variants=["default"])
768
    instance._CheckOSVariant(os, "os+default")
769

    
770
  def testOkWithoutVariant(self):
771
    os = self.cfg.CreateOs(supported_variants=[])
772
    instance._CheckOSVariant(os, "os")
773

    
774

    
775
class TestCheckTargetNodeIPolicy(TestLUInstanceCreate):
776
  def setUp(self):
777
    super(TestCheckTargetNodeIPolicy, self).setUp()
778

    
779
    self.op = self.diskless_op
780

    
781
    self.instance = self.cfg.AddNewInstance()
782
    self.target_group = self.cfg.AddNewNodeGroup()
783
    self.target_node = self.cfg.AddNewNode(group=self.target_group)
784

    
785
  @withLockedLU
786
  def testNoViolation(self, lu):
787
    compute_recoder = mock.Mock(return_value=[])
788
    instance.CheckTargetNodeIPolicy(lu, NotImplemented, self.instance,
789
                                    self.target_node, NotImplemented,
790
                                    _compute_fn=compute_recoder)
791
    self.assertTrue(compute_recoder.called)
792
    self.mcpu.assertLogIsEmpty()
793

    
794
  @withLockedLU
795
  def testNoIgnore(self, lu):
796
    compute_recoder = mock.Mock(return_value=["mem_size not in range"])
797
    self.assertRaises(errors.OpPrereqError, instance.CheckTargetNodeIPolicy,
798
                      lu, NotImplemented, self.instance,
799
                      self.target_node, NotImplemented,
800
                      _compute_fn=compute_recoder)
801
    self.assertTrue(compute_recoder.called)
802
    self.mcpu.assertLogIsEmpty()
803

    
804
  @withLockedLU
805
  def testIgnoreViolation(self, lu):
806
    compute_recoder = mock.Mock(return_value=["mem_size not in range"])
807
    instance.CheckTargetNodeIPolicy(lu, NotImplemented, self.instance,
808
                                    self.target_node, NotImplemented,
809
                                    ignore=True, _compute_fn=compute_recoder)
810
    self.assertTrue(compute_recoder.called)
811
    msg = ("Instance does not meet target node group's .* instance policy:"
812
           " mem_size not in range")
813
    self.mcpu.assertLogContainsRegex(msg)
814

    
815

    
816
class TestApplyContainerMods(unittest.TestCase):
817
  def testEmptyContainer(self):
818
    container = []
819
    chgdesc = []
820
    instance._ApplyContainerMods("test", container, chgdesc, [], None, None,
821
                                 None)
822
    self.assertEqual(container, [])
823
    self.assertEqual(chgdesc, [])
824

    
825
  def testAdd(self):
826
    container = []
827
    chgdesc = []
828
    mods = instance._PrepareContainerMods([
829
      (constants.DDM_ADD, -1, "Hello"),
830
      (constants.DDM_ADD, -1, "World"),
831
      (constants.DDM_ADD, 0, "Start"),
832
      (constants.DDM_ADD, -1, "End"),
833
      ], None)
834
    instance._ApplyContainerMods("test", container, chgdesc, mods,
835
                                 None, None, None)
836
    self.assertEqual(container, ["Start", "Hello", "World", "End"])
837
    self.assertEqual(chgdesc, [])
838

    
839
    mods = instance._PrepareContainerMods([
840
      (constants.DDM_ADD, 0, "zero"),
841
      (constants.DDM_ADD, 3, "Added"),
842
      (constants.DDM_ADD, 5, "four"),
843
      (constants.DDM_ADD, 7, "xyz"),
844
      ], None)
845
    instance._ApplyContainerMods("test", container, chgdesc, mods,
846
                                 None, None, None)
847
    self.assertEqual(container,
848
                     ["zero", "Start", "Hello", "Added", "World", "four",
849
                      "End", "xyz"])
850
    self.assertEqual(chgdesc, [])
851

    
852
    for idx in [-2, len(container) + 1]:
853
      mods = instance._PrepareContainerMods([
854
        (constants.DDM_ADD, idx, "error"),
855
        ], None)
856
      self.assertRaises(IndexError, instance._ApplyContainerMods,
857
                        "test", container, None, mods, None, None, None)
858

    
859
  def testRemoveError(self):
860
    for idx in [0, 1, 2, 100, -1, -4]:
861
      mods = instance._PrepareContainerMods([
862
        (constants.DDM_REMOVE, idx, None),
863
        ], None)
864
      self.assertRaises(IndexError, instance._ApplyContainerMods,
865
                        "test", [], None, mods, None, None, None)
866

    
867
    mods = instance._PrepareContainerMods([
868
      (constants.DDM_REMOVE, 0, object()),
869
      ], None)
870
    self.assertRaises(AssertionError, instance._ApplyContainerMods,
871
                      "test", [""], None, mods, None, None, None)
872

    
873
  def testAddError(self):
874
    for idx in range(-100, -1) + [100]:
875
      mods = instance._PrepareContainerMods([
876
        (constants.DDM_ADD, idx, None),
877
        ], None)
878
      self.assertRaises(IndexError, instance._ApplyContainerMods,
879
                        "test", [], None, mods, None, None, None)
880

    
881
  def testRemove(self):
882
    container = ["item 1", "item 2"]
883
    mods = instance._PrepareContainerMods([
884
      (constants.DDM_ADD, -1, "aaa"),
885
      (constants.DDM_REMOVE, -1, None),
886
      (constants.DDM_ADD, -1, "bbb"),
887
      ], None)
888
    chgdesc = []
889
    instance._ApplyContainerMods("test", container, chgdesc, mods,
890
                                 None, None, None)
891
    self.assertEqual(container, ["item 1", "item 2", "bbb"])
892
    self.assertEqual(chgdesc, [
893
      ("test/2", "remove"),
894
      ])
895

    
896
  def testModify(self):
897
    container = ["item 1", "item 2"]
898
    mods = instance._PrepareContainerMods([
899
      (constants.DDM_MODIFY, -1, "a"),
900
      (constants.DDM_MODIFY, 0, "b"),
901
      (constants.DDM_MODIFY, 1, "c"),
902
      ], None)
903
    chgdesc = []
904
    instance._ApplyContainerMods("test", container, chgdesc, mods,
905
                                 None, None, None)
906
    self.assertEqual(container, ["item 1", "item 2"])
907
    self.assertEqual(chgdesc, [])
908

    
909
    for idx in [-2, len(container) + 1]:
910
      mods = instance._PrepareContainerMods([
911
        (constants.DDM_MODIFY, idx, "error"),
912
        ], None)
913
      self.assertRaises(IndexError, instance._ApplyContainerMods,
914
                        "test", container, None, mods, None, None, None)
915

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

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

    
930
  @staticmethod
931
  def _RemoveTestFn(idx, item, private):
932
    private.data = ("remove", idx, item)
933

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

    
978

    
979
class _FakeConfigForGenDiskTemplate(ConfigMock):
980
  def __init__(self):
981
    super(_FakeConfigForGenDiskTemplate, self).__init__()
982

    
983
    self._unique_id = itertools.count()
984
    self._drbd_minor = itertools.count(20)
985
    self._port = itertools.count(constants.FIRST_DRBD_PORT)
986
    self._secret = itertools.count()
987

    
988
  def GenerateUniqueID(self, ec_id):
989
    return "ec%s-uq%s" % (ec_id, self._unique_id.next())
990

    
991
  def AllocateDRBDMinor(self, nodes, instance):
992
    return [self._drbd_minor.next()
993
            for _ in nodes]
994

    
995
  def AllocatePort(self):
996
    return self._port.next()
997

    
998
  def GenerateDRBDSecret(self, ec_id):
999
    return "ec%s-secret%s" % (ec_id, self._secret.next())
1000

    
1001

    
1002
class TestGenerateDiskTemplate(CmdlibTestCase):
1003
  def setUp(self):
1004
    super(TestGenerateDiskTemplate, self).setUp()
1005

    
1006
    self.cfg = _FakeConfigForGenDiskTemplate()
1007
    self.cluster.enabled_disk_templates = list(constants.DISK_TEMPLATES)
1008

    
1009
    self.nodegroup = self.cfg.AddNewNodeGroup(name="ng")
1010

    
1011
    self.lu = self.GetMockLU()
1012

    
1013
  @staticmethod
1014
  def GetDiskParams():
1015
    return copy.deepcopy(constants.DISK_DT_DEFAULTS)
1016

    
1017
  def testWrongDiskTemplate(self):
1018
    gdt = instance.GenerateDiskTemplate
1019
    disk_template = "##unknown##"
1020

    
1021
    assert disk_template not in constants.DISK_TEMPLATES
1022

    
1023
    self.assertRaises(errors.OpPrereqError, gdt, self.lu, disk_template,
1024
                      "inst26831.example.com", "node30113.example.com", [], [],
1025
                      NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1026
                      self.GetDiskParams())
1027

    
1028
  def testDiskless(self):
1029
    gdt = instance.GenerateDiskTemplate
1030

    
1031
    result = gdt(self.lu, constants.DT_DISKLESS, "inst27734.example.com",
1032
                 "node30113.example.com", [], [],
1033
                 NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1034
                 self.GetDiskParams())
1035
    self.assertEqual(result, [])
1036

    
1037
  def _TestTrivialDisk(self, template, disk_info, base_index, exp_dev_type,
1038
                       file_storage_dir=NotImplemented,
1039
                       file_driver=NotImplemented):
1040
    gdt = instance.GenerateDiskTemplate
1041

    
1042
    map(lambda params: utils.ForceDictType(params,
1043
                                           constants.IDISK_PARAMS_TYPES),
1044
        disk_info)
1045

    
1046
    # Check if non-empty list of secondaries is rejected
1047
    self.assertRaises(errors.ProgrammerError, gdt, self.lu,
1048
                      template, "inst25088.example.com",
1049
                      "node185.example.com", ["node323.example.com"], [],
1050
                      NotImplemented, NotImplemented, base_index,
1051
                      self.lu.LogInfo, self.GetDiskParams())
1052

    
1053
    result = gdt(self.lu, template, "inst21662.example.com",
1054
                 "node21741.example.com", [],
1055
                 disk_info, file_storage_dir, file_driver, base_index,
1056
                 self.lu.LogInfo, self.GetDiskParams())
1057

    
1058
    for (idx, disk) in enumerate(result):
1059
      self.assertTrue(isinstance(disk, objects.Disk))
1060
      self.assertEqual(disk.dev_type, exp_dev_type)
1061
      self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
1062
      self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
1063
      self.assertTrue(disk.children is None)
1064

    
1065
    self._CheckIvNames(result, base_index, base_index + len(disk_info))
1066
    instance._UpdateIvNames(base_index, result)
1067
    self._CheckIvNames(result, base_index, base_index + len(disk_info))
1068

    
1069
    return result
1070

    
1071
  def _CheckIvNames(self, disks, base_index, end_index):
1072
    self.assertEqual(map(operator.attrgetter("iv_name"), disks),
1073
                     ["disk/%s" % i for i in range(base_index, end_index)])
1074

    
1075
  def testPlain(self):
1076
    disk_info = [{
1077
      constants.IDISK_SIZE: 1024,
1078
      constants.IDISK_MODE: constants.DISK_RDWR,
1079
      }, {
1080
      constants.IDISK_SIZE: 4096,
1081
      constants.IDISK_VG: "othervg",
1082
      constants.IDISK_MODE: constants.DISK_RDWR,
1083
      }]
1084

    
1085
    result = self._TestTrivialDisk(constants.DT_PLAIN, disk_info, 3,
1086
                                   constants.DT_PLAIN)
1087

    
1088
    self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1089
      ("xenvg", "ec1-uq0.disk3"),
1090
      ("othervg", "ec1-uq1.disk4"),
1091
      ])
1092

    
1093
  def testFile(self):
1094
    # anything != DT_FILE would do here
1095
    self.cluster.enabled_disk_templates = [constants.DT_PLAIN]
1096
    self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
1097
                      constants.DT_FILE, [], 0, NotImplemented)
1098
    self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
1099
                      constants.DT_SHARED_FILE, [], 0, NotImplemented)
1100

    
1101
    for disk_template in [constants.DT_FILE, constants.DT_SHARED_FILE]:
1102
      disk_info = [{
1103
        constants.IDISK_SIZE: 80 * 1024,
1104
        constants.IDISK_MODE: constants.DISK_RDONLY,
1105
        }, {
1106
        constants.IDISK_SIZE: 4096,
1107
        constants.IDISK_MODE: constants.DISK_RDWR,
1108
        }, {
1109
        constants.IDISK_SIZE: 6 * 1024,
1110
        constants.IDISK_MODE: constants.DISK_RDWR,
1111
        }]
1112

    
1113
      self.cluster.enabled_disk_templates = [disk_template]
1114
      result = self._TestTrivialDisk(
1115
        disk_template, disk_info, 2, disk_template,
1116
        file_storage_dir="/tmp", file_driver=constants.FD_BLKTAP)
1117

    
1118
      self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1119
        (constants.FD_BLKTAP, "/tmp/disk2"),
1120
        (constants.FD_BLKTAP, "/tmp/disk3"),
1121
        (constants.FD_BLKTAP, "/tmp/disk4"),
1122
        ])
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 SetDiskID(self, device, node_uuid):
1253
    assert isinstance(device, objects.Disk)
1254
    assert node_uuid == self._exp_node_uuid
1255

    
1256
  def GetNodeName(self, node_uuid):
1257
    assert node_uuid == self._exp_node_uuid
1258
    return "name.of.expected.node"
1259

    
1260

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

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

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

    
1275

    
1276
class _DiskWipeProgressTracker:
1277
  def __init__(self, start_offset):
1278
    self._start_offset = start_offset
1279
    self.progress = {}
1280

    
1281
  def __call__(self, (disk, _), offset, size):
1282
    assert isinstance(offset, (long, int))
1283
    assert isinstance(size, (long, int))
1284

    
1285
    max_chunk_size = (disk.size / 100.0 * constants.MIN_WIPE_CHUNK_PERCENT)
1286

    
1287
    assert offset >= self._start_offset
1288
    assert (offset + size) <= disk.size
1289

    
1290
    assert size > 0
1291
    assert size <= constants.MAX_WIPE_CHUNK
1292
    assert size <= max_chunk_size
1293

    
1294
    assert offset == self._start_offset or disk.logical_id in self.progress
1295

    
1296
    # Keep track of progress
1297
    cur_progress = self.progress.setdefault(disk.logical_id, self._start_offset)
1298

    
1299
    assert cur_progress == offset
1300

    
1301
    # Record progress
1302
    self.progress[disk.logical_id] += size
1303

    
1304
    return (True, None)
1305

    
1306

    
1307
class TestWipeDisks(unittest.TestCase):
1308
  def _FailingPauseCb(self, (disks, _), pause):
1309
    self.assertEqual(len(disks), 3)
1310
    self.assertTrue(pause)
1311
    # Simulate an RPC error
1312
    return (False, "error")
1313

    
1314
  def testPauseFailure(self):
1315
    node_name = "node1372.example.com"
1316

    
1317
    lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, self._FailingPauseCb,
1318
                                     NotImplemented),
1319
                 cfg=_ConfigForDiskWipe(node_name))
1320

    
1321
    disks = [
1322
      objects.Disk(dev_type=constants.DT_PLAIN),
1323
      objects.Disk(dev_type=constants.DT_PLAIN),
1324
      objects.Disk(dev_type=constants.DT_PLAIN),
1325
      ]
1326

    
1327
    inst = objects.Instance(name="inst21201",
1328
                            primary_node=node_name,
1329
                            disk_template=constants.DT_PLAIN,
1330
                            disks=disks)
1331

    
1332
    self.assertRaises(errors.OpExecError, instance.WipeDisks, lu, inst)
1333

    
1334
  def _FailingWipeCb(self, (disk, _), offset, size):
1335
    # This should only ever be called for the first disk
1336
    self.assertEqual(disk.logical_id, "disk0")
1337
    return (False, None)
1338

    
1339
  def testFailingWipe(self):
1340
    node_uuid = "node13445-uuid"
1341
    pt = _DiskPauseTracker()
1342

    
1343
    lu = _FakeLU(rpc=_RpcForDiskWipe(node_uuid, pt, self._FailingWipeCb),
1344
                 cfg=_ConfigForDiskWipe(node_uuid))
1345

    
1346
    disks = [
1347
      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk0",
1348
                   size=100 * 1024),
1349
      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk1",
1350
                   size=500 * 1024),
1351
      objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk2", size=256),
1352
      ]
1353

    
1354
    inst = objects.Instance(name="inst562",
1355
                            primary_node=node_uuid,
1356
                            disk_template=constants.DT_PLAIN,
1357
                            disks=disks)
1358

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

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

    
1376
  def _PrepareWipeTest(self, start_offset, disks):
1377
    node_name = "node-with-offset%s.example.com" % start_offset
1378
    pauset = _DiskPauseTracker()
1379
    progresst = _DiskWipeProgressTracker(start_offset)
1380

    
1381
    lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, pauset, progresst),
1382
                 cfg=_ConfigForDiskWipe(node_name))
1383

    
1384
    instance = objects.Instance(name="inst3560",
1385
                                primary_node=node_name,
1386
                                disk_template=constants.DT_PLAIN,
1387
                                disks=disks)
1388

    
1389
    return (lu, instance, pauset, progresst)
1390

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

    
1401
    (lu, inst, pauset, progresst) = self._PrepareWipeTest(0, disks)
1402

    
1403
    instance.WipeDisks(lu, inst)
1404

    
1405
    self.assertEqual(pauset.history, [
1406
      ("disk0", 1024, True),
1407
      ("disk1", 500 * 1024, True),
1408
      ("disk2", 128, True),
1409
      ("disk3", constants.MAX_WIPE_CHUNK, True),
1410
      ("disk0", 1024, False),
1411
      ("disk1", 500 * 1024, False),
1412
      ("disk2", 128, False),
1413
      ("disk3", constants.MAX_WIPE_CHUNK, False),
1414
      ])
1415

    
1416
    # Ensure the complete disk has been wiped
1417
    self.assertEqual(progresst.progress,
1418
                     dict((i.logical_id, i.size) for i in disks))
1419

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

    
1429
      (lu, inst, pauset, progresst) = \
1430
        self._PrepareWipeTest(start_offset, disks)
1431

    
1432
      # Test start offset with only one disk
1433
      instance.WipeDisks(lu, inst,
1434
                         disks=[(1, disks[1], start_offset)])
1435

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

    
1445

    
1446
class TestCheckOpportunisticLocking(unittest.TestCase):
1447
  class OpTest(opcodes.OpCode):
1448
    OP_PARAMS = [
1449
      ("opportunistic_locking", False, ht.TBool, None),
1450
      ("iallocator", None, ht.TMaybe(ht.TNonEmptyString), "")
1451
      ]
1452

    
1453
  @classmethod
1454
  def _MakeOp(cls, **kwargs):
1455
    op = cls.OpTest(**kwargs)
1456
    op.Validate(True)
1457
    return op
1458

    
1459
  def testMissingAttributes(self):
1460
    self.assertRaises(AttributeError, instance._CheckOpportunisticLocking,
1461
                      object())
1462

    
1463
  def testDefaults(self):
1464
    op = self._MakeOp()
1465
    instance._CheckOpportunisticLocking(op)
1466

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

    
1478

    
1479
class TestLUInstanceRemove(CmdlibTestCase):
1480
  def testRemoveMissingInstance(self):
1481
    op = opcodes.OpInstanceRemove(instance_name="missing.inst")
1482
    self.ExecOpCodeExpectOpPrereqError(op, "Instance 'missing.inst' not known")
1483

    
1484
  def testRemoveInst(self):
1485
    inst = self.cfg.AddNewInstance(disks=[])
1486
    op = opcodes.OpInstanceRemove(instance_name=inst.name)
1487
    self.ExecOpCode(op)
1488

    
1489

    
1490
class TestLUInstanceMove(CmdlibTestCase):
1491
  def setUp(self):
1492
    super(TestLUInstanceMove, self).setUp()
1493

    
1494
    self.node = self.cfg.AddNewNode()
1495

    
1496
    self.rpc.call_blockdev_assemble.return_value = \
1497
      self.RpcResultsBuilder() \
1498
        .CreateSuccessfulNodeResult(self.node, "/dev/mocked_path")
1499
    self.rpc.call_blockdev_export.return_value = \
1500
      self.RpcResultsBuilder() \
1501
        .CreateSuccessfulNodeResult(self.master, "")
1502
    self.rpc.call_blockdev_remove.return_value = \
1503
      self.RpcResultsBuilder() \
1504
        .CreateSuccessfulNodeResult(self.master, "")
1505

    
1506
  def testMissingInstance(self):
1507
    op = opcodes.OpInstanceMove(instance_name="missing.inst",
1508
                                target_node=self.node.name)
1509
    self.ExecOpCodeExpectOpPrereqError(op, "Instance 'missing.inst' not known")
1510

    
1511
  def testUncopyableDiskTemplate(self):
1512
    inst = self.cfg.AddNewInstance(disk_template=constants.DT_SHARED_FILE)
1513
    op = opcodes.OpInstanceMove(instance_name=inst.name,
1514
                                target_node=self.node.name)
1515
    self.ExecOpCodeExpectOpPrereqError(
1516
      op, "Disk template sharedfile not suitable for copying")
1517

    
1518
  def testAlreadyOnTargetNode(self):
1519
    inst = self.cfg.AddNewInstance()
1520
    op = opcodes.OpInstanceMove(instance_name=inst.name,
1521
                                target_node=self.master.name)
1522
    self.ExecOpCodeExpectOpPrereqError(
1523
      op, "Instance .* is already on the node .*")
1524

    
1525
  def testMoveStoppedInstance(self):
1526
    inst = self.cfg.AddNewInstance()
1527
    op = opcodes.OpInstanceMove(instance_name=inst.name,
1528
                                target_node=self.node.name)
1529
    self.ExecOpCode(op)
1530

    
1531
  def testMoveRunningInstance(self):
1532
    self.rpc.call_node_info.return_value = \
1533
      self.RpcResultsBuilder() \
1534
        .AddSuccessfulNode(self.node,
1535
                           (NotImplemented, NotImplemented,
1536
                            ({"memory_free": 10000}, ))) \
1537
        .Build()
1538
    self.rpc.call_instance_start.return_value = \
1539
      self.RpcResultsBuilder() \
1540
        .CreateSuccessfulNodeResult(self.node, "")
1541

    
1542
    inst = self.cfg.AddNewInstance(admin_state=constants.ADMINST_UP)
1543
    op = opcodes.OpInstanceMove(instance_name=inst.name,
1544
                                target_node=self.node.name)
1545
    self.ExecOpCode(op)
1546

    
1547
  def testMoveFailingStart(self):
1548
    self.rpc.call_node_info.return_value = \
1549
      self.RpcResultsBuilder() \
1550
        .AddSuccessfulNode(self.node,
1551
                           (NotImplemented, NotImplemented,
1552
                            ({"memory_free": 10000}, ))) \
1553
        .Build()
1554
    self.rpc.call_instance_start.return_value = \
1555
      self.RpcResultsBuilder() \
1556
        .CreateFailedNodeResult(self.node)
1557

    
1558
    inst = self.cfg.AddNewInstance(admin_state=constants.ADMINST_UP)
1559
    op = opcodes.OpInstanceMove(instance_name=inst.name,
1560
                                target_node=self.node.name)
1561
    self.ExecOpCodeExpectOpExecError(
1562
      op, "Could not start instance .* on node .*")
1563

    
1564
  def testMoveFailingBlockdevAssemble(self):
1565
    inst = self.cfg.AddNewInstance()
1566
    self.rpc.call_blockdev_assemble.return_value = \
1567
      self.RpcResultsBuilder() \
1568
        .CreateFailedNodeResult(self.node)
1569
    op = opcodes.OpInstanceMove(instance_name=inst.name,
1570
                                target_node=self.node.name)
1571
    self.ExecOpCodeExpectOpExecError(op, "Errors during disk copy")
1572

    
1573
  def testMoveFailingBlockdevExport(self):
1574
    inst = self.cfg.AddNewInstance()
1575
    self.rpc.call_blockdev_export.return_value = \
1576
      self.RpcResultsBuilder() \
1577
        .CreateFailedNodeResult(self.node)
1578
    op = opcodes.OpInstanceMove(instance_name=inst.name,
1579
                                target_node=self.node.name)
1580
    self.ExecOpCodeExpectOpExecError(op, "Errors during disk copy")
1581

    
1582

    
1583
class TestLUInstanceRename(CmdlibTestCase):
1584
  def setUp(self):
1585
    super(TestLUInstanceRename, self).setUp()
1586

    
1587
    self.inst = self.cfg.AddNewInstance()
1588

    
1589
    self.op = opcodes.OpInstanceRename(instance_name=self.inst.name,
1590
                                       new_name="new_name.example.com")
1591

    
1592
  def testIpCheckWithoutNameCheck(self):
1593
    op = self.CopyOpCode(self.op,
1594
                         ip_check=True,
1595
                         name_check=False)
1596
    self.ExecOpCodeExpectOpPrereqError(
1597
      op, "IP address check requires a name check")
1598

    
1599
  def testIpAlreadyInUse(self):
1600
    self.netutils_mod.TcpPing.return_value = True
1601
    op = self.CopyOpCode(self.op)
1602
    self.ExecOpCodeExpectOpPrereqError(
1603
      op, "IP .* of instance .* already in use")
1604

    
1605
  def testExistingInstanceName(self):
1606
    self.cfg.AddNewInstance(name="new_name.example.com")
1607
    op = self.CopyOpCode(self.op)
1608
    self.ExecOpCodeExpectOpPrereqError(
1609
      op, "Instance .* is already in the cluster")
1610

    
1611
  def testFileInstance(self):
1612
    self.rpc.call_blockdev_assemble.return_value = \
1613
      self.RpcResultsBuilder() \
1614
        .CreateSuccessfulNodeResult(self.master, None)
1615
    self.rpc.call_blockdev_shutdown.return_value = \
1616
      self.RpcResultsBuilder() \
1617
        .CreateSuccessfulNodeResult(self.master, None)
1618

    
1619
    inst = self.cfg.AddNewInstance(disk_template=constants.DT_FILE)
1620
    op = self.CopyOpCode(self.op,
1621
                         instance_name=inst.name)
1622
    self.ExecOpCode(op)
1623

    
1624

    
1625
class TestLUInstanceMultiAlloc(CmdlibTestCase):
1626
  def setUp(self):
1627
    super(TestLUInstanceMultiAlloc, self).setUp()
1628

    
1629
    self.inst_op = opcodes.OpInstanceCreate(instance_name="inst.example.com",
1630
                                            disk_template=constants.DT_DRBD8,
1631
                                            disks=[],
1632
                                            nics=[],
1633
                                            os_type="mock_os",
1634
                                            hypervisor=constants.HT_XEN_HVM,
1635
                                            mode=constants.INSTANCE_CREATE)
1636

    
1637
  def testInstanceWithIAllocator(self):
1638
    inst = self.CopyOpCode(self.inst_op,
1639
                           iallocator="mock")
1640
    op = opcodes.OpInstanceMultiAlloc(instances=[inst])
1641
    self.ExecOpCodeExpectOpPrereqError(
1642
      op, "iallocator are not allowed to be set on instance objects")
1643

    
1644
  def testOnlySomeNodesGiven(self):
1645
    inst1 = self.CopyOpCode(self.inst_op,
1646
                            pnode=self.master.name)
1647
    inst2 = self.CopyOpCode(self.inst_op)
1648
    op = opcodes.OpInstanceMultiAlloc(instances=[inst1, inst2])
1649
    self.ExecOpCodeExpectOpPrereqError(
1650
      op, "There are instance objects providing pnode/snode while others"
1651
          " do not")
1652

    
1653
  def testMissingIAllocator(self):
1654
    self.cluster.default_iallocator = None
1655
    inst = self.CopyOpCode(self.inst_op)
1656
    op = opcodes.OpInstanceMultiAlloc(instances=[inst])
1657
    self.ExecOpCodeExpectOpPrereqError(
1658
      op, "No iallocator or nodes on the instances given and no cluster-wide"
1659
          " default iallocator found")
1660

    
1661
  def testDuplicateInstanceNames(self):
1662
    inst1 = self.CopyOpCode(self.inst_op)
1663
    inst2 = self.CopyOpCode(self.inst_op)
1664
    op = opcodes.OpInstanceMultiAlloc(instances=[inst1, inst2])
1665
    self.ExecOpCodeExpectOpPrereqError(
1666
      op, "There are duplicate instance names")
1667

    
1668
  def testWithGivenNodes(self):
1669
    snode = self.cfg.AddNewNode()
1670
    inst = self.CopyOpCode(self.inst_op,
1671
                           pnode=self.master.name,
1672
                           snode=snode.name)
1673
    op = opcodes.OpInstanceMultiAlloc(instances=[inst])
1674
    self.ExecOpCode(op)
1675

    
1676
  def testDryRun(self):
1677
    snode = self.cfg.AddNewNode()
1678
    inst = self.CopyOpCode(self.inst_op,
1679
                           pnode=self.master.name,
1680
                           snode=snode.name)
1681
    op = opcodes.OpInstanceMultiAlloc(instances=[inst],
1682
                                      dry_run=True)
1683
    self.ExecOpCode(op)
1684

    
1685
  def testWithIAllocator(self):
1686
    snode = self.cfg.AddNewNode()
1687
    self.iallocator_cls.return_value.result = \
1688
      ([("inst.example.com", [self.master.name, snode.name])], [])
1689

    
1690
    inst = self.CopyOpCode(self.inst_op)
1691
    op = opcodes.OpInstanceMultiAlloc(instances=[inst],
1692
                                      iallocator="mock_ialloc")
1693
    self.ExecOpCode(op)
1694

    
1695
  def testWithIAllocatorOpportunisticLocking(self):
1696
    snode = self.cfg.AddNewNode()
1697
    self.iallocator_cls.return_value.result = \
1698
      ([("inst.example.com", [self.master.name, snode.name])], [])
1699

    
1700
    inst = self.CopyOpCode(self.inst_op)
1701
    op = opcodes.OpInstanceMultiAlloc(instances=[inst],
1702
                                      iallocator="mock_ialloc",
1703
                                      opportunistic_locking=True)
1704
    self.ExecOpCode(op)
1705

    
1706
  def testFailingIAllocator(self):
1707
    self.iallocator_cls.return_value.success = False
1708

    
1709
    inst = self.CopyOpCode(self.inst_op)
1710
    op = opcodes.OpInstanceMultiAlloc(instances=[inst],
1711
                                      iallocator="mock_ialloc")
1712
    self.ExecOpCodeExpectOpPrereqError(
1713
      op, "Can't compute nodes using iallocator")
1714

    
1715

    
1716
class TestLUInstanceSetParams(CmdlibTestCase):
1717
  def setUp(self):
1718
    super(TestLUInstanceSetParams, self).setUp()
1719

    
1720
    self.inst = self.cfg.AddNewInstance()
1721
    self.op = opcodes.OpInstanceSetParams(instance_name=self.inst.name)
1722

    
1723
    self.running_inst = \
1724
      self.cfg.AddNewInstance(admin_state=constants.ADMINST_UP)
1725
    self.running_op = \
1726
      opcodes.OpInstanceSetParams(instance_name=self.running_inst.name)
1727

    
1728
    self.snode = self.cfg.AddNewNode()
1729

    
1730
    self.mocked_storage_type = constants.ST_LVM_VG
1731
    self.mocked_storage_free = 10000
1732
    self.mocked_master_cpu_total = 16
1733
    self.mocked_master_memory_free = 2048
1734
    self.mocked_snode_cpu_total = 16
1735
    self.mocked_snode_memory_free = 512
1736

    
1737
    self.mocked_running_inst_memory = 1024
1738
    self.mocked_running_inst_vcpus = 8
1739
    self.mocked_running_inst_state = "running"
1740
    self.mocked_running_inst_time = 10938474
1741

    
1742
    bootid = "mock_bootid"
1743
    storage_info = [
1744
      {
1745
        "type": self.mocked_storage_type,
1746
        "storage_free": self.mocked_storage_free
1747
      }
1748
    ]
1749
    hv_info_master = {
1750
      "cpu_total": self.mocked_master_cpu_total,
1751
      "memory_free": self.mocked_master_memory_free
1752
    }
1753
    hv_info_snode = {
1754
      "cpu_total": self.mocked_snode_cpu_total,
1755
      "memory_free": self.mocked_snode_memory_free
1756
    }
1757

    
1758
    self.rpc.call_node_info.return_value = \
1759
      self.RpcResultsBuilder() \
1760
        .AddSuccessfulNode(self.master,
1761
                           (bootid, storage_info, (hv_info_master, ))) \
1762
        .AddSuccessfulNode(self.snode,
1763
                           (bootid, storage_info, (hv_info_snode, ))) \
1764
        .Build()
1765

    
1766
    def _InstanceInfo(_, instance, __, ___):
1767
      if instance == self.inst.name:
1768
        return self.RpcResultsBuilder() \
1769
          .CreateSuccessfulNodeResult(self.master, None)
1770
      elif instance == self.running_inst.name:
1771
        return self.RpcResultsBuilder() \
1772
          .CreateSuccessfulNodeResult(
1773
            self.master, {
1774
              "memory": self.mocked_running_inst_memory,
1775
              "vcpus": self.mocked_running_inst_vcpus,
1776
              "state": self.mocked_running_inst_state,
1777
              "time": self.mocked_running_inst_time
1778
            })
1779
      else:
1780
        raise AssertionError()
1781
    self.rpc.call_instance_info.side_effect = _InstanceInfo
1782

    
1783
    self.rpc.call_bridges_exist.return_value = \
1784
      self.RpcResultsBuilder() \
1785
        .CreateSuccessfulNodeResult(self.master, True)
1786

    
1787
  def testNoChanges(self):
1788
    op = self.CopyOpCode(self.op)
1789
    self.ExecOpCodeExpectOpPrereqError(op, "No changes submitted")
1790

    
1791
  def testGlobalHvparams(self):
1792
    op = self.CopyOpCode(self.op,
1793
                         hvparams={constants.HV_MIGRATION_PORT: 1234})
1794
    self.ExecOpCodeExpectOpPrereqError(
1795
      op, "hypervisor parameters are global and cannot be customized")
1796

    
1797
  def testHvparams(self):
1798
    op = self.CopyOpCode(self.op,
1799
                         hvparams={constants.HV_BOOT_ORDER: "cd"})
1800
    self.ExecOpCode(op)
1801

    
1802
  def testDisksAndDiskTemplate(self):
1803
    op = self.CopyOpCode(self.op,
1804
                         disk_template=constants.DT_PLAIN,
1805
                         disks=[[constants.DDM_ADD, -1, {}]])
1806
    self.ExecOpCodeExpectOpPrereqError(
1807
      op, "Disk template conversion and other disk changes not supported at"
1808
          " the same time")
1809

    
1810
  def testDiskTemplateToMirroredNoRemoteNode(self):
1811
    op = self.CopyOpCode(self.op,
1812
                         disk_template=constants.DT_DRBD8)
1813
    self.ExecOpCodeExpectOpPrereqError(
1814
      op, "Changing the disk template to a mirrored one requires specifying"
1815
          " a secondary node")
1816

    
1817
  def testPrimaryNodeToOldPrimaryNode(self):
1818
    op = self.CopyOpCode(self.op,
1819
                         pnode=self.master.name)
1820
    self.ExecOpCode(op)
1821

    
1822
  def testPrimaryNodeChange(self):
1823
    node = self.cfg.AddNewNode()
1824
    op = self.CopyOpCode(self.op,
1825
                         pnode=node.name)
1826
    self.ExecOpCode(op)
1827

    
1828
  def testPrimaryNodeChangeRunningInstance(self):
1829
    node = self.cfg.AddNewNode()
1830
    op = self.CopyOpCode(self.running_op,
1831
                         pnode=node.name)
1832
    self.ExecOpCodeExpectOpPrereqError(op, "Instance is still running")
1833

    
1834
  def testOsChange(self):
1835
    os = self.cfg.CreateOs(supported_variants=[])
1836
    self.rpc.call_os_get.return_value = \
1837
      self.RpcResultsBuilder() \
1838
        .CreateSuccessfulNodeResult(self.master, os)
1839
    op = self.CopyOpCode(self.op,
1840
                         os_name=os.name)
1841
    self.ExecOpCode(op)
1842

    
1843
  def testVCpuChange(self):
1844
    op = self.CopyOpCode(self.op,
1845
                         beparams={
1846
                           constants.BE_VCPUS: 4
1847
                         })
1848
    self.ExecOpCode(op)
1849

    
1850
  def testWrongCpuMask(self):
1851
    op = self.CopyOpCode(self.op,
1852
                         beparams={
1853
                           constants.BE_VCPUS: 4
1854
                         },
1855
                         hvparams={
1856
                           constants.HV_CPU_MASK: "1,2:3,4"
1857
                         })
1858
    self.ExecOpCodeExpectOpPrereqError(
1859
      op, "Number of vCPUs .* does not match the CPU mask .*")
1860

    
1861
  def testCorrectCpuMask(self):
1862
    op = self.CopyOpCode(self.op,
1863
                         beparams={
1864
                           constants.BE_VCPUS: 4
1865
                         },
1866
                         hvparams={
1867
                           constants.HV_CPU_MASK: "1,2:3,4:all:1,4"
1868
                         })
1869
    self.ExecOpCode(op)
1870

    
1871
  def testOsParams(self):
1872
    op = self.CopyOpCode(self.op,
1873
                         osparams={
1874
                           self.os.supported_parameters[0]: "test_param_val"
1875
                         })
1876
    self.ExecOpCode(op)
1877

    
1878
  def testIncreaseMemoryTooMuch(self):
1879
    op = self.CopyOpCode(self.running_op,
1880
                         beparams={
1881
                           constants.BE_MAXMEM:
1882
                             self.mocked_master_memory_free * 2
1883
                         })
1884
    self.ExecOpCodeExpectOpPrereqError(
1885
      op, "This change will prevent the instance from starting")
1886

    
1887
  def testIncreaseMemory(self):
1888
    op = self.CopyOpCode(self.running_op,
1889
                         beparams={
1890
                           constants.BE_MAXMEM: self.mocked_master_memory_free
1891
                         })
1892
    self.ExecOpCode(op)
1893

    
1894
  def testIncreaseMemoryTooMuchForSecondary(self):
1895
    inst = self.cfg.AddNewInstance(admin_state=constants.ADMINST_UP,
1896
                                   disk_template=constants.DT_DRBD8,
1897
                                   secondary_node=self.snode)
1898
    self.rpc.call_instance_info.side_effect = [
1899
      self.RpcResultsBuilder()
1900
        .CreateSuccessfulNodeResult(self.master,
1901
                                    {
1902
                                      "memory":
1903
                                        self.mocked_snode_memory_free * 2,
1904
                                      "vcpus": self.mocked_running_inst_vcpus,
1905
                                      "state": self.mocked_running_inst_state,
1906
                                      "time": self.mocked_running_inst_time
1907
                                    })]
1908

    
1909
    op = self.CopyOpCode(self.op,
1910
                         instance_name=inst.name,
1911
                         beparams={
1912
                           constants.BE_MAXMEM:
1913
                             self.mocked_snode_memory_free * 2,
1914
                           constants.BE_AUTO_BALANCE: True
1915
                         })
1916
    self.ExecOpCodeExpectOpPrereqError(
1917
      op, "This change will prevent the instance from failover to its"
1918
          " secondary node")
1919

    
1920
  def testInvalidRuntimeMemory(self):
1921
    op = self.CopyOpCode(self.running_op,
1922
                         runtime_mem=self.mocked_master_memory_free * 2)
1923
    self.ExecOpCodeExpectOpPrereqError(
1924
      op, "Instance .* must have memory between .* and .* of memory")
1925

    
1926
  def testIncreaseRuntimeMemory(self):
1927
    op = self.CopyOpCode(self.running_op,
1928
                         runtime_mem=self.mocked_master_memory_free,
1929
                         beparams={
1930
                           constants.BE_MAXMEM: self.mocked_master_memory_free
1931
                         })
1932
    self.ExecOpCode(op)
1933

    
1934
  def testAddNicWithPoolIpNoNetwork(self):
1935
    op = self.CopyOpCode(self.op,
1936
                         nics=[(constants.DDM_ADD, -1,
1937
                                {
1938
                                  constants.INIC_IP: constants.NIC_IP_POOL
1939
                                })])
1940
    self.ExecOpCodeExpectOpPrereqError(
1941
      op, "If ip=pool, parameter network cannot be none")
1942

    
1943
  def testAddNicWithPoolIp(self):
1944
    net = self.cfg.AddNewNetwork()
1945
    self.cfg.ConnectNetworkToGroup(net, self.group)
1946
    op = self.CopyOpCode(self.op,
1947
                         nics=[(constants.DDM_ADD, -1,
1948
                                {
1949
                                  constants.INIC_IP: constants.NIC_IP_POOL,
1950
                                  constants.INIC_NETWORK: net.name
1951
                                })])
1952
    self.ExecOpCode(op)
1953

    
1954
  def testAddNicWithInvalidIp(self):
1955
    op = self.CopyOpCode(self.op,
1956
                         nics=[(constants.DDM_ADD, -1,
1957
                                {
1958
                                  constants.INIC_IP: "invalid"
1959
                                })])
1960
    self.ExecOpCodeExpectOpPrereqError(
1961
      op, "Invalid IP address")
1962

    
1963
  def testAddNic(self):
1964
    op = self.CopyOpCode(self.op,
1965
                         nics=[(constants.DDM_ADD, -1, {})])
1966
    self.ExecOpCode(op)
1967

    
1968
  def testAddNicWithIp(self):
1969
    op = self.CopyOpCode(self.op,
1970
                         nics=[(constants.DDM_ADD, -1,
1971
                                {
1972
                                  constants.INIC_IP: "2.3.1.4"
1973
                                })])
1974
    self.ExecOpCode(op)
1975

    
1976
  def testModifyNicRoutedWithoutIp(self):
1977
    op = self.CopyOpCode(self.op,
1978
                         nics=[(constants.DDM_MODIFY, 0,
1979
                                {
1980
                                  constants.INIC_MODE: constants.NIC_MODE_ROUTED
1981
                                })])
1982
    self.ExecOpCodeExpectOpPrereqError(
1983
      op, "Cannot set the NIC IP address to None on a routed NIC")
1984

    
1985
  def testModifyNicSetMac(self):
1986
    op = self.CopyOpCode(self.op,
1987
                         nics=[(constants.DDM_MODIFY, 0,
1988
                                {
1989
                                  constants.INIC_MAC: "0a:12:95:15:bf:75"
1990
                                })])
1991
    self.ExecOpCode(op)
1992

    
1993
  def testModifyNicWithPoolIpNoNetwork(self):
1994
    op = self.CopyOpCode(self.op,
1995
                         nics=[(constants.DDM_MODIFY, -1,
1996
                                {
1997
                                  constants.INIC_IP: constants.NIC_IP_POOL
1998
                                })])
1999
    self.ExecOpCodeExpectOpPrereqError(
2000
      op, "ip=pool, but no network found")
2001

    
2002
  def testModifyNicSetNet(self):
2003
    old_net = self.cfg.AddNewNetwork()
2004
    self.cfg.ConnectNetworkToGroup(old_net, self.group)
2005
    inst = self.cfg.AddNewInstance(nics=[
2006
      self.cfg.CreateNic(network=old_net,
2007
                         ip="192.168.123.2")])
2008

    
2009
    new_net = self.cfg.AddNewNetwork(mac_prefix="be")
2010
    self.cfg.ConnectNetworkToGroup(new_net, self.group)
2011
    op = self.CopyOpCode(self.op,
2012
                         instance_name=inst.name,
2013
                         nics=[(constants.DDM_MODIFY, 0,
2014
                                {
2015
                                  constants.INIC_NETWORK: new_net.name
2016
                                })])
2017
    self.ExecOpCode(op)
2018

    
2019
  def testModifyNicSetLinkWhileConnected(self):
2020
    old_net = self.cfg.AddNewNetwork()
2021
    self.cfg.ConnectNetworkToGroup(old_net, self.group)
2022
    inst = self.cfg.AddNewInstance(nics=[
2023
      self.cfg.CreateNic(network=old_net)])
2024

    
2025
    op = self.CopyOpCode(self.op,
2026
                         instance_name=inst.name,
2027
                         nics=[(constants.DDM_MODIFY, 0,
2028
                                {
2029
                                  constants.INIC_LINK: "mock_link"
2030
                                })])
2031
    self.ExecOpCodeExpectOpPrereqError(
2032
      op, "Not allowed to change link or mode of a NIC that is connected"
2033
          " to a network")
2034

    
2035
  def testModifyNicSetNetAndIp(self):
2036
    net = self.cfg.AddNewNetwork(mac_prefix="be", network="123.123.123.0/24")
2037
    self.cfg.ConnectNetworkToGroup(net, self.group)
2038
    op = self.CopyOpCode(self.op,
2039
                         nics=[(constants.DDM_MODIFY, 0,
2040
                                {
2041
                                  constants.INIC_NETWORK: net.name,
2042
                                  constants.INIC_IP: "123.123.123.1"
2043
                                })])
2044
    self.ExecOpCode(op)
2045

    
2046
  def testModifyNic(self):
2047
    op = self.CopyOpCode(self.op,
2048
                         nics=[(constants.DDM_MODIFY, 0, {})])
2049
    self.ExecOpCode(op)
2050

    
2051
  def testRemoveLastNic(self):
2052
    op = self.CopyOpCode(self.op,
2053
                         nics=[(constants.DDM_REMOVE, 0, {})])
2054
    self.ExecOpCodeExpectOpPrereqError(
2055
      op, "violates policy")
2056

    
2057
  def testRemoveNic(self):
2058
    inst = self.cfg.AddNewInstance(nics=[self.cfg.CreateNic(),
2059
                                         self.cfg.CreateNic()])
2060
    op = self.CopyOpCode(self.op,
2061
                         instance_name=inst.name,
2062
                         nics=[(constants.DDM_REMOVE, 0, {})])
2063
    self.ExecOpCode(op)
2064

    
2065
  def testSetOffline(self):
2066
    op = self.CopyOpCode(self.op,
2067
                         offline=True)
2068
    self.ExecOpCode(op)
2069

    
2070
  def testUnsetOffline(self):
2071
    op = self.CopyOpCode(self.op,
2072
                         offline=False)
2073
    self.ExecOpCode(op)
2074

    
2075
  def testAddDiskInvalidMode(self):
2076
    op = self.CopyOpCode(self.op,
2077
                         disks=[[constants.DDM_ADD, -1,
2078
                                 {
2079
                                   constants.IDISK_MODE: "invalid"
2080
                                 }]])
2081
    self.ExecOpCodeExpectOpPrereqError(
2082
      op, "Invalid disk access mode 'invalid'")
2083

    
2084
  def testAddDiskMissingSize(self):
2085
    op = self.CopyOpCode(self.op,
2086
                         disks=[[constants.DDM_ADD, -1, {}]])
2087
    self.ExecOpCodeExpectOpPrereqError(
2088
      op, "Required disk parameter 'size' missing")
2089

    
2090
  def testAddDiskInvalidSize(self):
2091
    op = self.CopyOpCode(self.op,
2092
                         disks=[[constants.DDM_ADD, -1,
2093
                                 {
2094
                                   constants.IDISK_SIZE: "invalid"
2095
                                 }]])
2096
    self.ExecOpCodeExpectException(
2097
      op, errors.TypeEnforcementError, "is not a valid size")
2098

    
2099
  def testAddDisk(self):
2100
    op = self.CopyOpCode(self.op,
2101
                         disks=[[constants.DDM_ADD, -1,
2102
                                 {
2103
                                   constants.IDISK_SIZE: 1024
2104
                                 }]])
2105
    self.ExecOpCode(op)
2106

    
2107
  def testAddDiskNoneName(self):
2108
    op = self.CopyOpCode(self.op,
2109
                         disks=[[constants.DDM_ADD, -1,
2110
                                 {
2111
                                   constants.IDISK_SIZE: 1024,
2112
                                   constants.IDISK_NAME: constants.VALUE_NONE
2113
                                 }]])
2114
    self.ExecOpCode(op)
2115

    
2116
  def testModifyDiskWithSize(self):
2117
    op = self.CopyOpCode(self.op,
2118
                         disks=[[constants.DDM_MODIFY, 0,
2119
                                 {
2120
                                   constants.IDISK_SIZE: 1024
2121
                                 }]])
2122
    self.ExecOpCodeExpectOpPrereqError(
2123
      op, "Disk size change not possible, use grow-disk")
2124

    
2125
  def testModifyDiskWithRandomParams(self):
2126
    op = self.CopyOpCode(self.op,
2127
                         disks=[[constants.DDM_MODIFY, 0,
2128
                                 {
2129
                                   constants.IDISK_METAVG: "new_meta_vg",
2130
                                   constants.IDISK_MODE: "invalid",
2131
                                   constants.IDISK_NAME: "new_name"
2132
                                 }]])
2133
    self.ExecOpCodeExpectOpPrereqError(
2134
      op, "Disk modification doesn't support additional arbitrary parameters")
2135

    
2136
  def testModifyDiskUnsetName(self):
2137
    op = self.CopyOpCode(self.op,
2138
                         disks=[[constants.DDM_MODIFY, 0,
2139
                                  {
2140
                                    constants.IDISK_NAME: constants.VALUE_NONE
2141
                                  }]])
2142
    self.ExecOpCode(op)
2143

    
2144
  def testSetOldDiskTemplate(self):
2145
    op = self.CopyOpCode(self.op,
2146
                         disk_template=self.inst.disk_template)
2147
    self.ExecOpCodeExpectOpPrereqError(
2148
      op, "Instance already has disk template")
2149

    
2150
  def testSetDisabledDiskTemplate(self):
2151
    self.cfg.SetEnabledDiskTemplates([self.inst.disk_template])
2152
    op = self.CopyOpCode(self.op,
2153
                         disk_template=constants.DT_EXT)
2154
    self.ExecOpCodeExpectOpPrereqError(
2155
      op, "Disk template .* is not enabled for this cluster")
2156

    
2157
  def testInvalidDiskTemplateConversion(self):
2158
    op = self.CopyOpCode(self.op,
2159
                         disk_template=constants.DT_EXT)
2160
    self.ExecOpCodeExpectOpPrereqError(
2161
      op, "Unsupported disk template conversion from .* to .*")
2162

    
2163
  def testConvertToDRBDWithSecondarySameAsPrimary(self):
2164
    op = self.CopyOpCode(self.op,
2165
                         disk_template=constants.DT_DRBD8,
2166
                         remote_node=self.master.name)
2167
    self.ExecOpCodeExpectOpPrereqError(
2168
      op, "Given new secondary node .* is the same as the primary node"
2169
          " of the instance")
2170

    
2171
  def testConvertPlainToDRBD(self):
2172
    self.rpc.call_blockdev_shutdown.return_value = \
2173
      self.RpcResultsBuilder() \
2174
        .CreateSuccessfulNodeResult(self.master, True)
2175
    self.rpc.call_blockdev_getmirrorstatus.return_value = \
2176
      self.RpcResultsBuilder() \
2177
        .CreateSuccessfulNodeResult(self.master, [objects.BlockDevStatus()])
2178

    
2179
    op = self.CopyOpCode(self.op,
2180
                         disk_template=constants.DT_DRBD8,
2181
                         remote_node=self.snode.name)
2182
    self.ExecOpCode(op)
2183

    
2184
  def testConvertDRBDToPlain(self):
2185
    self.inst.disks = [self.cfg.CreateDisk(dev_type=constants.DT_DRBD8,
2186
                                           primary_node=self.master,
2187
                                           secondary_node=self.snode)]
2188
    self.inst.disk_template = constants.DT_DRBD8
2189
    self.rpc.call_blockdev_shutdown.return_value = \
2190
      self.RpcResultsBuilder() \
2191
        .CreateSuccessfulNodeResult(self.master, True)
2192
    self.rpc.call_blockdev_remove.return_value = \
2193
      self.RpcResultsBuilder() \
2194
        .CreateSuccessfulNodeResult(self.master)
2195
    self.rpc.call_blockdev_getmirrorstatus.return_value = \
2196
      self.RpcResultsBuilder() \
2197
        .CreateSuccessfulNodeResult(self.master, [objects.BlockDevStatus()])
2198

    
2199
    op = self.CopyOpCode(self.op,
2200
                         disk_template=constants.DT_PLAIN)
2201
    self.ExecOpCode(op)
2202

    
2203

    
2204
class TestLUInstanceChangeGroup(CmdlibTestCase):
2205
  def setUp(self):
2206
    super(TestLUInstanceChangeGroup, self).setUp()
2207

    
2208
    self.group2 = self.cfg.AddNewNodeGroup()
2209
    self.node2 = self.cfg.AddNewNode(group=self.group2)
2210
    self.inst = self.cfg.AddNewInstance()
2211
    self.op = opcodes.OpInstanceChangeGroup(instance_name=self.inst.name)
2212

    
2213
  def testTargetGroupIsInstanceGroup(self):
2214
    op = self.CopyOpCode(self.op,
2215
                         target_groups=[self.group.name])
2216
    self.ExecOpCodeExpectOpPrereqError(
2217
      op, "Can't use group\(s\) .* as targets, they are used by the"
2218
          " instance .*")
2219

    
2220
  def testNoTargetGroups(self):
2221
    inst = self.cfg.AddNewInstance(disk_template=constants.DT_DRBD8,
2222
                                   primary_node=self.master,
2223
                                   secondary_node=self.node2)
2224
    op = self.CopyOpCode(self.op,
2225
                         instance_name=inst.name)
2226
    self.ExecOpCodeExpectOpPrereqError(
2227
      op, "There are no possible target groups")
2228

    
2229
  def testFailingIAllocator(self):
2230
    self.iallocator_cls.return_value.success = False
2231
    op = self.CopyOpCode(self.op)
2232

    
2233
    self.ExecOpCodeExpectOpPrereqError(
2234
      op, "Can't compute solution for changing group of instance .*"
2235
          " using iallocator .*")
2236

    
2237
  def testChangeGroup(self):
2238
    self.iallocator_cls.return_value.success = True
2239
    self.iallocator_cls.return_value.result = ([], [], [])
2240
    op = self.CopyOpCode(self.op)
2241

    
2242
    self.ExecOpCode(op)
2243

    
2244

    
2245
if __name__ == "__main__":
2246
  testutils.GanetiTestProgram()