Statistics
| Branch: | Tag: | Revision:

root / test / py / cmdlib / instance_unittest.py @ 922a9e65

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=192.0.2.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 GetNodeName(self, node_uuid):
1253
    assert node_uuid == self._exp_node_uuid
1254
    return "name.of.expected.node"
1255

    
1256

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

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

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

    
1271

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

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

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

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

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

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

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

    
1295
    assert cur_progress == offset
1296

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

    
1300
    return (True, None)
1301

    
1302

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1399
    instance.WipeDisks(lu, inst)
1400

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

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

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

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

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

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

    
1441

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

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

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

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

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

    
1474

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

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

    
1485

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

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

    
1492
    self.rpc.call_blockdev_assemble.return_value = \
1493
      self.RpcResultsBuilder() \
1494
        .CreateSuccessfulNodeResult(self.node, "/dev/mocked_path")
1495
    self.rpc.call_blockdev_export.return_value = \
1496
      self.RpcResultsBuilder() \
1497
        .CreateSuccessfulNodeResult(self.master, "")
1498
    self.rpc.call_blockdev_remove.return_value = \
1499
      self.RpcResultsBuilder() \
1500
        .CreateSuccessfulNodeResult(self.master, "")
1501

    
1502
  def testMissingInstance(self):
1503
    op = opcodes.OpInstanceMove(instance_name="missing.inst",
1504
                                target_node=self.node.name)
1505
    self.ExecOpCodeExpectOpPrereqError(op, "Instance 'missing.inst' not known")
1506

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

    
1514
  def testAlreadyOnTargetNode(self):
1515
    inst = self.cfg.AddNewInstance()
1516
    op = opcodes.OpInstanceMove(instance_name=inst.name,
1517
                                target_node=self.master.name)
1518
    self.ExecOpCodeExpectOpPrereqError(
1519
      op, "Instance .* is already on the node .*")
1520

    
1521
  def testMoveStoppedInstance(self):
1522
    inst = self.cfg.AddNewInstance()
1523
    op = opcodes.OpInstanceMove(instance_name=inst.name,
1524
                                target_node=self.node.name)
1525
    self.ExecOpCode(op)
1526

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

    
1538
    inst = self.cfg.AddNewInstance(admin_state=constants.ADMINST_UP)
1539
    op = opcodes.OpInstanceMove(instance_name=inst.name,
1540
                                target_node=self.node.name)
1541
    self.ExecOpCode(op)
1542

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

    
1554
    inst = self.cfg.AddNewInstance(admin_state=constants.ADMINST_UP)
1555
    op = opcodes.OpInstanceMove(instance_name=inst.name,
1556
                                target_node=self.node.name)
1557
    self.ExecOpCodeExpectOpExecError(
1558
      op, "Could not start instance .* on node .*")
1559

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

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

    
1578

    
1579
class TestLUInstanceRename(CmdlibTestCase):
1580
  def setUp(self):
1581
    super(TestLUInstanceRename, self).setUp()
1582

    
1583
    self.inst = self.cfg.AddNewInstance()
1584

    
1585
    self.op = opcodes.OpInstanceRename(instance_name=self.inst.name,
1586
                                       new_name="new_name.example.com")
1587

    
1588
  def testIpCheckWithoutNameCheck(self):
1589
    op = self.CopyOpCode(self.op,
1590
                         ip_check=True,
1591
                         name_check=False)
1592
    self.ExecOpCodeExpectOpPrereqError(
1593
      op, "IP address check requires a name check")
1594

    
1595
  def testIpAlreadyInUse(self):
1596
    self.netutils_mod.TcpPing.return_value = True
1597
    op = self.CopyOpCode(self.op)
1598
    self.ExecOpCodeExpectOpPrereqError(
1599
      op, "IP .* of instance .* already in use")
1600

    
1601
  def testExistingInstanceName(self):
1602
    self.cfg.AddNewInstance(name="new_name.example.com")
1603
    op = self.CopyOpCode(self.op)
1604
    self.ExecOpCodeExpectOpPrereqError(
1605
      op, "Instance .* is already in the cluster")
1606

    
1607
  def testFileInstance(self):
1608
    self.rpc.call_blockdev_assemble.return_value = \
1609
      self.RpcResultsBuilder() \
1610
        .CreateSuccessfulNodeResult(self.master, None)
1611
    self.rpc.call_blockdev_shutdown.return_value = \
1612
      self.RpcResultsBuilder() \
1613
        .CreateSuccessfulNodeResult(self.master, None)
1614

    
1615
    inst = self.cfg.AddNewInstance(disk_template=constants.DT_FILE)
1616
    op = self.CopyOpCode(self.op,
1617
                         instance_name=inst.name)
1618
    self.ExecOpCode(op)
1619

    
1620

    
1621
class TestLUInstanceMultiAlloc(CmdlibTestCase):
1622
  def setUp(self):
