e837f0f87ce86be528aacb9574e5376b720219c3
[ganeti-github.git] / test / py / cmdlib / instance_unittest.py
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2008, 2011, 2012, 2013 Google Inc.
5 # All rights reserved.
6 #
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions are
9 # met:
10 #
11 # 1. Redistributions of source code must retain the above copyright notice,
12 # this list of conditions and the following disclaimer.
13 #
14 # 2. Redistributions in binary form must reproduce the above copyright
15 # notice, this list of conditions and the following disclaimer in the
16 # documentation and/or other materials provided with the distribution.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
19 # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
20 # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
22 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23 # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26 # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30
31 """Tests for LUInstance*
32
33 """
34
35 import copy
36 import itertools
37 import re
38 import unittest
39 import mock
40 import operator
41 import os
42
43 from ganeti import backend
44 from ganeti import compat
45 from ganeti import config
46 from ganeti import constants
47 from ganeti import errors
48 from ganeti import ht
49 from ganeti import opcodes
50 from ganeti import objects
51 from ganeti.rpc import node as rpc
52 from ganeti import utils
53 from ganeti.cmdlib import instance
54 from ganeti.cmdlib import instance_storage
55 from ganeti.cmdlib import instance_create
56 from ganeti.cmdlib import instance_set_params
57 from ganeti.cmdlib import instance_utils
58
59 from cmdlib.cmdlib_unittest import _FakeLU
60
61 from testsupport import *
62
63 import testutils
64
65
66 class TestComputeIPolicyInstanceSpecViolation(unittest.TestCase):
67 def setUp(self):
68 self.ispec = {
69 constants.ISPEC_MEM_SIZE: 2048,
70 constants.ISPEC_CPU_COUNT: 2,
71 constants.ISPEC_DISK_COUNT: 1,
72 constants.ISPEC_DISK_SIZE: [512],
73 constants.ISPEC_NIC_COUNT: 0,
74 constants.ISPEC_SPINDLE_USE: 1,
75 }
76 self.stub = mock.MagicMock()
77 self.stub.return_value = []
78
79 def testPassThrough(self):
80 ret = instance_utils.ComputeIPolicyInstanceSpecViolation(
81 NotImplemented, self.ispec, [constants.DT_PLAIN], _compute_fn=self.stub)
82 self.assertEqual(ret, [])
83 self.stub.assert_called_with(NotImplemented, 2048, 2, 1, 0, [512],
84 1, [constants.DT_PLAIN])
85
86
87 class TestLUInstanceCreate(CmdlibTestCase):
88 def _setupOSDiagnose(self):
89 os_result = [(self.os.name,
90 self.os.path,
91 True,
92 "",
93 self.os.supported_variants,
94 self.os.supported_parameters,
95 self.os.api_versions,
96 True)]
97 self.rpc.call_os_diagnose.return_value = \
98 self.RpcResultsBuilder() \
99 .AddSuccessfulNode(self.master, os_result) \
100 .AddSuccessfulNode(self.node1, os_result) \
101 .AddSuccessfulNode(self.node2, os_result) \
102 .Build()
103
104 def setUp(self):
105 super(TestLUInstanceCreate, self).setUp()
106 self.ResetMocks()
107
108 self.MockOut(instance_create, 'netutils', self.netutils_mod)
109 self.MockOut(instance_utils, 'netutils', self.netutils_mod)
110
111 self.net = self.cfg.AddNewNetwork()
112 self.cfg.ConnectNetworkToGroup(self.net, self.group)
113
114 self.node1 = self.cfg.AddNewNode()
115 self.node2 = self.cfg.AddNewNode()
116
117 hv_info = ("bootid",
118 [{
119 "type": constants.ST_LVM_VG,
120 "storage_free": 10000
121 }],
122 ({"memory_free": 10000}, ))
123 self.rpc.call_node_info.return_value = \
124 self.RpcResultsBuilder() \
125 .AddSuccessfulNode(self.master, hv_info) \
126 .AddSuccessfulNode(self.node1, hv_info) \
127 .AddSuccessfulNode(self.node2, hv_info) \
128 .Build()
129
130 self._setupOSDiagnose()
131
132 self.rpc.call_blockdev_getmirrorstatus.side_effect = \
133 lambda node, _: self.RpcResultsBuilder() \
134 .CreateSuccessfulNodeResult(node, [])
135
136 self.iallocator_cls.return_value.result = [self.node1.name, self.node2.name]
137
138 self.diskless_op = opcodes.OpInstanceCreate(
139 instance_name="diskless.example.com",
140 pnode=self.master.name,
141 disk_template=constants.DT_DISKLESS,
142 mode=constants.INSTANCE_CREATE,
143 nics=[{}],
144 disks=[],
145 os_type=self.os_name_variant)
146
147 self.plain_op = opcodes.OpInstanceCreate(
148 instance_name="plain.example.com",
149 pnode=self.master.name,
150 disk_template=constants.DT_PLAIN,
151 mode=constants.INSTANCE_CREATE,
152 nics=[{}],
153 disks=[{
154 constants.IDISK_SIZE: 1024
155 }],
156 os_type=self.os_name_variant)
157
158 self.block_op = opcodes.OpInstanceCreate(
159 instance_name="block.example.com",
160 pnode=self.master.name,
161 disk_template=constants.DT_BLOCK,
162 mode=constants.INSTANCE_CREATE,
163 nics=[{}],
164 disks=[{
165 constants.IDISK_SIZE: 1024,
166 constants.IDISK_ADOPT: "/dev/disk/block0"
167 }],
168 os_type=self.os_name_variant)
169
170 self.drbd_op = opcodes.OpInstanceCreate(
171 instance_name="drbd.example.com",
172 pnode=self.node1.name,
173 snode=self.node2.name,
174 disk_template=constants.DT_DRBD8,
175 mode=constants.INSTANCE_CREATE,
176 nics=[{}],
177 disks=[{
178 constants.IDISK_SIZE: 1024
179 }],
180 os_type=self.os_name_variant)
181
182 self.file_op = opcodes.OpInstanceCreate(
183 instance_name="file.example.com",
184 pnode=self.node1.name,
185 disk_template=constants.DT_FILE,
186 mode=constants.INSTANCE_CREATE,
187 nics=[{}],
188 disks=[{
189 constants.IDISK_SIZE: 1024
190 }],
191 os_type=self.os_name_variant)
192
193 self.shared_file_op = opcodes.OpInstanceCreate(
194 instance_name="shared-file.example.com",
195 pnode=self.node1.name,
196 disk_template=constants.DT_SHARED_FILE,
197 mode=constants.INSTANCE_CREATE,
198 nics=[{}],
199 disks=[{
200 constants.IDISK_SIZE: 1024
201 }],
202 os_type=self.os_name_variant)
203
204 self.gluster_op = opcodes.OpInstanceCreate(
205 instance_name="gluster.example.com",
206 pnode=self.node1.name,
207 disk_template=constants.DT_GLUSTER,
208 mode=constants.INSTANCE_CREATE,
209 nics=[{}],
210 disks=[{
211 constants.IDISK_SIZE: 1024
212 }],
213 os_type=self.os_name_variant)
214
215 self.rbd_op = opcodes.OpInstanceCreate(
216 instance_name="gluster.example.com",
217 pnode=self.node1.name,
218 disk_template=constants.DT_RBD,
219 mode=constants.INSTANCE_CREATE,
220 nics=[{}],
221 disks=[{
222 constants.IDISK_SIZE: 1024
223 }],
224 os_type=self.os_name_variant)
225
226 def testSimpleCreate(self):
227 op = self.CopyOpCode(self.diskless_op)
228 self.ExecOpCode(op)
229
230 def testStrangeHostnameResolve(self):
231 op = self.CopyOpCode(self.diskless_op)
232 self.netutils_mod.GetHostname.return_value = \
233 HostnameMock("random.host.example.com", "203.0.113.1")
234 self.ExecOpCodeExpectOpPrereqError(
235 op, "Resolved hostname .* does not look the same as given hostname")
236
237 def testOpportunisticLockingNoIAllocator(self):
238 op = self.CopyOpCode(self.diskless_op,
239 opportunistic_locking=True,
240 iallocator=None)
241 self.ExecOpCodeExpectOpPrereqError(
242 op, "Opportunistic locking is only available in combination with an"
243 " instance allocator")
244
245 def testNicWithNetAndMode(self):
246 op = self.CopyOpCode(self.diskless_op,
247 nics=[{
248 constants.INIC_NETWORK: self.net.name,
249 constants.INIC_MODE: constants.NIC_MODE_BRIDGED
250 }])
251 self.ExecOpCodeExpectOpPrereqError(
252 op, "If network is given, no mode or link is allowed to be passed")
253
254 def testAutoIpNoNameCheck(self):
255 op = self.CopyOpCode(self.diskless_op,
256 nics=[{
257 constants.INIC_IP: constants.VALUE_AUTO
258 }],
259 ip_check=False,
260 name_check=False)
261 self.ExecOpCodeExpectOpPrereqError(
262 op, "IP address set to auto but name checks have been skipped")
263
264 def testAutoIp(self):
265 op = self.CopyOpCode(self.diskless_op,
266 nics=[{
267 constants.INIC_IP: constants.VALUE_AUTO
268 }])
269 self.ExecOpCode(op)
270
271 def testPoolIpNoNetwork(self):
272 op = self.CopyOpCode(self.diskless_op,
273 nics=[{
274 constants.INIC_IP: constants.NIC_IP_POOL
275 }])
276 self.ExecOpCodeExpectOpPrereqError(
277 op, "if ip=pool, parameter network must be passed too")
278
279 def testValidIp(self):
280 op = self.CopyOpCode(self.diskless_op,
281 nics=[{
282 constants.INIC_IP: "203.0.113.1"
283 }])
284 self.ExecOpCode(op)
285
286 def testRoutedNoIp(self):
287 op = self.CopyOpCode(self.diskless_op,
288 nics=[{
289 constants.INIC_NETWORK: constants.VALUE_NONE,
290 constants.INIC_MODE: constants.NIC_MODE_ROUTED
291 }])
292 self.ExecOpCodeExpectOpPrereqError(
293 op, "Routed nic mode requires an ip address"
294 " if not attached to a network")
295
296 def testValicMac(self):
297 op = self.CopyOpCode(self.diskless_op,
298 nics=[{
299 constants.INIC_MAC: "f0:df:f4:a3:d1:cf"
300 }])
301 self.ExecOpCode(op)
302
303 def testValidNicParams(self):
304 op = self.CopyOpCode(self.diskless_op,
305 nics=[{
306 constants.INIC_MODE: constants.NIC_MODE_BRIDGED,
307 constants.INIC_LINK: "br_mock"
308 }])
309 self.ExecOpCode(op)
310
311 def testValidNicParamsOpenVSwitch(self):
312 op = self.CopyOpCode(self.diskless_op,
313 nics=[{
314 constants.INIC_MODE: constants.NIC_MODE_OVS,
315 constants.INIC_VLAN: "1"
316 }])
317 self.ExecOpCode(op)
318
319 def testNicNoneName(self):
320 op = self.CopyOpCode(self.diskless_op,
321 nics=[{
322 constants.INIC_NAME: constants.VALUE_NONE
323 }])
324 self.ExecOpCode(op)
325
326 def testConflictingIP(self):
327 op = self.CopyOpCode(self.diskless_op,
328 nics=[{
329 constants.INIC_IP: self.net.gateway[:-1] + "2"
330 }])
331 self.ExecOpCodeExpectOpPrereqError(
332 op, "The requested IP address .* belongs to network .*, but the target"
333 " NIC does not.")
334
335 def testVLanFormat(self):
336 for vlan in [".pinky", ":bunny", ":1:pinky", "bunny"]:
337 self.ResetMocks()
338 op = self.CopyOpCode(self.diskless_op,
339 nics=[{
340 constants.INIC_VLAN: vlan
341 }])
342 self.ExecOpCodeExpectOpPrereqError(
343 op, "Specified VLAN parameter is invalid")
344
345 def testPoolIp(self):
346 op = self.CopyOpCode(self.diskless_op,
347 nics=[{
348 constants.INIC_IP: constants.NIC_IP_POOL,
349 constants.INIC_NETWORK: self.net.name
350 }])
351 self.ExecOpCode(op)
352
353 def testPoolIpUnconnectedNetwork(self):
354 net = self.cfg.AddNewNetwork()
355 op = self.CopyOpCode(self.diskless_op,
356 nics=[{
357 constants.INIC_IP: constants.NIC_IP_POOL,
358 constants.INIC_NETWORK: net.name
359 }])
360 self.ExecOpCodeExpectOpPrereqError(
361 op, "No netparams found for network .*.")
362
363 def testIpNotInNetwork(self):
364 op = self.CopyOpCode(self.diskless_op,
365 nics=[{
366 constants.INIC_IP: "203.0.113.1",
367 constants.INIC_NETWORK: self.net.name
368 }])
369 self.ExecOpCodeExpectOpPrereqError(
370 op, "IP address .* already in use or does not belong to network .*")
371
372 def testMixAdoptAndNotAdopt(self):
373 op = self.CopyOpCode(self.diskless_op,
374 disk_template=constants.DT_PLAIN,
375 disks=[{
376 constants.IDISK_ADOPT: "lv1"
377 }, {}])
378 self.ExecOpCodeExpectOpPrereqError(
379 op, "Either all disks are adopted or none is")
380
381 def testMustAdoptWithoutAdopt(self):
382 op = self.CopyOpCode(self.diskless_op,
383 disk_template=constants.DT_BLOCK,
384 disks=[{}])
385 self.ExecOpCodeExpectOpPrereqError(
386 op, "Disk template blockdev requires disk adoption, but no 'adopt'"
387 " parameter given")
388
389 def testDontAdoptWithAdopt(self):
390 op = self.CopyOpCode(self.diskless_op,
391 disk_template=constants.DT_DRBD8,
392 disks=[{
393 constants.IDISK_ADOPT: "lv1"
394 }])
395 self.ExecOpCodeExpectOpPrereqError(
396 op, "Disk adoption is not supported for the 'drbd' disk template")
397
398 def testAdoptWithIAllocator(self):
399 op = self.CopyOpCode(self.diskless_op,
400 disk_template=constants.DT_PLAIN,
401 disks=[{
402 constants.IDISK_ADOPT: "lv1"
403 }],
404 iallocator="mock")
405 self.ExecOpCodeExpectOpPrereqError(
406 op, "Disk adoption not allowed with an iallocator script")
407
408 def testAdoptWithImport(self):
409 op = self.CopyOpCode(self.diskless_op,
410 disk_template=constants.DT_PLAIN,
411 disks=[{
412 constants.IDISK_ADOPT: "lv1"
413 }],
414 mode=constants.INSTANCE_IMPORT)
415 self.ExecOpCodeExpectOpPrereqError(
416 op, "Disk adoption not allowed for instance import")
417
418 def testArgumentCombinations(self):
419 op = self.CopyOpCode(self.diskless_op,
420 # start flag will be flipped
421 no_install=True,
422 start=True,
423 # no allowed combination
424 ip_check=True,
425 name_check=False)
426 self.ExecOpCodeExpectOpPrereqError(
427 op, "Cannot do IP address check without a name check")
428
429 def testInvalidFileDriver(self):
430 op = self.CopyOpCode(self.diskless_op,
431 file_driver="invalid_file_driver")
432 self.ExecOpCodeExpectOpPrereqError(
433 op, "Parameter 'OP_INSTANCE_CREATE.file_driver' fails validation")
434
435 def testMissingSecondaryNode(self):
436 op = self.CopyOpCode(self.diskless_op,
437 pnode=self.master.name,
438 disk_template=constants.DT_DRBD8)
439 self.ExecOpCodeExpectOpPrereqError(
440 op, "The networked disk templates need a mirror node")
441
442 def testIgnoredSecondaryNode(self):
443 op = self.CopyOpCode(self.diskless_op,
444 pnode=self.master.name,
445 snode=self.node1.name,
446 disk_template=constants.DT_PLAIN)
447 try:
448 self.ExecOpCode(op)
449 except Exception:
450 pass
451 self.mcpu.assertLogContainsRegex(
452 "Secondary node will be ignored on non-mirrored disk template")
453
454 def testMissingOsType(self):
455 op = self.CopyOpCode(self.diskless_op,
456 os_type=self.REMOVE)
457 self.ExecOpCodeExpectOpPrereqError(op, "No guest OS or OS image specified")
458
459 def testBlacklistedOs(self):
460 self.cluster.blacklisted_os = [self.os_name_variant]
461 op = self.CopyOpCode(self.diskless_op)
462 self.ExecOpCodeExpectOpPrereqError(
463 op, "Guest OS .* is not allowed for installation")
464
465 def testMissingDiskTemplate(self):
466 self.cluster.enabled_disk_templates = [constants.DT_DISKLESS]
467 op = self.CopyOpCode(self.diskless_op,
468 disk_template=self.REMOVE)
469 self.ExecOpCode(op)
470
471 def testExistingInstance(self):
472 inst = self.cfg.AddNewInstance()
473 op = self.CopyOpCode(self.diskless_op,
474 instance_name=inst.name)
475 self.ExecOpCodeExpectOpPrereqError(
476 op, "Instance .* is already in the cluster")
477
478 def testPlainInstance(self):
479 op = self.CopyOpCode(self.plain_op)
480 self.ExecOpCode(op)
481
482 def testPlainIAllocator(self):
483 op = self.CopyOpCode(self.plain_op,
484 pnode=self.REMOVE,
485 iallocator="mock")
486 self.ExecOpCode(op)
487
488 def testIAllocatorOpportunisticLocking(self):
489 op = self.CopyOpCode(self.plain_op,
490 pnode=self.REMOVE,
491 iallocator="mock",
492 opportunistic_locking=True)
493 self.ExecOpCode(op)
494
495 def testFailingIAllocator(self):
496 self.iallocator_cls.return_value.success = False
497 op = self.CopyOpCode(self.plain_op,
498 pnode=self.REMOVE,
499 iallocator="mock")
500 self.ExecOpCodeExpectOpPrereqError(
501 op, "Can't compute nodes using iallocator")
502
503 def testDrbdInstance(self):
504 op = self.CopyOpCode(self.drbd_op)
505 self.ExecOpCode(op)
506
507 def testDrbdIAllocator(self):
508 op = self.CopyOpCode(self.drbd_op,
509 pnode=self.REMOVE,
510 snode=self.REMOVE,
511 iallocator="mock")
512 self.ExecOpCode(op)
513
514 def testFileInstance(self):
515 op = self.CopyOpCode(self.file_op)
516 self.ExecOpCode(op)
517
518 def testFileInstanceNoClusterStorage(self):
519 self.cluster.file_storage_dir = None
520 op = self.CopyOpCode(self.file_op)
521 self.ExecOpCodeExpectOpPrereqError(
522 op, "Cluster file storage dir for 'file' storage type not defined")
523
524 def testFileInstanceAdditionalPath(self):
525 op = self.CopyOpCode(self.file_op,
526 file_storage_dir="mock_dir")
527 self.ExecOpCode(op)
528
529 def testIdentifyDefaults(self):
530 op = self.CopyOpCode(self.plain_op,
531 hvparams={
532 constants.HV_BOOT_ORDER: "cd"
533 },
534 beparams=constants.BEC_DEFAULTS.copy(),
535 nics=[{
536 constants.NIC_MODE: constants.NIC_MODE_BRIDGED
537 }],
538 osparams={
539 self.os_name_variant: {}
540 },
541 osparams_private={},
542 identify_defaults=True)
543 self.ExecOpCode(op)
544
545 inst = self.cfg.GetAllInstancesInfo().values()[0]
546 self.assertEqual(0, len(inst.hvparams))
547 self.assertEqual(0, len(inst.beparams))
548 assert self.os_name_variant not in inst.osparams or \
549 len(inst.osparams[self.os_name_variant]) == 0
550
551 def testOfflineNode(self):
552 self.node1.offline = True
553 op = self.CopyOpCode(self.diskless_op,
554 pnode=self.node1.name)
555 self.ExecOpCodeExpectOpPrereqError(op, "Cannot use offline primary node")
556
557 def testDrainedNode(self):
558 self.node1.drained = True
559 op = self.CopyOpCode(self.diskless_op,
560 pnode=self.node1.name)
561 self.ExecOpCodeExpectOpPrereqError(op, "Cannot use drained primary node")
562
563 def testNonVmCapableNode(self):
564 self.node1.vm_capable = False
565 op = self.CopyOpCode(self.diskless_op,
566 pnode=self.node1.name)
567 self.ExecOpCodeExpectOpPrereqError(
568 op, "Cannot use non-vm_capable primary node")
569
570 def testNonEnabledHypervisor(self):
571 self.cluster.enabled_hypervisors = [constants.HT_XEN_HVM]
572 op = self.CopyOpCode(self.diskless_op,
573 hypervisor=constants.HT_FAKE)
574 self.ExecOpCodeExpectOpPrereqError(
575 op, "Selected hypervisor .* not enabled in the cluster")
576
577 def testAddTag(self):
578 op = self.CopyOpCode(self.diskless_op,
579 tags=["tag"])
580 self.ExecOpCode(op)
581
582 def testInvalidTag(self):
583 op = self.CopyOpCode(self.diskless_op,
584 tags=["too_long" * 20])
585 self.ExecOpCodeExpectException(op, errors.TagError, "Tag too long")
586
587 def testPingableInstanceName(self):
588 self.netutils_mod.TcpPing.return_value = True
589 op = self.CopyOpCode(self.diskless_op)
590 self.ExecOpCodeExpectOpPrereqError(
591 op, "IP .* of instance diskless.example.com already in use")
592
593 def testPrimaryIsSecondaryNode(self):
594 op = self.CopyOpCode(self.drbd_op,
595 snode=self.drbd_op.pnode)
596 self.ExecOpCodeExpectOpPrereqError(
597 op, "The secondary node cannot be the primary node")
598
599 def testPrimarySecondaryDifferentNodeGroups(self):
600 group = self.cfg.AddNewNodeGroup()
601 self.node2.group = group.uuid
602 op = self.CopyOpCode(self.drbd_op)
603 self.ExecOpCode(op)
604 self.mcpu.assertLogContainsRegex(
605 "The primary and secondary nodes are in two different node groups")
606
607 def testExclusiveStorageUnsupportedDiskTemplate(self):
608 self.node1.ndparams[constants.ND_EXCLUSIVE_STORAGE] = True
609 op = self.CopyOpCode(self.drbd_op)
610 self.ExecOpCodeExpectOpPrereqError(
611 op, "Disk template drbd not supported with exclusive storage")
612
613 def testAdoptPlain(self):
614 self.rpc.call_lv_list.return_value = \
615 self.RpcResultsBuilder() \
616 .AddSuccessfulNode(self.master, {
617 "xenvg/mock_disk_1": (10000, None, False)
618 }) \
619 .Build()
620 op = self.CopyOpCode(self.plain_op)
621 op.disks[0].update({constants.IDISK_ADOPT: "mock_disk_1"})
622 self.ExecOpCode(op)
623
624 def testAdoptPlainMissingLv(self):
625 self.rpc.call_lv_list.return_value = \
626 self.RpcResultsBuilder() \
627 .AddSuccessfulNode(self.master, {}) \
628 .Build()
629 op = self.CopyOpCode(self.plain_op)
630 op.disks[0].update({constants.IDISK_ADOPT: "mock_disk_1"})
631 self.ExecOpCodeExpectOpPrereqError(op, "Missing logical volume")
632
633 def testAdoptPlainOnlineLv(self):
634 self.rpc.call_lv_list.return_value = \
635 self.RpcResultsBuilder() \
636 .AddSuccessfulNode(self.master, {
637 "xenvg/mock_disk_1": (10000, None, True)
638 }) \
639 .Build()
640 op = self.CopyOpCode(self.plain_op)
641 op.disks[0].update({constants.IDISK_ADOPT: "mock_disk_1"})
642 self.ExecOpCodeExpectOpPrereqError(
643 op, "Online logical volumes found, cannot adopt")
644
645 def testAdoptBlock(self):
646 self.rpc.call_bdev_sizes.return_value = \
647 self.RpcResultsBuilder() \
648 .AddSuccessfulNode(self.master, {
649 "/dev/disk/block0": 10000
650 }) \
651 .Build()
652 op = self.CopyOpCode(self.block_op)
653 self.ExecOpCode(op)
654
655 def testAdoptBlockDuplicateNames(self):
656 op = self.CopyOpCode(self.block_op,
657 disks=[{
658 constants.IDISK_SIZE: 0,
659 constants.IDISK_ADOPT: "/dev/disk/block0"
660 }, {
661 constants.IDISK_SIZE: 0,
662 constants.IDISK_ADOPT: "/dev/disk/block0"
663 }])
664 self.ExecOpCodeExpectOpPrereqError(
665 op, "Duplicate disk names given for adoption")
666
667 def testAdoptBlockInvalidNames(self):
668 op = self.CopyOpCode(self.block_op,
669 disks=[{
670 constants.IDISK_SIZE: 0,
671 constants.IDISK_ADOPT: "/invalid/block0"
672 }])
673 self.ExecOpCodeExpectOpPrereqError(
674 op, "Device node.* lie outside .* and cannot be adopted")
675
676 def testAdoptBlockMissingDisk(self):
677 self.rpc.call_bdev_sizes.return_value = \
678 self.RpcResultsBuilder() \
679 .AddSuccessfulNode(self.master, {}) \
680 .Build()
681 op = self.CopyOpCode(self.block_op)
682 self.ExecOpCodeExpectOpPrereqError(op, "Missing block device")
683
684 def testNoWaitForSyncDrbd(self):
685 op = self.CopyOpCode(self.drbd_op,
686 wait_for_sync=False)
687 self.ExecOpCode(op)
688
689 def testNoWaitForSyncPlain(self):
690 op = self.CopyOpCode(self.plain_op,
691 wait_for_sync=False)
692 self.ExecOpCode(op)
693
694 def testImportPlainFromGivenSrcNode(self):
695 exp_info = """
696 [export]
697 version=0
698 os=%s
699 [instance]
700 name=old_name.example.com
701 """ % self.os.name
702
703 self.rpc.call_export_info.return_value = \
704 self.RpcResultsBuilder() \
705 .CreateSuccessfulNodeResult(self.master, exp_info)
706 op = self.CopyOpCode(self.plain_op,
707 mode=constants.INSTANCE_IMPORT,
708 src_node=self.master.name)
709 self.ExecOpCode(op)
710
711 def testImportPlainWithoutSrcNodeNotFound(self):
712 op = self.CopyOpCode(self.plain_op,
713 mode=constants.INSTANCE_IMPORT)
714 self.ExecOpCodeExpectOpPrereqError(
715 op, "No export found for relative path")
716
717 def testImportPlainWithoutSrcNode(self):
718 exp_info = """
719 [export]
720 version=0
721 os=%s
722 [instance]
723 name=old_name.example.com
724 """ % self.os.name
725
726 self.rpc.call_export_list.return_value = \
727 self.RpcResultsBuilder() \
728 .AddSuccessfulNode(self.master, {"mock_path": {}}) \
729 .Build()
730 self.rpc.call_export_info.return_value = \
731 self.RpcResultsBuilder() \
732 .CreateSuccessfulNodeResult(self.master, exp_info)
733
734 op = self.CopyOpCode(self.plain_op,
735 mode=constants.INSTANCE_IMPORT,
736 src_path="mock_path")
737 self.ExecOpCode(op)
738
739 def testImportPlainCorruptExportInfo(self):
740 exp_info = ""
741 self.rpc.call_export_info.return_value = \
742 self.RpcResultsBuilder() \
743 .CreateSuccessfulNodeResult(self.master, exp_info)
744 op = self.CopyOpCode(self.plain_op,
745 mode=constants.INSTANCE_IMPORT,
746 src_node=self.master.name)
747 self.ExecOpCodeExpectException(op, errors.ProgrammerError,
748 "Corrupted export config")
749
750 def testImportPlainWrongExportInfoVersion(self):
751 exp_info = """
752 [export]
753 version=1
754 """
755 self.rpc.call_export_info.return_value = \
756 self.RpcResultsBuilder() \
757 .CreateSuccessfulNodeResult(self.master, exp_info)
758 op = self.CopyOpCode(self.plain_op,
759 mode=constants.INSTANCE_IMPORT,
760 src_node=self.master.name)
761 self.ExecOpCodeExpectOpPrereqError(op, "Wrong export version")
762
763 def testImportPlainWithParametersAndImport(self):
764 exp_info = """
765 [export]
766 version=0
767 os=%s
768 [instance]
769 name=old_name.example.com
770 disk0_size=1024
771 disk1_size=1500
772 disk1_dump=mock_path
773 nic0_mode=bridged
774 nic0_link=br_mock
775 nic0_mac=f6:ab:f4:45:d1:af
776 nic0_ip=192.0.2.1
777 tags=tag1 tag2
778 hypervisor=xen-hvm
779 [hypervisor]
780 boot_order=cd
781 [backend]
782 memory=1024
783 vcpus=8
784 [os]
785 param1=val1
786 """ % self.os.name
787
788 self.rpc.call_export_info.return_value = \
789 self.RpcResultsBuilder() \
790 .CreateSuccessfulNodeResult(self.master, exp_info)
791 self.rpc.call_import_start.return_value = \
792 self.RpcResultsBuilder() \
793 .CreateSuccessfulNodeResult(self.master, "daemon_name")
794 self.rpc.call_impexp_status.return_value = \
795 self.RpcResultsBuilder() \
796 .CreateSuccessfulNodeResult(self.master,
797 [
798 objects.ImportExportStatus(exit_status=0)
799 ])
800 self.rpc.call_impexp_cleanup.return_value = \
801 self.RpcResultsBuilder() \
802 .CreateSuccessfulNodeResult(self.master, True)
803
804 op = self.CopyOpCode(self.plain_op,
805 disks=[],
806 nics=[],
807 tags=[],
808 hypervisor=None,
809 hvparams={},
810 mode=constants.INSTANCE_IMPORT,
811 src_node=self.master.name)
812 self.ExecOpCode(op)
813
814
815 class TestDiskTemplateDiskTypeBijection(TestLUInstanceCreate):
816 """Tests that one disk template corresponds to exactly one disk type."""
817
818 def GetSingleInstance(self):
819 instances = self.cfg.GetInstancesInfoByFilter(lambda _: True)
820 self.assertEqual(len(instances), 1,
821 "Expected 1 instance, got\n%s" % instances)
822 return instances.values()[0]
823
824 def testDiskTemplateLogicalIdBijectionDiskless(self):
825 op = self.CopyOpCode(self.diskless_op)
826 self.ExecOpCode(op)
827 instance = self.GetSingleInstance()
828 self.assertEqual(instance.disk_template, constants.DT_DISKLESS)
829 self.assertEqual(instance.disks, [])
830
831 def testDiskTemplateLogicalIdBijectionPlain(self):
832 op = self.CopyOpCode(self.plain_op)
833 self.ExecOpCode(op)
834 instance = self.GetSingleInstance()
835 self.assertEqual(instance.disk_template, constants.DT_PLAIN)
836 disks = self.cfg.GetInstanceDisks(instance.uuid)
837 self.assertEqual(disks[0].dev_type, constants.DT_PLAIN)
838
839 def testDiskTemplateLogicalIdBijectionBlock(self):
840 self.rpc.call_bdev_sizes.return_value = \
841 self.RpcResultsBuilder() \
842 .AddSuccessfulNode(self.master, {
843 "/dev/disk/block0": 10000
844 }) \
845 .Build()
846 op = self.CopyOpCode(self.block_op)
847 self.ExecOpCode(op)
848 instance = self.GetSingleInstance()
849 self.assertEqual(instance.disk_template, constants.DT_BLOCK)
850 disks = self.cfg.GetInstanceDisks(instance.uuid)
851 self.assertEqual(disks[0].dev_type, constants.DT_BLOCK)
852
853 def testDiskTemplateLogicalIdBijectionDrbd(self):
854 op = self.CopyOpCode(self.drbd_op)
855 self.ExecOpCode(op)
856 instance = self.GetSingleInstance()
857 self.assertEqual(instance.disk_template, constants.DT_DRBD8)
858 disks = self.cfg.GetInstanceDisks(instance.uuid)
859 self.assertEqual(disks[0].dev_type, constants.DT_DRBD8)
860
861 def testDiskTemplateLogicalIdBijectionFile(self):
862 op = self.CopyOpCode(self.file_op)
863 self.ExecOpCode(op)
864 instance = self.GetSingleInstance()
865 self.assertEqual(instance.disk_template, constants.DT_FILE)
866 disks = self.cfg.GetInstanceDisks(instance.uuid)
867 self.assertEqual(disks[0].dev_type, constants.DT_FILE)
868
869 def testDiskTemplateLogicalIdBijectionSharedFile(self):
870 self.cluster.shared_file_storage_dir = '/tmp'
871 op = self.CopyOpCode(self.shared_file_op)
872 self.ExecOpCode(op)
873 instance = self.GetSingleInstance()
874 self.assertEqual(instance.disk_template, constants.DT_SHARED_FILE)
875 disks = self.cfg.GetInstanceDisks(instance.uuid)
876 self.assertEqual(disks[0].dev_type, constants.DT_SHARED_FILE)
877
878 def testDiskTemplateLogicalIdBijectionGluster(self):
879 self.cluster.gluster_storage_dir = '/tmp'
880 op = self.CopyOpCode(self.gluster_op)
881 self.ExecOpCode(op)
882 instance = self.GetSingleInstance()
883 self.assertEqual(instance.disk_template, constants.DT_GLUSTER)
884 disks = self.cfg.GetInstanceDisks(instance.uuid)
885 self.assertEqual(disks[0].dev_type, constants.DT_GLUSTER)
886
887 def testDiskTemplateLogicalIdBijectionRbd(self):
888 op = self.CopyOpCode(self.rbd_op)
889 self.ExecOpCode(op)
890 instance = self.GetSingleInstance()
891 self.assertEqual(instance.disk_template, constants.DT_RBD)
892 disks = self.cfg.GetInstanceDisks(instance.uuid)
893 self.assertEqual(disks[0].dev_type, constants.DT_RBD)
894
895
896 class TestCheckOSVariant(CmdlibTestCase):
897 def testNoVariantsSupported(self):
898 os = self.cfg.CreateOs(supported_variants=[])
899 self.assertRaises(backend.RPCFail, backend._CheckOSVariant,
900 os, "os+variant")
901
902 def testNoVariantGiven(self):
903 os = self.cfg.CreateOs(supported_variants=["default"])
904 self.assertRaises(backend.RPCFail, backend._CheckOSVariant,
905 os, "os")
906
907 def testWrongVariantGiven(self):
908 os = self.cfg.CreateOs(supported_variants=["default"])
909 self.assertRaises(backend.RPCFail, backend._CheckOSVariant,
910 os, "os+wrong_variant")
911
912 def testOkWithVariant(self):
913 os = self.cfg.CreateOs(supported_variants=["default"])
914 backend._CheckOSVariant(os, "os+default")
915
916 def testOkWithoutVariant(self):
917 os = self.cfg.CreateOs(supported_variants=[])
918 backend._CheckOSVariant(os, "os")
919
920
921 class TestCheckTargetNodeIPolicy(TestLUInstanceCreate):
922 def setUp(self):
923 super(TestCheckTargetNodeIPolicy, self).setUp()
924
925 self.op = self.diskless_op
926
927 self.instance = self.cfg.AddNewInstance()
928 self.target_group = self.cfg.AddNewNodeGroup()
929 self.target_node = self.cfg.AddNewNode(group=self.target_group)
930
931 @withLockedLU
932 def testNoViolation(self, lu):
933 compute_recoder = mock.Mock(return_value=[])
934 instance.CheckTargetNodeIPolicy(lu, NotImplemented, self.instance,
935 self.target_node, NotImplemented,
936 _compute_fn=compute_recoder)
937 self.assertTrue(compute_recoder.called)
938 self.mcpu.assertLogIsEmpty()
939
940 @withLockedLU
941 def testNoIgnore(self, lu):
942 compute_recoder = mock.Mock(return_value=["mem_size not in range"])
943 self.assertRaises(errors.OpPrereqError, instance.CheckTargetNodeIPolicy,
944 lu, NotImplemented, self.instance,
945 self.target_node, NotImplemented,
946 _compute_fn=compute_recoder)
947 self.assertTrue(compute_recoder.called)
948 self.mcpu.assertLogIsEmpty()
949
950 @withLockedLU
951 def testIgnoreViolation(self, lu):
952 compute_recoder = mock.Mock(return_value=["mem_size not in range"])
953 instance.CheckTargetNodeIPolicy(lu, NotImplemented, self.instance,
954 self.target_node, NotImplemented,
955 ignore=True, _compute_fn=compute_recoder)
956 self.assertTrue(compute_recoder.called)
957 msg = ("Instance does not meet target node group's .* instance policy:"
958 " mem_size not in range")
959 self.mcpu.assertLogContainsRegex(msg)
960
961
962 class TestIndexOperations(unittest.TestCase):
963
964 """Test if index operations on containers work as expected."""
965
966 def testGetIndexFromIdentifierTail(self):
967 """Check if -1 is translated to tail index."""
968 container = ['item1134']
969
970 idx = instance_utils.GetIndexFromIdentifier("-1", "test", container)
971 self.assertEqual(1, idx)
972
973 def testGetIndexFromIdentifierEmpty(self):
974 """Check if empty containers return 0 as index."""
975 container = []
976
977 idx = instance_utils.GetIndexFromIdentifier("0", "test", container)
978 self.assertEqual(0, idx)
979 idx = instance_utils.GetIndexFromIdentifier("-1", "test", container)
980 self.assertEqual(0, idx)
981
982 def testGetIndexFromIdentifierError(self):
983 """Check if wrong input raises an exception."""
984 container = []
985
986 self.assertRaises(errors.OpPrereqError,
987 instance_utils.GetIndexFromIdentifier,
988 "lala", "test", container)
989
990 def testGetIndexFromIdentifierOffByOne(self):
991 """Check for off-by-one errors."""
992 container = []
993
994 self.assertRaises(IndexError, instance_utils.GetIndexFromIdentifier,
995 "1", "test", container)
996
997 def testGetIndexFromIdentifierOutOfRange(self):
998 """Check for identifiers out of the container range."""
999 container = []
1000
1001 self.assertRaises(IndexError, instance_utils.GetIndexFromIdentifier,
1002 "-1134", "test", container)
1003 self.assertRaises(IndexError, instance_utils.GetIndexFromIdentifier,
1004 "1134", "test", container)
1005
1006 def testInsertItemtoIndex(self):
1007 """Test if we can insert an item to a container at a specified index."""
1008 container = []
1009
1010 instance_utils.InsertItemToIndex(0, 2, container)
1011 self.assertEqual([2], container)
1012
1013 instance_utils.InsertItemToIndex(0, 1, container)
1014 self.assertEqual([1, 2], container)
1015
1016 instance_utils.InsertItemToIndex(-1, 3, container)
1017 self.assertEqual([1, 2, 3], container)
1018
1019 self.assertRaises(AssertionError, instance_utils.InsertItemToIndex, -2,
1020 1134, container)
1021
1022 self.assertRaises(AssertionError, instance_utils.InsertItemToIndex, 4, 1134,
1023 container)
1024
1025
1026 class TestApplyContainerMods(unittest.TestCase):
1027
1028 def applyAndAssert(self, container, inp, expected_container,
1029 expected_chgdesc=[]):
1030 """Apply a list of changes to a container and check the container state
1031
1032 Parameters:
1033 @type container: List
1034 @param container: The container on which we will apply the changes
1035 @type inp: List<(action, index, object)>
1036 @param inp: The list of changes, a tupple with three elements:
1037 i. action, e.g. constants.DDM_ADD
1038 ii. index, e.g. -1, 0, 10
1039 iii. object (any type)
1040 @type expected: List
1041 @param expected: The expected state of the container
1042 @type chgdesc: List
1043 @param chgdesc: List of applied changes
1044
1045
1046 """
1047 chgdesc = []
1048 mods = instance_utils.PrepareContainerMods(inp, None)
1049 instance_utils.ApplyContainerMods("test", container, chgdesc, mods,
1050 None, None, None, None, None)
1051 self.assertEqual(container, expected_container)
1052 self.assertEqual(chgdesc, expected_chgdesc)
1053
1054 def _insertContainerSuccessFn(self, op):
1055 container = []
1056 inp = [(op, -1, "Hello"),
1057 (op, -1, "World"),
1058 (op, 0, "Start"),
1059 (op, -1, "End"),
1060 ]
1061 expected = ["Start", "Hello", "World", "End"]
1062 self.applyAndAssert(container, inp, expected)
1063
1064 inp = [(op, 0, "zero"),
1065 (op, 3, "Added"),
1066 (op, 5, "four"),
1067 (op, 7, "xyz"),
1068 ]
1069 expected = ["zero", "Start", "Hello", "Added", "World", "four", "End",
1070 "xyz"]
1071 self.applyAndAssert(container, inp, expected)
1072
1073 def _insertContainerErrorFn(self, op):
1074 container = []
1075 expected = None
1076
1077 inp = [(op, 1, "error"), ]
1078 self.assertRaises(IndexError, self.applyAndAssert, container, inp,
1079 expected)
1080
1081 inp = [(op, -2, "error"), ]
1082 self.assertRaises(IndexError, self.applyAndAssert, container, inp,
1083 expected)
1084
1085 def _extractContainerSuccessFn(self, op):
1086 container = ["item1", "item2", "item3", "item4", "item5"]
1087 inp = [(op, -1, None),
1088 (op, -0, None),
1089 (op, 1, None),
1090 ]
1091 expected = ["item2", "item4"]
1092 chgdesc = [('test/4', op),
1093 ('test/0', op),
1094 ('test/1', op)
1095 ]
1096 self.applyAndAssert(container, inp, expected, chgdesc)
1097
1098 def _extractContainerErrorFn(self, op):
1099 container = []
1100 expected = None
1101
1102 inp = [(op, 0, None), ]
1103 self.assertRaises(IndexError, self.applyAndAssert, container, inp,
1104 expected)
1105
1106 inp = [(op, -1, None), ]
1107 self.assertRaises(IndexError, self.applyAndAssert, container, inp,
1108 expected)
1109
1110 inp = [(op, 2, None), ]
1111 self.assertRaises(IndexError, self.applyAndAssert, container, inp,
1112 expected)
1113 container = [""]
1114 inp = [(op, 0, None), ]
1115 expected = None
1116 self.assertRaises(AssertionError, self.applyAndAssert, container, inp,
1117 expected)
1118
1119 def testEmptyContainer(self):
1120 container = []
1121 chgdesc = []
1122 instance_utils.ApplyContainerMods("test", container, chgdesc, [], None,
1123 None, None, None, None)
1124 self.assertEqual(container, [])
1125 self.assertEqual(chgdesc, [])
1126
1127 def testAddSuccess(self):
1128 self._insertContainerSuccessFn(constants.DDM_ADD)
1129
1130 def testAddError(self):
1131 self._insertContainerErrorFn(constants.DDM_ADD)
1132
1133 def testAttachSuccess(self):
1134 self._insertContainerSuccessFn(constants.DDM_ATTACH)
1135
1136 def testAttachError(self):
1137 self._insertContainerErrorFn(constants.DDM_ATTACH)
1138
1139 def testRemoveSuccess(self):
1140 self._extractContainerSuccessFn(constants.DDM_REMOVE)
1141
1142 def testRemoveError(self):
1143 self._extractContainerErrorFn(constants.DDM_REMOVE)
1144
1145 def testDetachSuccess(self):
1146 self._extractContainerSuccessFn(constants.DDM_DETACH)
1147
1148 def testDetachError(self):
1149 self._extractContainerErrorFn(constants.DDM_DETACH)
1150
1151 def testModify(self):
1152 container = ["item 1", "item 2"]
1153 mods = instance_utils.PrepareContainerMods([
1154 (constants.DDM_MODIFY, -1, "a"),
1155 (constants.DDM_MODIFY, 0, "b"),
1156 (constants.DDM_MODIFY, 1, "c"),
1157 ], None)
1158 chgdesc = []
1159 instance_utils.ApplyContainerMods("test", container, chgdesc, mods,
1160 None, None, None, None, None)
1161 self.assertEqual(container, ["item 1", "item 2"])
1162 self.assertEqual(chgdesc, [])
1163
1164 for idx in [-2, len(container) + 1]:
1165 mods = instance_utils.PrepareContainerMods([
1166 (constants.DDM_MODIFY, idx, "error"),
1167 ], None)
1168 self.assertRaises(IndexError, instance_utils.ApplyContainerMods,
1169 "test", container, None, mods, None, None, None, None,
1170 None)
1171
1172 @staticmethod
1173 def _CreateTestFn(idx, params, private):
1174 private.data = ("add", idx, params)
1175 return ((100 * idx, params), [
1176 ("test/%s" % idx, hex(idx)),
1177 ])
1178
1179 @staticmethod
1180 def _AttachTestFn(idx, params, private):
1181 private.data = ("attach", idx, params)
1182 return ((100 * idx, params), [
1183 ("test/%s" % idx, hex(idx)),
1184 ])
1185
1186 @staticmethod
1187 def _ModifyTestFn(idx, item, params, private):
1188 private.data = ("modify", idx, params)
1189 return [
1190 ("test/%s" % idx, "modify %s" % params),
1191 ]
1192
1193 @staticmethod
1194 def _RemoveTestFn(idx, item, private):
1195 private.data = ("remove", idx, item)
1196
1197 @staticmethod
1198 def _DetachTestFn(idx, item, private):
1199 private.data = ("detach", idx, item)
1200
1201 def testAddWithCreateFunction(self):
1202 container = []
1203 chgdesc = []
1204 mods = instance_utils.PrepareContainerMods([
1205 (constants.DDM_ADD, -1, "Hello"),
1206 (constants.DDM_ADD, -1, "World"),
1207 (constants.DDM_ADD, 0, "Start"),
1208 (constants.DDM_ADD, -1, "End"),
1209 (constants.DDM_REMOVE, 2, None),
1210 (constants.DDM_MODIFY, -1, "foobar"),
1211 (constants.DDM_REMOVE, 2, None),
1212 (constants.DDM_ADD, 1, "More"),
1213 (constants.DDM_DETACH, -1, None),
1214 (constants.DDM_ATTACH, 0, "Hello"),
1215 ], mock.Mock)
1216 instance_utils.ApplyContainerMods("test", container, chgdesc, mods,
1217 self._CreateTestFn, self._AttachTestFn,
1218 self._ModifyTestFn, self._RemoveTestFn,
1219 self._DetachTestFn)
1220 self.assertEqual(container, [
1221 (000, "Hello"),
1222 (000, "Start"),
1223 (100, "More"),
1224 ])
1225 self.assertEqual(chgdesc, [
1226 ("test/0", "0x0"),
1227 ("test/1", "0x1"),
1228 ("test/0", "0x0"),
1229 ("test/3", "0x3"),
1230 ("test/2", "remove"),
1231 ("test/2", "modify foobar"),
1232 ("test/2", "remove"),
1233 ("test/1", "0x1"),
1234 ("test/2", "detach"),
1235 ("test/0", "0x0"),
1236 ])
1237 self.assertTrue(compat.all(op == private.data[0]
1238 for (op, _, _, private) in mods))
1239 self.assertEqual([private.data for (op, _, _, private) in mods], [
1240 ("add", 0, "Hello"),
1241 ("add", 1, "World"),
1242 ("add", 0, "Start"),
1243 ("add", 3, "End"),
1244 ("remove", 2, (100, "World")),
1245 ("modify", 2, "foobar"),
1246 ("remove", 2, (300, "End")),
1247 ("add", 1, "More"),
1248 ("detach", 2, (000, "Hello")),
1249 ("attach", 0, "Hello"),
1250 ])
1251
1252
1253 class _FakeConfigForGenDiskTemplate(ConfigMock):
1254 def __init__(self):
1255 super(_FakeConfigForGenDiskTemplate, self).__init__()
1256
1257 self._unique_id = itertools.count()
1258 self._drbd_minor = itertools.count(20)
1259 self._port = itertools.count(constants.FIRST_DRBD_PORT)
1260 self._secret = itertools.count()
1261
1262 def GenerateUniqueID(self, ec_id):
1263 return "ec%s-uq%s" % (ec_id, self._unique_id.next())
1264
1265 def AllocateDRBDMinor(self, nodes, disk):
1266 return [self._drbd_minor.next()
1267 for _ in nodes]
1268
1269 def AllocatePort(self):
1270 return self._port.next()
1271
1272 def GenerateDRBDSecret(self, ec_id):
1273 return "ec%s-secret%s" % (ec_id, self._secret.next())
1274
1275
1276 class TestGenerateDiskTemplate(CmdlibTestCase):
1277 def setUp(self):
1278 super(TestGenerateDiskTemplate, self).setUp()
1279
1280 self.cfg = _FakeConfigForGenDiskTemplate()
1281 self.cluster.enabled_disk_templates = list(constants.DISK_TEMPLATES)
1282
1283 self.nodegroup = self.cfg.AddNewNodeGroup(name="ng")
1284
1285 self.lu = self.GetMockLU()
1286
1287 @staticmethod
1288 def GetDiskParams():
1289 return copy.deepcopy(constants.DISK_DT_DEFAULTS)
1290
1291 def testWrongDiskTemplate(self):
1292 gdt = instance_storage.GenerateDiskTemplate
1293 disk_template = "##unknown##"
1294
1295 assert disk_template not in constants.DISK_TEMPLATES
1296
1297 self.assertRaises(errors.OpPrereqError, gdt, self.lu, disk_template,
1298 "inst26831.example.com", "node30113.example.com", [], [],
1299 NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1300 self.GetDiskParams())
1301
1302 def testDiskless(self):
1303 gdt = instance_storage.GenerateDiskTemplate
1304
1305 result = gdt(self.lu, constants.DT_DISKLESS, "inst27734.example.com",
1306 "node30113.example.com", [], [],
1307 NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1308 self.GetDiskParams())
1309 self.assertEqual(result, [])
1310
1311 def _TestTrivialDisk(self, template, disk_info, base_index, exp_dev_type,
1312 file_storage_dir=NotImplemented,
1313 file_driver=NotImplemented):
1314 gdt = instance_storage.GenerateDiskTemplate
1315
1316 map(lambda params: utils.ForceDictType(params,
1317 constants.IDISK_PARAMS_TYPES),
1318 disk_info)
1319
1320 # Check if non-empty list of secondaries is rejected
1321 self.assertRaises(errors.ProgrammerError, gdt, self.lu,
1322 template, "inst25088.example.com",
1323 "node185.example.com", ["node323.example.com"], [],
1324 NotImplemented, NotImplemented, base_index,
1325 self.lu.LogInfo, self.GetDiskParams())
1326
1327 result = gdt(self.lu, template, "inst21662.example.com",
1328 "node21741.example.com", [],
1329 disk_info, file_storage_dir, file_driver, base_index,
1330 self.lu.LogInfo, self.GetDiskParams())
1331
1332 for (idx, disk) in enumerate(result):
1333 self.assertTrue(isinstance(disk, objects.Disk))
1334 self.assertEqual(disk.dev_type, exp_dev_type)
1335 self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
1336 self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
1337 self.assertTrue(disk.children is None)
1338
1339 self._CheckIvNames(result, base_index, base_index + len(disk_info))
1340 config._UpdateIvNames(base_index, result)
1341 self._CheckIvNames(result, base_index, base_index + len(disk_info))
1342
1343 return result
1344
1345 def _CheckIvNames(self, disks, base_index, end_index):
1346 self.assertEqual(map(operator.attrgetter("iv_name"), disks),
1347 ["disk/%s" % i for i in range(base_index, end_index)])
1348
1349 def testPlain(self):
1350 disk_info = [{
1351 constants.IDISK_SIZE: 1024,
1352 constants.IDISK_MODE: constants.DISK_RDWR,
1353 }, {
1354 constants.IDISK_SIZE: 4096,
1355 constants.IDISK_VG: "othervg",
1356 constants.IDISK_MODE: constants.DISK_RDWR,
1357 }]
1358
1359 result = self._TestTrivialDisk(constants.DT_PLAIN, disk_info, 3,
1360 constants.DT_PLAIN)
1361
1362 self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1363 ("xenvg", "ec1-uq0.disk3"),
1364 ("othervg", "ec1-uq1.disk4"),
1365 ])
1366 self.assertEqual(map(operator.attrgetter("nodes"), result), [
1367 ["node21741.example.com"], ["node21741.example.com"]])
1368
1369
1370 def testFile(self):
1371 # anything != DT_FILE would do here
1372 self.cluster.enabled_disk_templates = [constants.DT_PLAIN]
1373 self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
1374 constants.DT_FILE, [], 0, NotImplemented)
1375 self.assertRaises(errors.OpPrereqError, self._TestTrivialDisk,
1376 constants.DT_SHARED_FILE, [], 0, NotImplemented)
1377
1378 for disk_template in constants.DTS_FILEBASED:
1379 disk_info = [{
1380 constants.IDISK_SIZE: 80 * 1024,
1381 constants.IDISK_MODE: constants.DISK_RDONLY,
1382 }, {
1383 constants.IDISK_SIZE: 4096,
1384 constants.IDISK_MODE: constants.DISK_RDWR,
1385 }, {
1386 constants.IDISK_SIZE: 6 * 1024,
1387 constants.IDISK_MODE: constants.DISK_RDWR,
1388 }]
1389
1390 self.cluster.enabled_disk_templates = [disk_template]
1391 result = self._TestTrivialDisk(
1392 disk_template, disk_info, 2, disk_template,
1393 file_storage_dir="/tmp", file_driver=constants.FD_BLKTAP)
1394
1395 if disk_template == constants.DT_GLUSTER:
1396 # Here "inst21662.example.com" is actually the instance UUID, not its
1397 # name, so while this result looks wrong, it is actually correct.
1398 expected = [(constants.FD_BLKTAP,
1399 'ganeti/inst21662.example.com.%d' % x)
1400 for x in (2,3,4)]
1401 self.assertEqual(map(operator.attrgetter("logical_id"), result),
1402 expected)
1403 self.assertEqual(map(operator.attrgetter("nodes"), result), [
1404 [], [], []])
1405 else:
1406 if disk_template == constants.DT_FILE:
1407 self.assertEqual(map(operator.attrgetter("nodes"), result), [
1408 ["node21741.example.com"], ["node21741.example.com"],
1409 ["node21741.example.com"]])
1410 else:
1411 self.assertEqual(map(operator.attrgetter("nodes"), result), [
1412 [], [], []])
1413
1414 for (idx, disk) in enumerate(result):
1415 (file_driver, file_storage_dir) = disk.logical_id
1416 dir_fmt = r"^/tmp/.*\.%s\.disk%d$" % (disk_template, idx + 2)
1417 self.assertEqual(file_driver, constants.FD_BLKTAP)
1418 # FIXME: use assertIsNotNone when py 2.7 is minimum supported version
1419 self.assertNotEqual(re.match(dir_fmt, file_storage_dir), None)
1420
1421 def testBlock(self):
1422 disk_info = [{
1423 constants.IDISK_SIZE: 8 * 1024,
1424 constants.IDISK_MODE: constants.DISK_RDWR,
1425 constants.IDISK_ADOPT: "/tmp/some/block/dev",
1426 }]
1427
1428 result = self._TestTrivialDisk(constants.DT_BLOCK, disk_info, 10,
1429 constants.DT_BLOCK)
1430
1431 self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1432 (constants.BLOCKDEV_DRIVER_MANUAL, "/tmp/some/block/dev"),
1433 ])
1434 self.assertEqual(map(operator.attrgetter("nodes"), result), [[]])
1435
1436 def testRbd(self):
1437 disk_info = [{
1438 constants.IDISK_SIZE: 8 * 1024,
1439 constants.IDISK_MODE: constants.DISK_RDONLY,
1440 }, {
1441 constants.IDISK_SIZE: 100 * 1024,
1442 constants.IDISK_MODE: constants.DISK_RDWR,
1443 }]
1444
1445 result = self._TestTrivialDisk(constants.DT_RBD, disk_info, 0,
1446 constants.DT_RBD)
1447
1448 self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1449 ("rbd", "ec1-uq0.rbd.disk0"),
1450 ("rbd", "ec1-uq1.rbd.disk1"),
1451 ])
1452 self.assertEqual(map(operator.attrgetter("nodes"), result), [[], []])
1453
1454 def testDrbd8(self):
1455 gdt = instance_storage.GenerateDiskTemplate
1456 drbd8_defaults = constants.DISK_LD_DEFAULTS[constants.DT_DRBD8]
1457 drbd8_default_metavg = drbd8_defaults[constants.LDP_DEFAULT_METAVG]
1458
1459 disk_info = [{
1460 constants.IDISK_SIZE: 1024,
1461 constants.IDISK_MODE: constants.DISK_RDWR,
1462 }, {
1463 constants.IDISK_SIZE: 100 * 1024,
1464 constants.IDISK_MODE: constants.DISK_RDONLY,
1465 constants.IDISK_METAVG: "metavg",
1466 }, {
1467 constants.IDISK_SIZE: 4096,
1468 constants.IDISK_MODE: constants.DISK_RDWR,
1469 constants.IDISK_VG: "vgxyz",
1470 },
1471 ]
1472
1473 exp_logical_ids = [
1474 [
1475 (self.lu.cfg.GetVGName(), "ec1-uq0.disk0_data"),
1476 (drbd8_default_metavg, "ec1-uq0.disk0_meta"),
1477 ], [
1478 (self.lu.cfg.GetVGName(), "ec1-uq1.disk1_data"),
1479 ("metavg", "ec1-uq1.disk1_meta"),
1480 ], [
1481 ("vgxyz", "ec1-uq2.disk2_data"),
1482 (drbd8_default_metavg, "ec1-uq2.disk2_meta"),
1483 ]]
1484
1485 exp_nodes = ["node1334.example.com", "node12272.example.com"]
1486
1487 assert len(exp_logical_ids) == len(disk_info)
1488
1489 map(lambda params: utils.ForceDictType(params,
1490 constants.IDISK_PARAMS_TYPES),
1491 disk_info)
1492
1493 # Check if empty list of secondaries is rejected
1494 self.assertRaises(errors.ProgrammerError, gdt, self.lu, constants.DT_DRBD8,
1495 "inst827.example.com", "node1334.example.com", [],
1496 disk_info, NotImplemented, NotImplemented, 0,
1497 self.lu.LogInfo, self.GetDiskParams())
1498
1499 result = gdt(self.lu, constants.DT_DRBD8, "inst827.example.com",
1500 "node1334.example.com", ["node12272.example.com"],
1501 disk_info, NotImplemented, NotImplemented, 0, self.lu.LogInfo,
1502 self.GetDiskParams())
1503
1504 for (idx, disk) in enumerate(result):
1505 self.assertTrue(isinstance(disk, objects.Disk))
1506 self.assertEqual(disk.dev_type, constants.DT_DRBD8)
1507 self.assertEqual(disk.size, disk_info[idx][constants.IDISK_SIZE])
1508 self.assertEqual(disk.mode, disk_info[idx][constants.IDISK_MODE])
1509
1510 for child in disk.children:
1511 self.assertTrue(isinstance(disk, objects.Disk))
1512 self.assertEqual(child.dev_type, constants.DT_PLAIN)
1513 self.assertTrue(child.children is None)
1514 self.assertEqual(child.nodes, exp_nodes)
1515
1516 self.assertEqual(map(operator.attrgetter("logical_id"), disk.children),
1517 exp_logical_ids[idx])
1518 self.assertEqual(disk.nodes, exp_nodes)
1519
1520 self.assertEqual(len(disk.children), 2)
1521 self.assertEqual(disk.children[0].size, disk.size)
1522 self.assertEqual(disk.children[1].size, constants.DRBD_META_SIZE)
1523
1524 self._CheckIvNames(result, 0, len(disk_info))
1525 config._UpdateIvNames(0, result)
1526 self._CheckIvNames(result, 0, len(disk_info))
1527
1528 self.assertEqual(map(operator.attrgetter("logical_id"), result), [
1529 ("node1334.example.com", "node12272.example.com",
1530 constants.FIRST_DRBD_PORT, 20, 21, "ec1-secret0"),
1531 ("node1334.example.com", "node12272.example.com",
1532 constants.FIRST_DRBD_PORT + 1, 22, 23, "ec1-secret1"),
1533 ("node1334.example.com", "node12272.example.com",
1534 constants.FIRST_DRBD_PORT + 2, 24, 25, "ec1-secret2"),
1535 ])
1536
1537
1538 class _DiskPauseTracker:
1539 def __init__(self):
1540 self.history = []
1541
1542 def __call__(self, (disks, instance), pause):
1543 disk_uuids = [d.uuid for d in disks]
1544 assert not (set(disk_uuids) - set(instance.disks))
1545
1546 self.history.extend((i.logical_id, i.size, pause)
1547 for i in disks)
1548
1549 return (True, [True] * len(disks))
1550
1551
1552 class _ConfigForDiskWipe:
1553 def __init__(self, exp_node_uuid, disks):
1554 self._exp_node_uuid = exp_node_uuid
1555 self._disks = disks
1556
1557 def GetNodeName(self, node_uuid):
1558 assert node_uuid == self._exp_node_uuid
1559 return "name.of.expected.node"
1560
1561 def GetInstanceDisks(self, _):
1562 return self._disks
1563
1564
1565 class _RpcForDiskWipe:
1566 def __init__(self, exp_node, pause_cb, wipe_cb):
1567 self._exp_node = exp_node
1568 self._pause_cb = pause_cb
1569 self._wipe_cb = wipe_cb
1570
1571 def call_blockdev_pause_resume_sync(self, node, disks, pause):
1572 assert node == self._exp_node
1573 return rpc.RpcResult(data=self._pause_cb(disks, pause))
1574
1575 def call_blockdev_wipe(self, node, bdev, offset, size):
1576 assert node == self._exp_node
1577 return rpc.RpcResult(data=self._wipe_cb(bdev, offset, size))
1578
1579
1580 class _DiskWipeProgressTracker:
1581 def __init__(self, start_offset):
1582 self._start_offset = start_offset
1583 self.progress = {}
1584
1585 def __call__(self, (disk, _), offset, size):
1586 assert isinstance(offset, (long, int))
1587 assert isinstance(size, (long, int))
1588
1589 max_chunk_size = (disk.size / 100.0 * constants.MIN_WIPE_CHUNK_PERCENT)
1590
1591 assert offset >= self._start_offset
1592 assert (offset + size) <= disk.size
1593
1594 assert size > 0
1595 assert size <= constants.MAX_WIPE_CHUNK
1596 assert size <= max_chunk_size
1597
1598 assert offset == self._start_offset or disk.logical_id in self.progress
1599
1600 # Keep track of progress
1601 cur_progress = self.progress.setdefault(disk.logical_id, self._start_offset)
1602
1603 assert cur_progress == offset
1604
1605 # Record progress
1606 self.progress[disk.logical_id] += size
1607
1608 return (True, None)
1609
1610
1611 class TestWipeDisks(unittest.TestCase):
1612 def _FailingPauseCb(self, (disks, _), pause):
1613 self.assertEqual(len(disks), 3)
1614 self.assertTrue(pause)
1615 # Simulate an RPC error
1616 return (False, "error")
1617
1618 def testPauseFailure(self):
1619 node_name = "node1372.example.com"
1620
1621 disks = [
1622 objects.Disk(dev_type=constants.DT_PLAIN, uuid="disk0"),
1623 objects.Disk(dev_type=constants.DT_PLAIN, uuid="disk1"),
1624 objects.Disk(dev_type=constants.DT_PLAIN, uuid="disk2"),
1625 ]
1626
1627 lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, self._FailingPauseCb,
1628 NotImplemented),
1629 cfg=_ConfigForDiskWipe(node_name, disks))
1630
1631 inst = objects.Instance(name="inst21201",
1632 primary_node=node_name,
1633 disk_template=constants.DT_PLAIN,
1634 disks=[d.uuid for d in disks])
1635
1636 self.assertRaises(errors.OpExecError, instance_create.WipeDisks, lu, inst)
1637
1638 def _FailingWipeCb(self, (disk, _), offset, size):
1639 # This should only ever be called for the first disk
1640 self.assertEqual(disk.logical_id, "disk0")
1641 return (False, None)
1642
1643 def testFailingWipe(self):
1644 node_uuid = "node13445-uuid"
1645 pt = _DiskPauseTracker()
1646
1647 disks = [
1648 objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk0",
1649 size=100 * 1024, uuid="disk0"),
1650 objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk1",
1651 size=500 * 1024, uuid="disk1"),
1652 objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk2",
1653 size=256, uuid="disk2"),
1654 ]
1655
1656 lu = _FakeLU(rpc=_RpcForDiskWipe(node_uuid, pt, self._FailingWipeCb),
1657 cfg=_ConfigForDiskWipe(node_uuid, disks))
1658
1659 inst = objects.Instance(name="inst562",
1660 primary_node=node_uuid,
1661 disk_template=constants.DT_PLAIN,
1662 disks=[d.uuid for d in disks])
1663
1664 try:
1665 instance_create.WipeDisks(lu, inst)
1666 except errors.OpExecError, err:
1667 self.assertTrue(str(err), "Could not wipe disk 0 at offset 0 ")
1668 else:
1669 self.fail("Did not raise exception")
1670
1671 # Check if all disks were paused and resumed
1672 self.assertEqual(pt.history, [
1673 ("disk0", 100 * 1024, True),
1674 ("disk1", 500 * 1024, True),
1675 ("disk2", 256, True),
1676 ("disk0", 100 * 1024, False),
1677 ("disk1", 500 * 1024, False),
1678 ("disk2", 256, False),
1679 ])
1680
1681 def _PrepareWipeTest(self, start_offset, disks):
1682 node_name = "node-with-offset%s.example.com" % start_offset
1683 pauset = _DiskPauseTracker()
1684 progresst = _DiskWipeProgressTracker(start_offset)
1685
1686 lu = _FakeLU(rpc=_RpcForDiskWipe(node_name, pauset, progresst),
1687 cfg=_ConfigForDiskWipe(node_name, disks))
1688
1689 instance = objects.Instance(name="inst3560",
1690 primary_node=node_name,
1691 disk_template=constants.DT_PLAIN,
1692 disks=[d.uuid for d in disks])
1693
1694 return (lu, instance, pauset, progresst)
1695
1696 def testNormalWipe(self):
1697 disks = [
1698 objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk0",
1699 size=1024, uuid="disk0"),
1700 objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk1",
1701 size=500 * 1024, uuid="disk1"),
1702 objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk2",
1703 size=128, uuid="disk2"),
1704 objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk3",
1705 size=constants.MAX_WIPE_CHUNK, uuid="disk3"),
1706 ]
1707
1708 (lu, inst, pauset, progresst) = self._PrepareWipeTest(0, disks)
1709
1710 instance_create.WipeDisks(lu, inst)
1711
1712 self.assertEqual(pauset.history, [
1713 ("disk0", 1024, True),
1714 ("disk1", 500 * 1024, True),
1715 ("disk2", 128, True),
1716 ("disk3", constants.MAX_WIPE_CHUNK, True),
1717 ("disk0", 1024, False),
1718 ("disk1", 500 * 1024, False),
1719 ("disk2", 128, False),
1720 ("disk3", constants.MAX_WIPE_CHUNK, False),
1721 ])
1722
1723 # Ensure the complete disk has been wiped
1724 self.assertEqual(progresst.progress,
1725 dict((i.logical_id, i.size) for i in disks))
1726
1727 def testWipeWithStartOffset(self):
1728 for start_offset in [0, 280, 8895, 1563204]:
1729 disks = [
1730 objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk0",
1731 size=128, uuid="disk0"),
1732 objects.Disk(dev_type=constants.DT_PLAIN, logical_id="disk1",
1733 size=start_offset + (100 * 1024), uuid="disk1"),
1734 ]
1735
1736 (lu, inst, pauset, progresst) = \
1737 self._PrepareWipeTest(start_offset, disks)
1738
1739 # Test start offset with only one disk
1740 instance_create.WipeDisks(lu, inst,
1741 disks=[(1, disks[1], start_offset)])
1742
1743 # Only the second disk may have been paused and wiped
1744 self.assertEqual(pauset.history, [
1745 ("disk1", start_offset + (100 * 1024), True),
1746 ("disk1", start_offset + (100 * 1024), False),
1747 ])
1748 self.assertEqual(progresst.progress, {
1749 "disk1": disks[1].size,
1750 })
1751
1752
1753 class TestCheckOpportunisticLocking(unittest.TestCase):
1754 class OpTest(opcodes.OpCode):
1755 OP_PARAMS = [
1756 ("opportunistic_locking", False, ht.TBool, None),
1757 ("iallocator", None, ht.TMaybe(ht.TNonEmptyString), "")
1758 ]
1759
1760 @classmethod
1761 def _MakeOp(cls, **kwargs):
1762 op = cls.OpTest(**kwargs)
1763 op.Validate(True)
1764 return op
1765
1766 def testMissingAttributes(self):
1767 self.assertRaises(AttributeError, instance.CheckOpportunisticLocking,
1768 object())
1769
1770 def testDefaults(self):
1771 op = self._MakeOp()
1772 instance.CheckOpportunisticLocking(op)
1773
1774 def test(self):
1775 for iallocator in [None, "something", "other"]:
1776 for opplock in [False, True]:
1777 op = self._MakeOp(iallocator=iallocator,
1778 opportunistic_locking=opplock)
1779 if opplock and not iallocator:
1780 self.assertRaises(errors.OpPrereqError,
1781 instance.CheckOpportunisticLocking, op)
1782 else:
1783 instance.CheckOpportunisticLocking(op)
1784
1785
1786 class TestLUInstanceRemove(CmdlibTestCase):
1787 def testRemoveMissingInstance(self):
1788 op = opcodes.OpInstanceRemove(instance_name="missing.inst")
1789 self.ExecOpCodeExpectOpPrereqError(op, "Instance 'missing.inst' not known")
1790
1791 def testRemoveInst(self):
1792 inst = self.cfg.AddNewInstance(disks=[])
1793 op = opcodes.OpInstanceRemove(instance_name=inst.name)
1794 self.ExecOpCode(op)
1795
1796
1797 class TestLUInstanceMove(CmdlibTestCase):
1798 def setUp(self):
1799 super(TestLUInstanceMove, self).setUp()
1800
1801 self.node = self.cfg.AddNewNode()
1802
1803 self.rpc.call_blockdev_assemble.return_value = \
1804 self.RpcResultsBuilder() \
1805 .CreateSuccessfulNodeResult(self.node, ("/dev/mocked_path",
1806 "/var/run/ganeti/instance-disks/mocked_d",
1807 None))
1808 self.rpc.call_blockdev_remove.return_value = \
1809 self.RpcResultsBuilder() \
1810 .CreateSuccessfulNodeResult(self.master, "")
1811
1812 def ImportStart(node_uuid, opt, inst, component, args):
1813 return self.RpcResultsBuilder() \
1814 .CreateSuccessfulNodeResult(node_uuid,
1815 "deamon_on_%s" % node_uuid)
1816 self.rpc.call_import_start.side_effect = ImportStart
1817
1818 def ImpExpStatus(node_uuid, name):
1819 return self.RpcResultsBuilder() \
1820 .CreateSuccessfulNodeResult(node_uuid,
1821 [objects.ImportExportStatus(
1822 exit_status=0
1823 )])
1824 self.rpc.call_impexp_status.side_effect = ImpExpStatus
1825
1826 def ImpExpCleanup(node_uuid, name):
1827 return self.RpcResultsBuilder() \
1828 .CreateSuccessfulNodeResult(node_uuid)
1829 self.rpc.call_impexp_cleanup.side_effect = ImpExpCleanup
1830
1831 def testMissingInstance(self):
1832 op = opcodes.OpInstanceMove(instance_name="missing.inst",
1833 target_node=self.node.name)
1834 self.ExecOpCodeExpectOpPrereqError(op, "Instance 'missing.inst' not known")
1835
1836 def testUncopyableDiskTemplate(self):
1837 inst = self.cfg.AddNewInstance(disk_template=constants.DT_SHARED_FILE)
1838 op = opcodes.OpInstanceMove(instance_name=inst.name,
1839 target_node=self.node.name)
1840 self.ExecOpCodeExpectOpPrereqError(
1841 op, "Instance disk 0 has disk type sharedfile and is not suitable"
1842 " for copying")
1843
1844 def testAlreadyOnTargetNode(self):
1845 inst = self.cfg.AddNewInstance()
1846 op = opcodes.OpInstanceMove(instance_name=inst.name,
1847 target_node=self.master.name)
1848 self.ExecOpCodeExpectOpPrereqError(
1849 op, "Instance .* is already on the node .*")
1850
1851 def testMoveStoppedInstance(self):
1852 inst = self.cfg.AddNewInstance()
1853 op = opcodes.OpInstanceMove(instance_name=inst.name,
1854 target_node=self.node.name)
1855 self.ExecOpCode(op)
1856
1857 def testMoveRunningInstance(self):
1858 self.rpc.call_node_info.return_value = \
1859 self.RpcResultsBuilder() \
1860 .AddSuccessfulNode(self.node,
1861 (NotImplemented, NotImplemented,
1862 ({"memory_free": 10000}, ))) \
1863 .Build()
1864 self.rpc.call_instance_start.return_value = \
1865 self.RpcResultsBuilder() \
1866 .CreateSuccessfulNodeResult(self.node, "")
1867
1868 inst = self.cfg.AddNewInstance(admin_state=constants.ADMINST_UP)
1869 op = opcodes.OpInstanceMove(instance_name=inst.name,
1870 target_node=self.node.name)
1871 self.ExecOpCode(op)
1872
1873 def testMoveFailingStartInstance(self):
1874 self.rpc.call_node_info.return_value = \
1875 self.RpcResultsBuilder() \
1876 .AddSuccessfulNode(self.node,
1877 (NotImplemented, NotImplemented,
1878 ({"memory_free": 10000}, ))) \
1879 .Build()
1880 self.rpc.call_instance_start.return_value = \
1881 self.RpcResultsBuilder() \
1882 .CreateFailedNodeResult(self.node)
1883
1884 inst = self.cfg.AddNewInstance(admin_state=constants.ADMINST_UP)
1885 op = opcodes.OpInstanceMove(instance_name=inst.name,
1886 target_node=self.node.name)
1887 self.ExecOpCodeExpectOpExecError(
1888 op, "Could not start instance .* on node .*")
1889
1890 def testMoveFailingImpExpDaemonExitCode(self):
1891 inst = self.cfg.AddNewInstance()
1892 self.rpc.call_impexp_status.side_effect = None
1893 self.rpc.call_impexp_status.return_value = \
1894 self.RpcResultsBuilder() \
1895 .CreateSuccessfulNodeResult(self.node,
1896 [objects.ImportExportStatus(
1897 exit_status=1,
1898 recent_output=["mock output"]
1899 )])
1900 op = opcodes.OpInstanceMove(instance_name=inst.name,
1901 target_node=self.node.name)
1902 self.ExecOpCodeExpectOpExecError(op, "Errors during disk copy")
1903
1904 def testMoveFailingStartImpExpDaemon(self):
1905 inst = self.cfg.AddNewInstance()
1906 self.rpc.call_import_start.side_effect = None
1907 self.rpc.call_import_start.return_value = \
1908 self.RpcResultsBuilder() \
1909 .CreateFailedNodeResult(self.node)
1910 op = opcodes.OpInstanceMove(instance_name=inst.name,
1911 target_node=self.node.name)
1912 self.ExecOpCodeExpectOpExecError(op, "Errors during disk copy")
1913
1914
1915 class TestLUInstanceRename(CmdlibTestCase):
1916 def setUp(self):
1917 super(TestLUInstanceRename, self).setUp()
1918
1919 self.MockOut(instance_utils, 'netutils', self.netutils_mod)
1920
1921 self.inst = self.cfg.AddNewInstance()
1922
1923 self.op = opcodes.OpInstanceRename(instance_name=self.inst.name,
1924 new_name="new_name.example.com")
1925
1926 def testIpCheckWithoutNameCheck(self):
1927 op = self.CopyOpCode(self.op,
1928 ip_check=True,
1929 name_check=False)
1930 self.ExecOpCodeExpectOpPrereqError(
1931 op, "IP address check requires a name check")
1932
1933 def testIpAlreadyInUse(self):
1934 self.netutils_mod.TcpPing.return_value = True
1935 op = self.CopyOpCode(self.op)
1936 self.ExecOpCodeExpectOpPrereqError(
1937 op, "IP .* of instance .* already in use")
1938
1939 def testExistingInstanceName(self):
1940 self.cfg.AddNewInstance(name="new_name.example.com")
1941 op = self.CopyOpCode(self.op)
1942 self.ExecOpCodeExpectOpPrereqError(
1943 op, "Instance .* is already in the cluster")
1944
1945 def testFileInstance(self):
1946 self.rpc.call_blockdev_assemble.return_value = \
1947 self.RpcResultsBuilder() \
1948 .CreateSuccessfulNodeResult(self.master, (None, None, None))
1949 self.rpc.call_blockdev_shutdown.return_value = \
1950 self.RpcResultsBuilder() \
1951 .CreateSuccessfulNodeResult(self.master, (None, None))
1952
1953 inst = self.cfg.AddNewInstance(disk_template=constants.DT_FILE)
1954 op = self.CopyOpCode(self.op,
1955 instance_name=inst.name)
1956 self.ExecOpCode(op)
1957
1958
1959 class TestLUInstanceMultiAlloc(CmdlibTestCase):
1960 def setUp(self):
1961 super(TestLUInstanceMultiAlloc, self).setUp()
1962
1963 self.inst_op = opcodes.OpInstanceCreate(instance_name="inst.example.com",
1964 disk_template=constants.DT_DRBD8,
1965 disks=[],
1966 nics=[],
1967 os_type="mock_os",
1968 hypervisor=constants.HT_XEN_HVM,
1969 mode=constants.INSTANCE_CREATE)
1970
1971 def testInstanceWithIAllocator(self):
1972 inst = self.CopyOpCode(self.inst_op,
1973 iallocator="mock")
1974 op = opcodes.OpInstanceMultiAlloc(instances=[inst])
1975 self.ExecOpCodeExpectOpPrereqError(
1976 op, "iallocator are not allowed to be set on instance objects")
1977
1978 def testOnlySomeNodesGiven(self):
1979 inst1 = self.CopyOpCode(self.inst_op,
1980 pnode=self.master.name)
1981 inst2 = self.CopyOpCode(self.inst_op)
1982 op = opcodes.OpInstanceMultiAlloc(instances=[inst1, inst2])
1983 self.ExecOpCodeExpectOpPrereqError(
1984 op, "There are instance objects providing pnode/snode while others"
1985 " do not")
1986
1987 def testMissingIAllocator(self):
1988 self.cluster.default_iallocator = None
1989 inst = self.CopyOpCode(self.inst_op)
1990 op = opcodes.OpInstanceMultiAlloc(instances=[inst])
1991 self.ExecOpCodeExpectOpPrereqError(
1992 op, "No iallocator or nodes on the instances given and no cluster-wide"
1993 " default iallocator found")
1994
1995 def testDuplicateInstanceNames(self):
1996 inst1 = self.CopyOpCode(self.inst_op)
1997 inst2 = self.CopyOpCode(self.inst_op)
1998 op = opcodes.OpInstanceMultiAlloc(instances=[inst1, inst2])
1999 self.ExecOpCodeExpectOpPrereqError(
2000 op, "There are duplicate instance names")
2001
2002 def testWithGivenNodes(self):
2003 snode = self.cfg.AddNewNode()
2004 inst = self.CopyOpCode(self.inst_op,
2005 pnode=self.master.name,
2006 snode=snode.name)
2007 op = opcodes.OpInstanceMultiAlloc(instances=[inst])
2008 self.ExecOpCode(op)
2009
2010 def testDryRun(self):
2011 snode = self.cfg.AddNewNode()
2012 inst = self.CopyOpCode(self.inst_op,
2013 pnode=self.master.name,
2014 snode=snode.name)
2015 op = opcodes.OpInstanceMultiAlloc(instances=[inst],
2016 dry_run=True)
2017 self.ExecOpCode(op)
2018
2019 def testWithIAllocator(self):
2020 snode = self.cfg.AddNewNode()
2021 self.iallocator_cls.return_value.result = \
2022 ([("inst.example.com", [self.master.name, snode.name])], [])
2023
2024 inst = self.CopyOpCode(self.inst_op)
2025 op = opcodes.OpInstanceMultiAlloc(instances=[inst],
2026 iallocator="mock_ialloc")
2027 self.ExecOpCode(op)
2028
2029 def testManyInstancesWithIAllocator(self):
2030 snode = self.cfg.AddNewNode()
2031
2032 inst1 = self.CopyOpCode(self.inst_op)
2033 inst2 = self.CopyOpCode(self.inst_op, instance_name="inst2.example.com")
2034
2035 self.iallocator_cls.return_value.result = \
2036 ([("inst.example.com", [self.master.name, snode.name]),
2037 ("inst2.example.com", [self.master.name, snode.name])],
2038 [])
2039
2040 op = opcodes.OpInstanceMultiAlloc(instances=[inst1, inst2],
2041 iallocator="mock_ialloc")
2042 self.ExecOpCode(op)
2043
2044 def testWithIAllocatorOpportunisticLocking(self):
2045 snode = self.cfg.AddNewNode()
2046 self.iallocator_cls.return_value.result = \
2047 ([("inst.example.com", [self.master.name, snode.name])], [])
2048
2049 inst = self.CopyOpCode(self.inst_op)
2050 op = opcodes.OpInstanceMultiAlloc(instances=[inst],
2051 iallocator="mock_ialloc",
2052 opportunistic_locking=True)
2053 self.ExecOpCode(op)
2054
2055 def testFailingIAllocator(self):
2056 self.iallocator_cls.return_value.success = False
2057
2058 inst = self.CopyOpCode(self.inst_op)
2059 op = opcodes.OpInstanceMultiAlloc(instances=[inst],
2060 iallocator="mock_ialloc")
2061 self.ExecOpCodeExpectOpPrereqError(
2062 op, "Can't compute nodes using iallocator")
2063
2064
2065 class TestLUInstanceSetParams(CmdlibTestCase):
2066 def setUp(self):
2067 super(TestLUInstanceSetParams, self).setUp()
2068
2069 self.MockOut(instance_set_params, 'netutils', self.netutils_mod)
2070 self.MockOut(instance_utils, 'netutils', self.netutils_mod)
2071
2072 self.dev_type = constants.DT_PLAIN
2073 self.inst = self.cfg.AddNewInstance(disk_template=self.dev_type)
2074 self.op = opcodes.OpInstanceSetParams(instance_name=self.inst.name)
2075
2076 self.running_inst = \
2077 self.cfg.AddNewInstance(admin_state=constants.ADMINST_UP)
2078 self.running_op = \
2079 opcodes.OpInstanceSetParams(instance_name=self.running_inst.name)
2080
2081 ext_disks = [self.cfg.CreateDisk(dev_type=constants.DT_EXT,
2082 params={
2083 constants.IDISK_PROVIDER: "pvdr"
2084 })]
2085 self.ext_storage_inst = \
2086 self.cfg.AddNewInstance(disk_template=constants.DT_EXT,
2087 disks=ext_disks)
2088 self.ext_storage_op = \
2089 opcodes.OpInstanceSetParams(instance_name=self.ext_storage_inst.name)
2090
2091 self.snode = self.cfg.AddNewNode()
2092
2093 self.mocked_storage_type = constants.ST_LVM_VG
2094 self.mocked_storage_free = 10000
2095 self.mocked_master_cpu_total = 16
2096 self.mocked_master_memory_free = 2048
2097 self.mocked_snode_cpu_total = 16
2098 self.mocked_snode_memory_free = 512
2099
2100 self.mocked_running_inst_memory = 1024
2101 self.mocked_running_inst_vcpus = 8
2102 self.mocked_running_inst_state = "running"
2103 self.mocked_running_inst_time = 10938474
2104
2105 bootid = "mock_bootid"
2106 storage_info = [
2107 {
2108 "type": self.mocked_storage_type,
2109 "storage_free": self.mocked_storage_free
2110 }
2111 ]
2112 hv_info_master = {
2113 "cpu_total": self.mocked_master_cpu_total,
2114 "memory_free": self.mocked_master_memory_free
2115 }
2116 hv_info_snode = {
2117 "cpu_total": self.mocked_snode_cpu_total,
2118 "memory_free": self.mocked_snode_memory_free
2119 }
2120
2121 self.rpc.call_node_info.return_value = \
2122 self.RpcResultsBuilder() \
2123 .AddSuccessfulNode(self.master,
2124 (bootid, storage_info, (hv_info_master, ))) \
2125 .AddSuccessfulNode(self.snode,
2126 (bootid, storage_info, (hv_info_snode, ))) \
2127 .Build()
2128
2129 def _InstanceInfo(_, instance, __, ___):
2130 if instance in [self.inst.name, self.ext_storage_inst.name]:
2131 return self.RpcResultsBuilder() \
2132 .CreateSuccessfulNodeResult(self.master, None)
2133 elif instance == self.running_inst.name:
2134 return self.RpcResultsBuilder() \
2135 .CreateSuccessfulNodeResult(
2136 self.master, {
2137 "memory": self.mocked_running_inst_memory,
2138 "vcpus": self.mocked_running_inst_vcpus,
2139 "state": self.mocked_running_inst_state,
2140 "time": self.mocked_running_inst_time
2141 })
2142 else:
2143 raise AssertionError()
2144 self.rpc.call_instance_info.side_effect = _InstanceInfo
2145
2146 self.rpc.call_bridges_exist.return_value = \
2147 self.RpcResultsBuilder() \
2148 .CreateSuccessfulNodeResult(self.master, True)
2149
2150 self.rpc.call_blockdev_getmirrorstatus.side_effect = \
2151 lambda node, _: self.RpcResultsBuilder() \
2152 .CreateSuccessfulNodeResult(node, [])
2153
2154 self.rpc.call_blockdev_shutdown.side_effect = \
2155 lambda node, _: self.RpcResultsBuilder() \
2156 .CreateSuccessfulNodeResult(node, [])
2157
2158 def testNoChanges(self):
2159 op = self.CopyOpCode(self.op)
2160 self.ExecOpCodeExpectOpPrereqError(op, "No changes submitted")
2161
2162 def testGlobalHvparams(self):
2163 op = self.CopyOpCode(self.op,
2164 hvparams={constants.HV_MIGRATION_PORT: 1234})
2165 self.ExecOpCodeExpectOpPrereqError(
2166 op, "hypervisor parameters are global and cannot be customized")
2167
2168 def testHvparams(self):
2169 op = self.CopyOpCode(self.op,
2170 hvparams={constants.HV_BOOT_ORDER: "cd"})
2171 self.ExecOpCode(op)
2172
2173 def testDisksAndDiskTemplate(self):
2174 op = self.CopyOpCode(self.op,
2175 disk_template=constants.DT_PLAIN,
2176 disks=[[constants.DDM_ADD, -1, {}]])
2177 self.ExecOpCodeExpectOpPrereqError(
2178 op, "Disk template conversion and other disk changes not supported at"
2179 " the same time")
2180
2181 def testDiskTemplateToMirroredNoRemoteNode(self):
2182 op = self.CopyOpCode(self.op,
2183 disk_template=constants.DT_DRBD8)
2184 self.ExecOpCodeExpectOpPrereqError(
2185 op, "Changing the disk template to a mirrored one requires specifying"
2186 " a secondary node")
2187
2188 def testPrimaryNodeToOldPrimaryNode(self):
2189 op = self.CopyOpCode(self.op,
2190 pnode=self.master.name)
2191 self.ExecOpCode(op)
2192
2193 def testPrimaryNodeChange(self):
2194 node = self.cfg.AddNewNode()
2195 op = self.CopyOpCode(self.op,
2196 pnode=node.name)
2197 self.ExecOpCode(op)
2198
2199 def testPrimaryNodeChangeRunningInstance(self):
2200 node = self.cfg.AddNewNode()
2201 op = self.CopyOpCode(self.running_op,
2202 pnode=node.name)
2203 self.ExecOpCodeExpectOpPrereqError(op, "Instance is still running")
2204
2205 def testOsChange(self):
2206 os = self.cfg.CreateOs(supported_variants=[])
2207 self.rpc.call_os_validate.return_value = True
2208 op = self.CopyOpCode(self.op,
2209 os_name=os.name)
2210 self.ExecOpCode(op)
2211
2212 def testVCpuChange(self):
2213 op = self.CopyOpCode(self.op,
2214 beparams={
2215 constants.BE_VCPUS: 4
2216 })
2217 self.ExecOpCode(op)
2218
2219 def testWrongCpuMask(self):
2220 op = self.CopyOpCode(self.op,
2221 beparams={
2222 constants.BE_VCPUS: 4
2223 },
2224 hvparams={
2225 constants.HV_CPU_MASK: "1,2:3,4"
2226 })
2227 self.ExecOpCodeExpectOpPrereqError(
2228 op, "Number of vCPUs .* does not match the CPU mask .*")
2229
2230 def testCorrectCpuMask(self):
2231 op = self.CopyOpCode(self.op,
2232 beparams={
2233 constants.BE_VCPUS: 4
2234 },
2235 hvparams={
2236 constants.HV_CPU_MASK: "1,2:3,4:all:1,4"
2237 })
2238 self.ExecOpCode(op)
2239
2240 def testOsParams(self):
2241 op = self.CopyOpCode(self.op,
2242 osparams={
2243 self.os.supported_parameters[0]: "test_param_val"
2244 })
2245 self.ExecOpCode(op)
2246
2247 def testIncreaseMemoryTooMuch(self):
2248 op = self.CopyOpCode(self.running_op,
2249 beparams={
2250 constants.BE_MAXMEM:
2251 self.mocked_master_memory_free * 2
2252 })
2253 self.ExecOpCodeExpectOpPrereqError(
2254 op, "This change will prevent the instance from starting")
2255
2256 def testIncreaseMemory(self):
2257 op = self.CopyOpCode(self.running_op,
2258 beparams={
2259 constants.BE_MAXMEM: self.mocked_master_memory_free
2260 })
2261 self.ExecOpCode(op)
2262
2263 def testIncreaseMemoryTooMuchForSecondary(self):
2264 inst = self.cfg.AddNewInstance(admin_state=constants.ADMINST_UP,
2265 disk_template=constants.DT_DRBD8,
2266 secondary_node=self.snode)
2267 self.rpc.call_instance_info.side_effect = [
2268 self.RpcResultsBuilder()
2269 .CreateSuccessfulNodeResult(self.master,
2270 {
2271 "memory":
2272 self.mocked_snode_memory_free * 2,
2273 "vcpus": self.mocked_running_inst_vcpus,
2274 "state": self.mocked_running_inst_state,
2275 "time": self.mocked_running_inst_time
2276 })]
2277
2278 op = self.CopyOpCode(self.op,
2279 instance_name=inst.name,
2280 beparams={
2281 constants.BE_MAXMEM:
2282 self.mocked_snode_memory_free * 2,
2283 constants.BE_AUTO_BALANCE: True
2284 })
2285 self.ExecOpCodeExpectOpPrereqError(
2286 op, "This change will prevent the instance from failover to its"
2287 " secondary node")
2288
2289 def testInvalidRuntimeMemory(self):
2290 op = self.CopyOpCode(self.running_op,
2291 runtime_mem=self.mocked_master_memory_free * 2)
2292 self.ExecOpCodeExpectOpPrereqError(
2293 op, "Instance .* must have memory between .* and .* of memory")
2294
2295 def testIncreaseRuntimeMemory(self):
2296 op = self.CopyOpCode(self.running_op,
2297 runtime_mem=self.mocked_master_memory_free,
2298 beparams={
2299 constants.BE_MAXMEM: self.mocked_master_memory_free
2300 })
2301 self.ExecOpCode(op)
2302
2303 def testAddNicWithPoolIpNoNetwork(self):
2304 op = self.CopyOpCode(self.op,
2305 nics=[(constants.DDM_ADD, -1,
2306 {
2307 constants.INIC_IP: constants.NIC_IP_POOL
2308 })])
2309 self.ExecOpCodeExpectOpPrereqError(
2310 op, "If ip=pool, parameter network cannot be none")
2311
2312 def testAddNicWithPoolIp(self):
2313 net = self.cfg.AddNewNetwork()
2314 self.cfg.ConnectNetworkToGroup(net, self.group)
2315 op = self.CopyOpCode(self.op,
2316 nics=[(constants.DDM_ADD, -1,
2317 {
2318 constants.INIC_IP: constants.NIC_IP_POOL,
2319 constants.INIC_NETWORK: net.name
2320 })])
2321 self.ExecOpCode(op)
2322
2323 def testAddNicWithInvalidIp(self):
2324 op = self.CopyOpCode(self.op,
2325 nics=[(constants.DDM_ADD, -1,
2326 {
2327 constants.INIC_IP: "invalid"
2328 })])
2329 self.ExecOpCodeExpectOpPrereqError(
2330 op, "Invalid IP address")
2331
2332 def testAddNic(self):
2333 op = self.CopyOpCode(self.op,
2334 nics=[(constants.DDM_ADD, -1, {})])
2335 self.ExecOpCode(op)
2336
2337 def testAttachNICs(self):
2338 msg = "Attach operation is not supported for NICs"
2339 op = self.CopyOpCode(self.op,
2340 nics=[(constants.DDM_ATTACH, -1, {})])
2341 self.ExecOpCodeExpectOpPrereqError(op, msg)
2342
2343 def testNoHotplugSupport(self):
2344 op = self.CopyOpCode(self.op,
2345 nics=[(constants.DDM_ADD, -1, {})],
2346 hotplug=True)
2347 self.rpc.call_hotplug_supported.return_value = \
2348 self.RpcResultsBuilder() \
2349 .CreateFailedNodeResult(self.master)
2350 self.ExecOpCodeExpectOpPrereqError(op, "Hotplug is not possible")
2351 self.assertTrue(self.rpc.call_hotplug_supported.called)
2352
2353 def testHotplugIfPossible(self):
2354 op = self.CopyOpCode(self.op,
2355 nics=[(constants.DDM_ADD, -1, {})],
2356 hotplug_if_possible=True)
2357 self.rpc.call_hotplug_supported.return_value = \
2358 self.RpcResultsBuilder() \
2359 .CreateFailedNodeResult(self.master)
2360 self.ExecOpCode(op)
2361 self.assertTrue(self.rpc.call_hotplug_supported.called)
2362 self.assertFalse(self.rpc.call_hotplug_device.called)
2363
2364 def testHotAddNic(self):
2365 op = self.CopyOpCode(self.op,
2366 nics=[(constants.DDM_ADD, -1, {})],
2367 hotplug=True)
2368 self.rpc.call_hotplug_supported.return_value = \
2369 self.RpcResultsBuilder() \
2370 .CreateSuccessfulNodeResult(self.master)
2371 self.ExecOpCode(op)
2372 self.assertTrue(self.rpc.call_hotplug_supported.called)
2373 self.assertTrue(self.rpc.call_hotplug_device.called)
2374
2375 def testAddNicWithIp(self):
2376 op = self.CopyOpCode(self.op,
2377 nics=[(constants.DDM_ADD, -1,
2378 {
2379 constants.INIC_IP: "2.3.1.4"
2380 })])
2381 self.ExecOpCode(op)
2382
2383 def testModifyNicRoutedWithoutIp(self):
2384 op = self.CopyOpCode(self.op,
2385 nics=[(constants.DDM_MODIFY, 0,
2386 {
2387 constants.INIC_NETWORK: constants.VALUE_NONE,
2388 constants.INIC_MODE: constants.NIC_MODE_ROUTED
2389 })])
2390 self.ExecOpCodeExpectOpPrereqError(
2391 op, "Cannot set the NIC IP address to None on a routed NIC"
2392 " if not attached to a network")
2393
2394 def testModifyNicSetMac(self):
2395 op = self.CopyOpCode(self.op,
2396 nics=[(constants.DDM_MODIFY, 0,
2397 {
2398 constants.INIC_MAC: "0a:12:95:15:bf:75"
2399 })])
2400 self.ExecOpCode(op)
2401
2402 def testModifyNicWithPoolIpNoNetwork(self):
2403 op = self.CopyOpCode(self.op,
2404 nics=[(constants.DDM_MODIFY, -1,
2405 {
2406 constants.INIC_IP: constants.NIC_IP_POOL
2407 })])
2408 self.ExecOpCodeExpectOpPrereqError(
2409 op, "ip=pool, but no network found")
2410
2411 def testModifyNicSetNet(self):
2412 old_net = self.cfg.AddNewNetwork()
2413 self.cfg.ConnectNetworkToGroup(old_net, self.group)
2414 inst = self.cfg.AddNewInstance(nics=[
2415 self.cfg.CreateNic(network=old_net,
2416 ip="198.51.100.2")])
2417
2418 new_net = self.cfg.AddNewNetwork(mac_prefix="be")
2419 self.cfg.ConnectNetworkToGroup(new_net, self.group)
2420 op = self.CopyOpCode(self.op,
2421 instance_name=inst.name,
2422 nics=[(constants.DDM_MODIFY, 0,
2423 {
2424 constants.INIC_NETWORK: new_net.name
2425 })])
2426 self.ExecOpCode(op)
2427
2428 def testModifyNicSetLinkWhileConnected(self):
2429 old_net = self.cfg.AddNewNetwork()
2430 self.cfg.ConnectNetworkToGroup(old_net, self.group)
2431 inst = self.cfg.AddNewInstance(nics=[
2432 self.cfg.CreateNic(network=old_net)])
2433
2434 op = self.CopyOpCode(self.op,
2435 instance_name=inst.name,
2436 nics=[(constants.DDM_MODIFY, 0,
2437 {
2438 constants.INIC_LINK: "mock_link"
2439 })])
2440 self.ExecOpCodeExpectOpPrereqError(
2441 op, "Not allowed to change link or mode of a NIC that is connected"
2442 " to a network")
2443
2444 def testModifyNicSetNetAndIp(self):
2445 net = self.cfg.AddNewNetwork(mac_prefix="be", network="123.123.123.0/24")
2446 self.cfg.ConnectNetworkToGroup(net, self.group)
2447 op = self.CopyOpCode(self.op,
2448 nics=[(constants.DDM_MODIFY, 0,
2449 {
2450 constants.INIC_NETWORK: net.name,
2451 constants.INIC_IP: "123.123.123.1"
2452 })])
2453 self.ExecOpCode(op)
2454
2455 def testModifyNic(self):
2456 op = self.CopyOpCode(self.op,
2457 nics=[(constants.DDM_MODIFY, 0, {})])
2458 self.ExecOpCode(op)
2459
2460 def testHotModifyNic(self):
2461 op = self.CopyOpCode(self.op,
2462 nics=[(constants.DDM_MODIFY, 0, {})],
2463 hotplug=True)
2464 self.rpc.call_hotplug_supported.return_value = \
2465 self.RpcResultsBuilder() \
2466 .CreateSuccessfulNodeResult(self.master)
2467 self.ExecOpCode(op)
2468 self.assertTrue(self.rpc.call_hotplug_supported.called)
2469 self.assertTrue(self.rpc.call_hotplug_device.called)
2470
2471 def testRemoveLastNic(self):
2472 op = self.CopyOpCode(self.op,
2473 nics=[(constants.DDM_REMOVE, 0, {})])
2474 self.ExecOpCodeExpectOpPrereqError(
2475 op, "violates policy")
2476
2477 def testRemoveNic(self):
2478 inst = self.cfg.AddNewInstance(nics=[self.cfg.CreateNic(),
2479 self.cfg.CreateNic()])
2480 op = self.CopyOpCode(self.op,
2481 instance_name=inst.name,
2482 nics=[(constants.DDM_REMOVE, 0, {})])
2483 self.ExecOpCode(op)
2484
2485 def testDetachNICs(self):
2486 msg = "Detach operation is not supported for NICs"
2487 op = self.CopyOpCode(self.op,
2488 nics=[(constants.DDM_DETACH, -1, {})])
2489 self.ExecOpCodeExpectOpPrereqError(op, msg)
2490
2491 def testHotRemoveNic(self):
2492 inst = self.cfg.AddNewInstance(nics=[self.cfg.CreateNic(),
2493 self.cfg.CreateNic()])
2494 op = self.CopyOpCode(self.op,
2495 instance_name=inst.name,
2496 nics=[(constants.DDM_REMOVE, 0, {})],
2497 hotplug=True)
2498 self.rpc.call_hotplug_supported.return_value = \
2499 self.RpcResultsBuilder() \
2500 .CreateSuccessfulNodeResult(self.master)
2501 self.ExecOpCode(op)
2502 self.assertTrue(self.rpc.call_hotplug_supported.called)
2503 self.assertTrue(self.rpc.call_hotplug_device.called)
2504
2505 def testSetOffline(self):
2506 op = self.CopyOpCode(self.op,
2507 offline=True)
2508 self.ExecOpCode(op)
2509
2510 def testUnsetOffline(self):
2511 op = self.CopyOpCode(self.op,
2512 offline=False)
2513 self.ExecOpCode(op)
2514
2515 def testAddDiskInvalidMode(self):
2516 op = self.CopyOpCode(self.op,
2517 disks=[[constants.DDM_ADD, -1,
2518 {
2519 constants.IDISK_MODE: "invalid"
2520 }]])
2521 self.ExecOpCodeExpectOpPrereqError(
2522 op, "Invalid disk access mode 'invalid'")
2523
2524 def testAddDiskMissingSize(self):
2525 op = self.CopyOpCode(self.op,
2526 disks=[[constants.DDM_ADD, -1, {}]])
2527 self.ExecOpCodeExpectOpPrereqError(
2528 op, "Required disk parameter 'size' missing")
2529
2530 def testAddDiskInvalidSize(self):
2531 op = self.CopyOpCode(self.op,
2532 disks=[[constants.DDM_ADD, -1,
2533 {
2534 constants.IDISK_SIZE: "invalid"
2535 }]])
2536 self.ExecOpCodeExpectException(
2537 op, errors.TypeEnforcementError, "is not a valid size")
2538
2539 def testAddDiskUnknownParam(self):
2540 op = self.CopyOpCode(self.op,
2541 disks=[[constants.DDM_ADD, -1,
2542 {
2543 "uuid": "mock_uuid_1134"
2544 }]])
2545 self.ExecOpCodeExpectException(
2546 op, errors.TypeEnforcementError, "Unknown parameter 'uuid'")
2547
2548 def testAddDiskRunningInstanceNoWaitForSync(self):
2549 op = self.CopyOpCode(self.running_op,
2550 disks=[[constants.DDM_ADD, -1,
2551 {
2552 constants.IDISK_SIZE: 1024
2553 }]],
2554 wait_for_sync=False)
2555 self.ExecOpCode(op)
2556 self.assertFalse(self.rpc.call_blockdev_shutdown.called)
2557
2558 def testAddDiskDownInstance(self):
2559 op = self.CopyOpCode(self.op,
2560 disks=[[constants.DDM_ADD, -1,
2561 {
2562 constants.IDISK_SIZE: 1024
2563 }]])
2564 self.ExecOpCode(op)
2565 self.assertTrue(self.rpc.call_blockdev_shutdown.called)
2566
2567 def testAddDiskIndexBased(self):
2568 SPECIFIC_SIZE = 435 * 4
2569 insertion_index = len(self.inst.disks)
2570 op = self.CopyOpCode(self.op,
2571 disks=[[constants.DDM_ADD, insertion_index,
2572 {
2573 constants.IDISK_SIZE: SPECIFIC_SIZE
2574 }]])
2575 self.ExecOpCode(op)
2576 self.assertEqual(len(self.inst.disks), insertion_index + 1)
2577 new_disk = self.cfg.GetDisk(self.inst.disks[insertion_index])
2578 self.assertEqual(new_disk.size, SPECIFIC_SIZE)
2579
2580 def testAddDiskHugeIndex(self):
2581 op = self.CopyOpCode(self.op,
2582 disks=[[constants.DDM_ADD, 5,
2583 {
2584 constants.IDISK_SIZE: 1024
2585 }]])
2586 self.ExecOpCodeExpectException(
2587 op, IndexError, "Got disk index.*but there are only.*"
2588 )
2589
2590 def testAddExtDisk(self):
2591 op = self.CopyOpCode(self.ext_storage_op,
2592 disks=[[constants.DDM_ADD, -1,
2593 {
2594 constants.IDISK_SIZE: 1024
2595 }]])
2596 self.ExecOpCodeExpectOpPrereqError(op,
2597 "Missing provider for template 'ext'")
2598
2599 op = self.CopyOpCode(self.ext_storage_op,
2600 disks=[[constants.DDM_ADD, -1,
2601 {
2602 constants.IDISK_SIZE: 1024,
2603 constants.IDISK_PROVIDER: "bla"
2604 }]])
2605 self.ExecOpCode(op)
2606
2607 def testAddDiskDownInstanceNoWaitForSync(self):
2608 op = self.CopyOpCode(self.op,
2609 disks=[[constants.DDM_ADD, -1,
2610 {
2611 constants.IDISK_SIZE: 1024
2612 }]],
2613 wait_for_sync=False)
2614 self.ExecOpCodeExpectOpPrereqError(
2615 op, "Can't add a disk to an instance with deactivated disks"
2616 " and --no-wait-for-sync given")
2617
2618 def testAddDiskRunningInstance(self):
2619 op = self.CopyOpCode(self.running_op,
2620 disks=[[constants.DDM_ADD, -1,
2621 {
2622 constants.IDISK_SIZE: 1024
2623 }]])
2624 self.ExecOpCode(op)
2625
2626 self.assertFalse(self.rpc.call_blockdev_shutdown.called)
2627
2628 def testAddDiskNoneName(self):
2629 op = self.CopyOpCode(self.op,
2630 disks=[[constants.DDM_ADD, -1,
2631 {
2632 constants.IDISK_SIZE: 1024,
2633 constants.IDISK_NAME: constants.VALUE_NONE
2634 }]])
2635 self.ExecOpCode(op)
2636
2637 def testHotAddDisk(self):
2638 self.rpc.call_blockdev_assemble.return_value = \
2639 self.RpcResultsBuilder() \
2640 .CreateSuccessfulNodeResult(self.master, ("/dev/mocked_path",
2641 "/var/run/ganeti/instance-disks/mocked_d",
2642 None))
2643 op = self.CopyOpCode(self.op,
2644 disks=[[constants.DDM_ADD, -1,
2645 {
2646 constants.IDISK_SIZE: 1024,
2647 }]],
2648 hotplug=True)
2649 self.rpc.call_hotplug_supported.return_value = \
2650 self.RpcResultsBuilder() \
2651 .CreateSuccessfulNodeResult(self.master)
2652 self.ExecOpCode(op)
2653 self.assertTrue(self.rpc.call_hotplug_supported.called)
2654 self.assertTrue(self.rpc.call_blockdev_create.called)
2655 self.assertTrue(self.rpc.call_blockdev_assemble.called)
2656 self.assertTrue(self.rpc.call_hotplug_device.called)
2657
2658 def testAttachDiskWrongParams(self):
2659 msg = "Only one argument is permitted in attach op, either name or uuid"
2660 op = self.CopyOpCode(self.op,
2661 disks=[[constants.DDM_ATTACH, -1,
2662 {
2663 constants.IDISK_SIZE: 1134
2664 }]],
2665 )
2666 self.ExecOpCodeExpectOpPrereqError(op, msg)
2667 op = self.CopyOpCode(self.op,
2668 disks=[[constants.DDM_ATTACH, -1,
2669 {
2670 'uuid': "1134",
2671 constants.IDISK_NAME: "1134",
2672 }]],
2673 )
2674 self.ExecOpCodeExpectOpPrereqError(op, msg)
2675 op = self.CopyOpCode(self.op,
2676 disks=[[constants.DDM_ATTACH, -1,
2677 {
2678 'uuid': "1134",
2679 constants.IDISK_SIZE: 1134,
2680 }]],
2681 )
2682 self.ExecOpCodeExpectOpPrereqError(op, msg)
2683
2684 def testAttachDiskWrongTemplate(self):
2685 msg = "Instance has '%s' template while disk has '%s' template" % \
2686 (constants.DT_PLAIN, constants.DT_BLOCK)
2687 self.cfg.AddOrphanDisk(name="mock_disk_1134", dev_type=constants.DT_BLOCK)
2688 op = self.CopyOpCode(self.op,
2689 disks=[[constants.DDM_ATTACH, -1,
2690 {
2691 constants.IDISK_NAME: "mock_disk_1134"
2692 }]],
2693 )
2694 self.ExecOpCodeExpectOpPrereqError(op, msg)
2695
2696 def testAttachDiskWrongNodes(self):
2697 msg = "Disk nodes are \['mock_node_1134'\]"
2698
2699 self.cfg.AddOrphanDisk(name="mock_disk_1134", primary_node="mock_node_1134")
2700 op = self.CopyOpCode(self.op,
2701 disks=[[constants.DDM_ATTACH, -1,
2702 {
2703 constants.IDISK_NAME: "mock_disk_1134"
2704 }]],
2705 )
2706 self.ExecOpCodeExpectOpPrereqError(op, msg)
2707
2708 def testAttachDiskRunningInstance(self):
2709 self.cfg.AddOrphanDisk(name="mock_disk_1134")
2710 self.rpc.call_blockdev_assemble.return_value = \
2711 self.RpcResultsBuilder() \
2712 .CreateSuccessfulNodeResult(self.master,
2713 ("/dev/mocked_path",
2714 "/var/run/ganeti/instance-disks/mocked_d",
2715 None))
2716 op = self.CopyOpCode(self.running_op,
2717 disks=[[constants.DDM_ATTACH, -1,
2718 {
2719 constants.IDISK_NAME: "mock_disk_1134"
2720 }]],
2721 )
2722 self.ExecOpCode(op)
2723 self.assertTrue(self.rpc.call_blockdev_assemble.called)
2724 self.assertFalse(self.rpc.call_blockdev_shutdown.called)
2725
2726 def testAttachDiskRunningInstanceNoWaitForSync(self):
2727 self.cfg.AddOrphanDisk(name="mock_disk_1134")
2728 self.rpc.call_blockdev_assemble.return_value = \
2729 self.RpcResultsBuilder() \
2730 .CreateSuccessfulNodeResult(self.master,
2731 ("/dev/mocked_path",
2732 "/var/run/ganeti/instance-disks/mocked_d",
2733 None))
2734 op = self.CopyOpCode(self.running_op,
2735 disks=[[constants.DDM_ATTACH, -1,
2736 {
2737 constants.IDISK_NAME: "mock_disk_1134"
2738 }]],
2739 wait_for_sync=False)
2740 self.ExecOpCode(op)
2741 self.assertTrue(self.rpc.call_blockdev_assemble.called)
2742 self.assertFalse(self.rpc.call_blockdev_shutdown.called)
2743
2744 def testAttachDiskDownInstance(self):
2745 self.cfg.AddOrphanDisk(name="mock_disk_1134")
2746 op = self.CopyOpCode(self.op,
2747 disks=[[constants.DDM_ATTACH, -1,
2748 {
2749 constants.IDISK_NAME: "mock_disk_1134"
2750 }]])
2751 self.ExecOpCode(op)
2752
2753 self.assertTrue(self.rpc.call_blockdev_assemble.called)
2754 self.assertTrue(self.rpc.call_blockdev_shutdown.called)
2755
2756 def testAttachDiskDownInstanceNoWaitForSync(self):
2757 self.cfg.AddOrphanDisk(name="mock_disk_1134")
2758 op = self.CopyOpCode(self.op,
2759 disks=[[constants.DDM_ATTACH, -1,
2760 {
2761 constants.IDISK_NAME: "mock_disk_1134"
2762 }]],
2763 wait_for_sync=False)
2764 self.ExecOpCodeExpectOpPrereqError(
2765 op, "Can't attach a disk to an instance with deactivated disks"
2766 " and --no-wait-for-sync given.")
2767
2768 def testHotAttachDisk(self):
2769 self.cfg.AddOrphanDisk(name="mock_disk_1134")
2770 self.rpc.call_blockdev_assemble.return_value = \
2771 self.RpcResultsBuilder() \
2772 .CreateSuccessfulNodeResult(self.master,
2773 ("/dev/mocked_path",
2774 "/var/run/ganeti/instance-disks/mocked_d",
2775 None))
2776 op = self.CopyOpCode(self.op,
2777 disks=[[constants.DDM_ATTACH, -1,
2778 {
2779 constants.IDISK_NAME: "mock_disk_1134"
2780 }]],
2781 hotplug=True)
2782 self.rpc.call_hotplug_supported.return_value = \
2783 self.RpcResultsBuilder() \
2784 .CreateSuccessfulNodeResult(self.master)
2785 self.ExecOpCode(op)
2786 self.assertTrue(self.rpc.call_hotplug_supported.called)
2787 self.assertTrue(self.rpc.call_blockdev_assemble.called)
2788 self.assertTrue(self.rpc.call_hotplug_device.called)
2789
2790 def testHotRemoveDisk(self):
2791 inst = self.cfg.AddNewInstance(disks=[self.cfg.CreateDisk(),
2792 self.cfg.CreateDisk()])
2793 op = self.CopyOpCode(self.op,
2794 instance_name=inst.name,
2795 disks=[[constants.DDM_REMOVE, -1,
2796 {}]],
2797 hotplug=True)
2798 self.rpc.call_hotplug_supported.return_value = \
2799 self.RpcResultsBuilder() \
2800 .CreateSuccessfulNodeResult(self.master)
2801 self.ExecOpCode(op)
2802 self.assertTrue(self.rpc.call_hotplug_supported.called)
2803 self.assertTrue(self.rpc.call_hotplug_device.called)
2804 self.assertTrue(self.rpc.call_blockdev_shutdown.called)
2805 self.assertTrue(self.rpc.call_blockdev_remove.called)
2806
2807 def testHotDetachDisk(self):
2808 inst = self.cfg.AddNewInstance(disks=[self.cfg.CreateDisk(),
2809 self.cfg.CreateDisk()])
2810 op = self.CopyOpCode(self.op,
2811 instance_name=inst.name,
2812 disks=[[constants.DDM_DETACH, -1,
2813 {}]],
2814 hotplug=True)
2815 self.rpc.call_hotplug_supported.return_value = \
2816 self.RpcResultsBuilder() \
2817 .CreateSuccessfulNodeResult(self.master)
2818 self.ExecOpCode(op)
2819 self.assertTrue(self.rpc.call_hotplug_supported.called)
2820 self.assertTrue(self.rpc.call_hotplug_device.called)
2821 self.assertTrue(self.rpc.call_blockdev_shutdown.called)
2822
2823 def testDetachAttachFileBasedDisk(self):
2824 """Detach and re-attach a disk from a file-based instance."""
2825 # Create our disk and calculate the path where it is stored, its name, as
2826 # well as the expected path where it will be moved.
2827 mock_disk = self.cfg.CreateDisk(
2828 name='mock_disk_1134', dev_type=constants.DT_FILE,
2829 logical_id=('loop', '/tmp/instance/disk'))
2830
2831 # Create a file-based instance
2832 file_disk = self.cfg.CreateDisk(
2833 dev_type=constants.DT_FILE,
2834 logical_id=('loop', '/tmp/instance/disk2'))
2835 inst = self.cfg.AddNewInstance(name='instance',
2836 disk_template=constants.DT_FILE,
2837 disks=[file_disk, mock_disk],
2838 )
2839
2840 # Detach the disk and assert that it has been moved to the upper directory
2841 op = self.CopyOpCode(self.op,
2842 instance_name=inst.name,
2843 disks=[[constants.DDM_DETACH, -1,
2844 {}]],
2845 )
2846 self.ExecOpCode(op)
2847 mock_disk = self.cfg.GetDiskInfo(mock_disk.uuid)
2848 self.assertEqual('/tmp/disk', mock_disk.logical_id[1])
2849
2850 # Re-attach the disk and assert that it has been moved to the original
2851 # directory
2852 op = self.CopyOpCode(self.op,
2853 instance_name=inst.name,
2854 disks=[[constants.DDM_ATTACH, -1,
2855 {
2856 constants.IDISK_NAME: "mock_disk_1134"
2857 }]],
2858 )
2859 self.ExecOpCode(op)
2860 mock_disk = self.cfg.GetDiskInfo(mock_disk.uuid)
2861 self.assertIn('/tmp/instance', mock_disk.logical_id[1])
2862
2863 def testAttachDetachDisk(self):
2864 """Check if the disks can be attached and detached in sequence.
2865
2866 Also, check if the operations succeed both with name and uuid.
2867 """
2868 disk1 = self.cfg.CreateDisk(uuid="mock_uuid_1134")
2869 disk2 = self.cfg.CreateDisk(name="mock_name_1134")
2870
2871 inst = self.cfg.AddNewInstance(disks=[disk1, disk2])
2872
2873 op = self.CopyOpCode(self.op,
2874 instance_name=inst.name,
2875 disks=[[constants.DDM_DETACH, "mock_uuid_1134",
2876 {}]])
2877 self.ExecOpCode(op)
2878 self.assertEqual([disk2], self.cfg.GetInstanceDisks(inst.uuid))
2879
2880 op = self.CopyOpCode(self.op,
2881 instance_name=inst.name,
2882 disks=[[constants.DDM_ATTACH, 0,
2883 {
2884 'uuid': "mock_uuid_1134"
2885 }]])
2886 self.ExecOpCode(op)
2887 self.assertEqual([disk1, disk2], self.cfg.GetInstanceDisks(inst.uuid))
2888
2889 op = self.CopyOpCode(self.op,
2890 instance_name=inst.name,
2891 disks=[[constants.DDM_DETACH, 1,
2892 {}]])
2893 self.ExecOpCode(op)
2894 self.assertEqual([disk1], self.cfg.GetInstanceDisks(inst.uuid))
2895
2896 op = self.CopyOpCode(self.op,
2897 instance_name=inst.name,
2898 disks=[[constants.DDM_ATTACH, 0,
2899 {
2900 constants.IDISK_NAME: "mock_name_1134"
2901 }]])
2902 self.ExecOpCode(op)
2903 self.assertEqual([disk2, disk1], self.cfg.GetInstanceDisks(inst.uuid))
2904
2905 def testRemoveDiskRemovesStorageDir(self):
2906 inst = self.cfg.AddNewInstance(disks=[self.cfg.CreateDisk(dev_type='file')])
2907 op = self.CopyOpCode(self.op,
2908 instance_name=inst.name,
2909 disks=[[constants.DDM_REMOVE, -1,
2910 {}]])
2911 self.rpc.call_file_storage_dir_remove.return_value = \
2912 self.RpcResultsBuilder() \
2913 .CreateSuccessfulNodeResult(self.master)
2914 self.ExecOpCode(op)
2915 self.rpc.call_file_storage_dir_remove.assert_called_with(
2916 self.master.uuid, '/file/storage')
2917
2918 def testRemoveDiskKeepsStorageForRemaining(self):
2919 inst = self.cfg.AddNewInstance(disks=[self.cfg.CreateDisk(dev_type='file'),
2920 self.cfg.CreateDisk(dev_type='file')])
2921 op = self.CopyOpCode(self.op,
2922 instance_name=inst.name,
2923 disks=[[constants.DDM_REMOVE, -1,
2924 {}]])
2925 self.rpc.call_file_storage_dir_remove.return_value = \
2926 self.RpcResultsBuilder() \
2927 .CreateSuccessfulNodeResult(self.master)
2928 self.ExecOpCode(op)
2929 self.assertFalse(self.rpc.call_file_storage_dir_remove.called)
2930
2931 def testModifyDiskWithSize(self):
2932 op = self.CopyOpCode(self.op,
2933 disks=[[constants.DDM_MODIFY, 0,
2934 {
2935 constants.IDISK_SIZE: 1024
2936 }]])
2937 self.ExecOpCodeExpectOpPrereqError(
2938 op, "Disk size change not possible, use grow-disk")
2939
2940 def testModifyDiskWithRandomParams(self):
2941 op = self.CopyOpCode(self.op,
2942 disks=[[constants.DDM_MODIFY, 0,
2943 {
2944 constants.IDISK_METAVG: "new_meta_vg",
2945 constants.IDISK_MODE: "invalid",
2946 constants.IDISK_NAME: "new_name"
2947 }]])
2948 self.ExecOpCodeExpectException(op, errors.TypeEnforcementError,
2949 "Unknown parameter 'metavg'")
2950
2951 def testModifyDiskUnsetName(self):
2952 op = self.CopyOpCode(self.op,
2953 disks=[[constants.DDM_MODIFY, 0,
2954 {
2955 constants.IDISK_NAME: constants.VALUE_NONE
2956 }]])
2957 self.ExecOpCode(op)
2958
2959 def testModifyExtDiskProvider(self):
2960 mod = [[constants.DDM_MODIFY, 0,
2961 {
2962 constants.IDISK_PROVIDER: "anything"
2963 }]]
2964 op = self.CopyOpCode(self.op, disks=mod)
2965 self.ExecOpCodeExpectException(op, errors.TypeEnforcementError,
2966 "Unknown parameter 'provider'")
2967
2968 op = self.CopyOpCode(self.ext_storage_op, disks=mod)
2969 self.ExecOpCodeExpectOpPrereqError(op, "Disk 'provider' parameter change"
2970 " is not possible")
2971
2972 def testSetOldDiskTemplate(self):
2973 op = self.CopyOpCode(self.op,
2974 disk_template=self.dev_type)
2975 self.ExecOpCodeExpectOpPrereqError(
2976 op, "Instance already has disk template")
2977
2978 def testSetDisabledDiskTemplate(self):
2979 self.cfg.SetEnabledDiskTemplates([self.inst.disk_template])
2980 op = self.CopyOpCode(self.op,
2981 disk_template=constants.DT_EXT)
2982 self.ExecOpCodeExpectOpPrereqError(
2983 op, "Disk template .* is not enabled for this cluster")
2984
2985 def testConvertToExtWithMissingProvider(self):
2986 op = self.CopyOpCode(self.op,
2987 disk_template=constants.DT_EXT)
2988 self.ExecOpCodeExpectOpPrereqError(
2989 op, "Missing provider for template .*")
2990
2991 def testConvertToNotExtWithProvider(self):
2992 op = self.CopyOpCode(self.op,
2993 disk_template=constants.DT_FILE,
2994 ext_params={constants.IDISK_PROVIDER: "pvdr"})
2995 self.ExecOpCodeExpectOpPrereqError(
2996 op, "The 'provider' option is only valid for the ext disk"
2997 " template, not .*")
2998
2999 def testConvertToExtWithSameProvider(self):
3000 op = self.CopyOpCode(self.ext_storage_op,
3001 disk_template=constants.DT_EXT,
3002 ext_params={constants.IDISK_PROVIDER: "pvdr"})
3003 self.ExecOpCodeExpectOpPrereqError(
3004 op, "Not converting, 'disk/0' of type ExtStorage already using"
3005 " provider 'pvdr'")
3006
3007 def testConvertToInvalidDiskTemplate(self):
3008 for disk_template in constants.DTS_NOT_CONVERTIBLE_TO:
3009 op = self.CopyOpCode(self.op,
3010 disk_template=disk_template)
3011 self.ExecOpCodeExpectOpPrereqError(
3012 op, "Conversion to the .* disk template is not supported")
3013
3014 def testConvertFromInvalidDiskTemplate(self):
3015 for disk_template in constants.DTS_NOT_CONVERTIBLE_FROM:
3016 inst = self.cfg.AddNewInstance(disk_template=disk_template)
3017 op = self.CopyOpCode(self.op,
3018 instance_name=inst.name,
3019 disk_template=constants.DT_PLAIN)
3020 self.ExecOpCodeExpectOpPrereqError(
3021 op, "Conversion from the .* disk template is not supported")
3022
3023 def testConvertToDRBDWithSecondarySameAsPrimary(self):
3024 op = self.CopyOpCode(self.op,
3025 disk_template=constants.DT_DRBD8,
3026 remote_node=self.master.name)
3027 self.ExecOpCodeExpectOpPrereqError(
3028 op, "Given new secondary node .* is the same as the primary node"
3029 " of the instance")
3030
3031 def testConvertPlainToDRBD(self):
3032 self.rpc.call_blockdev_shutdown.return_value = \
3033 self.RpcResultsBuilder() \
3034 .CreateSuccessfulNodeResult(self.master, True)
3035 self.rpc.call_blockdev_getmirrorstatus.return_value = \
3036 self.RpcResultsBuilder() \
3037 .CreateSuccessfulNodeResult(self.master, [objects.BlockDevStatus()])
3038
3039 op = self.CopyOpCode(self.op,
3040 disk_template=constants.DT_DRBD8,
3041 remote_node=self.snode.name)
3042 self.ExecOpCode(op)
3043
3044 def testConvertDRBDToPlain(self):
3045 for disk_uuid in self.inst.disks:
3046 self.cfg.RemoveInstanceDisk(self.inst.uuid, disk_uuid)
3047 disk = self.cfg.CreateDisk(dev_type=constants.DT_DRBD8,
3048 primary_node=self.master,
3049 secondary_node=self.snode)
3050 self.cfg.AddInstanceDisk(self.inst.uuid, disk)
3051 self.inst.disk_template = constants.DT_DRBD8
3052 self.rpc.call_blockdev_shutdown.return_value = \
3053 self.RpcResultsBuilder() \
3054 .CreateSuccessfulNodeResult(self.master, True)
3055 self.rpc.call_blockdev_remove.return_value = \
3056 self.RpcResultsBuilder() \
3057 .CreateSuccessfulNodeResult(self.master)
3058 self.rpc.call_blockdev_getmirrorstatus.return_value = \
3059 self.RpcResultsBuilder() \
3060 .CreateSuccessfulNodeResult(self.master, [objects.BlockDevStatus()])
3061
3062 op = self.CopyOpCode(self.op,
3063 disk_template=constants.DT_PLAIN)
3064 self.ExecOpCode(op)
3065
3066
3067 class TestLUInstanceChangeGroup(CmdlibTestCase):
3068 def setUp(self):
3069 super(TestLUInstanceChangeGroup, self).setUp()
3070
3071 self.group2 = self.cfg.AddNewNodeGroup()
3072 self.node2 = self.cfg.AddNewNode(group=self.group2)
3073 self.inst = self.cfg.AddNewInstance()
3074 self.op = opcodes.OpInstanceChangeGroup(instance_name=self.inst.name)
3075
3076 def testTargetGroupIsInstanceGroup(self):
3077 op = self.CopyOpCode(self.op,
3078 target_groups=[self.group.name])
3079 self.ExecOpCodeExpectOpPrereqError(
3080 op, "Can't use group\(s\) .* as targets, they are used by the"
3081 " instance .*")
3082
3083 def testNoTargetGroups(self):
3084 inst = self.cfg.AddNewInstance(disk_template=constants.DT_DRBD8,
3085 primary_node=self.master,
3086 secondary_node=self.node2)
3087 op = self.CopyOpCode(self.op,
3088 instance_name=inst.name)
3089 self.ExecOpCodeExpectOpPrereqError(
3090 op, "There are no possible target groups")
3091
3092 def testFailingIAllocator(self):
3093 self.iallocator_cls.return_value.success = False
3094 op = self.CopyOpCode(self.op)
3095
3096 self.ExecOpCodeExpectOpPrereqError(
3097 op, "Can't compute solution for changing group of instance .*"
3098 " using iallocator .*")
3099
3100 def testChangeGroup(self):
3101 self.iallocator_cls.return_value.success = True
3102 self.iallocator_cls.return_value.result = ([], [], [])
3103 op = self.CopyOpCode(self.op)
3104
3105 self.ExecOpCode(op)
3106
3107
3108 if __name__ == "__main__":
3109 testutils.GanetiTestProgram()