Statistics
| Branch: | Tag: | Revision:

root / test / py / cmdlib / instance_unittest.py @ 72bac0c5

History | View | Annotate | Download (54.4 kB)

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

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

    
21

    
22
"""Tests for LUInstance*
23

24
"""
25

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

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

    
42
from cmdlib.cmdlib_unittest import _StubComputeIPolicySpecViolation, _FakeLU
43

    
44
from testsupport import *
45

    
46
import testutils
47

    
48

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

    
66

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
629
  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=123.123.123.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.LD_LV)
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, constants.LD_FILE,
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.LD_BLOCKDEV)
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.LD_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.LD_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.LD_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.LD_LV)
1212
        self.assertTrue(child.children is None)
1213

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

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

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

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

    
1234

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

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

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

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

    
1247

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

    
1252
  def SetDiskID(self, device, node_uuid):
1253
    assert isinstance(device, objects.Disk)
1254
    assert node_uuid == self._exp_node_uuid
1255

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

    
1260

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

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

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

    
1275

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

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

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

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

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

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

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

    
1299
    assert cur_progress == offset
1300

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

    
1304
    return (True, None)
1305

    
1306

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1403
    instance.WipeDisks(lu, inst)
1404

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

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

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

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

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

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

    
1445

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

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

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

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

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

    
1478

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

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

    
1489

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1582

    
1583
if __name__ == "__main__":
1584
  testutils.GanetiTestProgram()