1623
    super(TestLUInstanceMultiAlloc, self).setUp()
1624

    
1625
    self.inst_op = opcodes.OpInstanceCreate(instance_name="inst.example.com",
1626
                                            disk_template=constants.DT_DRBD8,
1627
                                            disks=[],
1628
                                            nics=[],
1629
                                            os_type="mock_os",
1630
                                            hypervisor=constants.HT_XEN_HVM,
1631
                                            mode=constants.INSTANCE_CREATE)
1632

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

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

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

    
1657
  def testDuplicateInstanceNames(self):
1658
    inst1 = self.CopyOpCode(self.inst_op)
1659
    inst2 = self.CopyOpCode(self.inst_op)
1660
    op = opcodes.OpInstanceMultiAlloc(instances=[inst1, inst2])
1661
    self.ExecOpCodeExpectOpPrereqError(
1662
      op, "There are duplicate instance names")
1663

    
1664
  def testWithGivenNodes(self):
1665
    snode = self.cfg.AddNewNode()
1666
    inst = self.CopyOpCode(self.inst_op,
1667
                           pnode=self.master.name,
1668
                           snode=snode.name)
1669
    op = opcodes.OpInstanceMultiAlloc(instances=[inst])
1670
    self.ExecOpCode(op)
1671

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

    
1681
  def testWithIAllocator(self):
1682
    snode = self.cfg.AddNewNode()
1683
    self.iallocator_cls.return_value.result = \
1684
      ([("inst.example.com", [self.master.name, snode.name])], [])
1685

    
1686
    inst = self.CopyOpCode(self.inst_op)
1687
    op = opcodes.OpInstanceMultiAlloc(instances=[inst],
1688
                                      iallocator="mock_ialloc")
1689
    self.ExecOpCode(op)
1690

    
1691
  def testWithIAllocatorOpportunisticLocking(self):
1692
    snode = self.cfg.AddNewNode()
1693
    self.iallocator_cls.return_value.result = \
1694
      ([("inst.example.com", [self.master.name, snode.name])], [])
1695

    
1696
    inst = self.CopyOpCode(self.inst_op)
1697
    op = opcodes.OpInstanceMultiAlloc(instances=[inst],
1698
                                      iallocator="mock_ialloc",
1699
                                      opportunistic_locking=True)
1700
    self.ExecOpCode(op)
1701

    
1702
  def testFailingIAllocator(self):
1703
    self.iallocator_cls.return_value.success = False
1704

    
1705
    inst = self.CopyOpCode(self.inst_op)
1706
    op = opcodes.OpInstanceMultiAlloc(instances=[inst],
1707
                                      iallocator="mock_ialloc")
1708
    self.ExecOpCodeExpectOpPrereqError(
1709
      op, "Can't compute nodes using iallocator")
1710

    
1711

    
1712
class TestLUInstanceSetParams(CmdlibTestCase):
1713
  def setUp(self):
1714
    super(TestLUInstanceSetParams, self).setUp()
1715

    
1716
    self.inst = self.cfg.AddNewInstance()
1717
    self.op = opcodes.OpInstanceSetParams(instance_name=self.inst.name)
1718

    
1719
    self.running_inst = \
1720
      self.cfg.AddNewInstance(admin_state=constants.ADMINST_UP)
1721
    self.running_op = \
1722
      opcodes.OpInstanceSetParams(instance_name=self.running_inst.name)
1723

    
1724
    self.snode = self.cfg.AddNewNode()
1725

    
1726
    self.mocked_storage_type = constants.ST_LVM_VG
1727
    self.mocked_storage_free = 10000
1728
    self.mocked_master_cpu_total = 16
1729
    self.mocked_master_memory_free = 2048
1730
    self.mocked_snode_cpu_total = 16
1731
    self.mocked_snode_memory_free = 512
1732

    
1733
    self.mocked_running_inst_memory = 1024
1734
    self.mocked_running_inst_vcpus = 8
1735
    self.mocked_running_inst_state = "running"
1736
    self.mocked_running_inst_time = 10938474
1737

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

    
1754
    self.rpc.call_node_info.return_value = \
1755
      self.RpcResultsBuilder() \
1756
        .AddSuccessfulNode(self.master,
1757
                           (bootid, storage_info, (hv_info_master, ))) \
1758
        .AddSuccessfulNode(self.snode,
1759
                           (bootid, storage_info, (hv_info_snode, ))) \
1760
        .Build()
1761

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

    
1779
    self.rpc.call_bridges_exist.return_value = \
1780
      self.RpcResultsBuilder() \
1781
        .CreateSuccessfulNodeResult(self.master, True)
1782

    
1783
    self.rpc.call_blockdev_getmirrorstatus.side_effect = \
1784
      lambda node, _: self.RpcResultsBuilder() \
1785
                        .CreateSuccessfulNodeResult(node, [])
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="198.51.100.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()