4 # Copyright (C) 2008, 2011, 2012, 2013 Google Inc.
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions are
11 # 1. Redistributions of source code must retain the above copyright notice,
12 # this list of conditions and the following disclaimer.
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.
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.
31 """Tests for LUInstance*
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
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
59 from cmdlib
.cmdlib_unittest
import _FakeLU
61 from testsupport
import *
66 class TestComputeIPolicyInstanceSpecViolation(unittest
.TestCase
):
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,
76 self
.stub
= mock
.MagicMock()
77 self
.stub
.return_value
= []
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
])
87 class TestLUInstanceCreate(CmdlibTestCase
):
88 def _setupOSDiagnose(self
):
89 os_result
= [(self
.os
.name
,
93 self
.os
.supported_variants
,
94 self
.os
.supported_parameters
,
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
) \
105 super(TestLUInstanceCreate
, self
).setUp()
108 self
.MockOut(instance_create
, 'netutils', self
.netutils_mod
)
109 self
.MockOut(instance_utils
, 'netutils', self
.netutils_mod
)
111 self
.net
= self
.cfg
.AddNewNetwork()
112 self
.cfg
.ConnectNetworkToGroup(self
.net
, self
.group
)
114 self
.node1
= self
.cfg
.AddNewNode()
115 self
.node2
= self
.cfg
.AddNewNode()
119 "type": constants
.ST_LVM_VG
,
120 "storage_free": 10000
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
) \
130 self
._setupOSDiagnose()
132 self
.rpc
.call_blockdev_getmirrorstatus
.side_effect
= \
133 lambda node
, _
: self
.RpcResultsBuilder() \
134 .CreateSuccessfulNodeResult(node
, [])
136 self
.iallocator_cls
.return_value
.result
= [self
.node1
.name
, self
.node2
.name
]
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
,
145 os_type
=self
.os_name_variant
)
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
,
154 constants
.IDISK_SIZE
: 1024
156 os_type
=self
.os_name_variant
)
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
,
165 constants
.IDISK_SIZE
: 1024,
166 constants
.IDISK_ADOPT
: "/dev/disk/block0"
168 os_type
=self
.os_name_variant
)
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
,
178 constants
.IDISK_SIZE
: 1024
180 os_type
=self
.os_name_variant
)
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
,
189 constants
.IDISK_SIZE
: 1024
191 os_type
=self
.os_name_variant
)
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
,
200 constants
.IDISK_SIZE
: 1024
202 os_type
=self
.os_name_variant
)
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
,
211 constants
.IDISK_SIZE
: 1024
213 os_type
=self
.os_name_variant
)
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
,
222 constants
.IDISK_SIZE
: 1024
224 os_type
=self
.os_name_variant
)
226 def testSimpleCreate(self
):
227 op
= self
.CopyOpCode(self
.diskless_op
)
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")
237 def testOpportunisticLockingNoIAllocator(self
):
238 op
= self
.CopyOpCode(self
.diskless_op
,
239 opportunistic_locking
=True,
241 self
.ExecOpCodeExpectOpPrereqError(
242 op
, "Opportunistic locking is only available in combination with an"
243 " instance allocator")
245 def testNicWithNetAndMode(self
):
246 op
= self
.CopyOpCode(self
.diskless_op
,
248 constants
.INIC_NETWORK
: self
.net
.name
,
249 constants
.INIC_MODE
: constants
.NIC_MODE_BRIDGED
251 self
.ExecOpCodeExpectOpPrereqError(
252 op
, "If network is given, no mode or link is allowed to be passed")
254 def testAutoIpNoNameCheck(self
):
255 op
= self
.CopyOpCode(self
.diskless_op
,
257 constants
.INIC_IP
: constants
.VALUE_AUTO
261 self
.ExecOpCodeExpectOpPrereqError(
262 op
, "IP address set to auto but name checks have been skipped")
264 def testAutoIp(self
):
265 op
= self
.CopyOpCode(self
.diskless_op
,
267 constants
.INIC_IP
: constants
.VALUE_AUTO
271 def testPoolIpNoNetwork(self
):
272 op
= self
.CopyOpCode(self
.diskless_op
,
274 constants
.INIC_IP
: constants
.NIC_IP_POOL
276 self
.ExecOpCodeExpectOpPrereqError(
277 op
, "if ip=pool, parameter network must be passed too")
279 def testValidIp(self
):
280 op
= self
.CopyOpCode(self
.diskless_op
,
282 constants
.INIC_IP
: "203.0.113.1"
286 def testRoutedNoIp(self
):
287 op
= self
.CopyOpCode(self
.diskless_op
,
289 constants
.INIC_NETWORK
: constants
.VALUE_NONE
,
290 constants
.INIC_MODE
: constants
.NIC_MODE_ROUTED
292 self
.ExecOpCodeExpectOpPrereqError(
293 op
, "Routed nic mode requires an ip address"
294 " if not attached to a network")
296 def testValicMac(self
):
297 op
= self
.CopyOpCode(self
.diskless_op
,
299 constants
.INIC_MAC
: "f0:df:f4:a3:d1:cf"
303 def testValidNicParams(self
):
304 op
= self
.CopyOpCode(self
.diskless_op
,
306 constants
.INIC_MODE
: constants
.NIC_MODE_BRIDGED
,
307 constants
.INIC_LINK
: "br_mock"
311 def testValidNicParamsOpenVSwitch(self
):
312 op
= self
.CopyOpCode(self
.diskless_op
,
314 constants
.INIC_MODE
: constants
.NIC_MODE_OVS
,
315 constants
.INIC_VLAN
: "1"
319 def testNicNoneName(self
):
320 op
= self
.CopyOpCode(self
.diskless_op
,
322 constants
.INIC_NAME
: constants
.VALUE_NONE
326 def testConflictingIP(self
):
327 op
= self
.CopyOpCode(self
.diskless_op
,
329 constants
.INIC_IP
: self
.net
.gateway
[:-1] + "2"
331 self
.ExecOpCodeExpectOpPrereqError(
332 op
, "The requested IP address .* belongs to network .*, but the target"
335 def testVLanFormat(self
):
336 for vlan
in [".pinky", ":bunny", ":1:pinky", "bunny"]:
338 op
= self
.CopyOpCode(self
.diskless_op
,
340 constants
.INIC_VLAN
: vlan
342 self
.ExecOpCodeExpectOpPrereqError(
343 op
, "Specified VLAN parameter is invalid")
345 def testPoolIp(self
):
346 op
= self
.CopyOpCode(self
.diskless_op
,
348 constants
.INIC_IP
: constants
.NIC_IP_POOL
,
349 constants
.INIC_NETWORK
: self
.net
.name
353 def testPoolIpUnconnectedNetwork(self
):
354 net
= self
.cfg
.AddNewNetwork()
355 op
= self
.CopyOpCode(self
.diskless_op
,
357 constants
.INIC_IP
: constants
.NIC_IP_POOL
,
358 constants
.INIC_NETWORK
: net
.name
360 self
.ExecOpCodeExpectOpPrereqError(
361 op
, "No netparams found for network .*.")
363 def testIpNotInNetwork(self
):
364 op
= self
.CopyOpCode(self
.diskless_op
,
366 constants
.INIC_IP
: "203.0.113.1",
367 constants
.INIC_NETWORK
: self
.net
.name
369 self
.ExecOpCodeExpectOpPrereqError(
370 op
, "IP address .* already in use or does not belong to network .*")
372 def testMixAdoptAndNotAdopt(self
):
373 op
= self
.CopyOpCode(self
.diskless_op
,
374 disk_template
=constants
.DT_PLAIN
,
376 constants
.IDISK_ADOPT
: "lv1"
378 self
.ExecOpCodeExpectOpPrereqError(
379 op
, "Either all disks are adopted or none is")
381 def testMustAdoptWithoutAdopt(self
):
382 op
= self
.CopyOpCode(self
.diskless_op
,
383 disk_template
=constants
.DT_BLOCK
,
385 self
.ExecOpCodeExpectOpPrereqError(
386 op
, "Disk template blockdev requires disk adoption, but no 'adopt'"
389 def testDontAdoptWithAdopt(self
):
390 op
= self
.CopyOpCode(self
.diskless_op
,
391 disk_template
=constants
.DT_DRBD8
,
393 constants
.IDISK_ADOPT
: "lv1"
395 self
.ExecOpCodeExpectOpPrereqError(
396 op
, "Disk adoption is not supported for the 'drbd' disk template")
398 def testAdoptWithIAllocator(self
):
399 op
= self
.CopyOpCode(self
.diskless_op
,
400 disk_template
=constants
.DT_PLAIN
,
402 constants
.IDISK_ADOPT
: "lv1"
405 self
.ExecOpCodeExpectOpPrereqError(
406 op
, "Disk adoption not allowed with an iallocator script")
408 def testAdoptWithImport(self
):
409 op
= self
.CopyOpCode(self
.diskless_op
,
410 disk_template
=constants
.DT_PLAIN
,
412 constants
.IDISK_ADOPT
: "lv1"
414 mode
=constants
.INSTANCE_IMPORT
)
415 self
.ExecOpCodeExpectOpPrereqError(
416 op
, "Disk adoption not allowed for instance import")
418 def testArgumentCombinations(self
):
419 op
= self
.CopyOpCode(self
.diskless_op
,
420 # start flag will be flipped
423 # no allowed combination
426 self
.ExecOpCodeExpectOpPrereqError(
427 op
, "Cannot do IP address check without a name check")
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")
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")
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
)
451 self
.mcpu
.assertLogContainsRegex(
452 "Secondary node will be ignored on non-mirrored disk template")
454 def testMissingOsType(self
):
455 op
= self
.CopyOpCode(self
.diskless_op
,
457 self
.ExecOpCodeExpectOpPrereqError(op
, "No guest OS or OS image specified")
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")
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
)
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")
478 def testPlainInstance(self
):
479 op
= self
.CopyOpCode(self
.plain_op
)
482 def testPlainIAllocator(self
):
483 op
= self
.CopyOpCode(self
.plain_op
,
488 def testIAllocatorOpportunisticLocking(self
):
489 op
= self
.CopyOpCode(self
.plain_op
,
492 opportunistic_locking
=True)
495 def testFailingIAllocator(self
):
496 self
.iallocator_cls
.return_value
.success
= False
497 op
= self
.CopyOpCode(self
.plain_op
,
500 self
.ExecOpCodeExpectOpPrereqError(
501 op
, "Can't compute nodes using iallocator")
503 def testDrbdInstance(self
):
504 op
= self
.CopyOpCode(self
.drbd_op
)
507 def testDrbdIAllocator(self
):
508 op
= self
.CopyOpCode(self
.drbd_op
,
514 def testFileInstance(self
):
515 op
= self
.CopyOpCode(self
.file_op
)
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")
524 def testFileInstanceAdditionalPath(self
):
525 op
= self
.CopyOpCode(self
.file_op
,
526 file_storage_dir
="mock_dir")
529 def testIdentifyDefaults(self
):
530 op
= self
.CopyOpCode(self
.plain_op
,
532 constants
.HV_BOOT_ORDER
: "cd"
534 beparams
=constants
.BEC_DEFAULTS
.copy(),
536 constants
.NIC_MODE
: constants
.NIC_MODE_BRIDGED
539 self
.os_name_variant
: {}
542 identify_defaults
=True)
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
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")
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")
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")
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")
577 def testAddTag(self
):
578 op
= self
.CopyOpCode(self
.diskless_op
,
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")
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")
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")
599 def testPrimarySecondaryDifferentNodeGroups(self
):
600 group
= self
.cfg
.AddNewNodeGroup()
601 self
.node2
.group
= group
.uuid
602 op
= self
.CopyOpCode(self
.drbd_op
)
604 self
.mcpu
.assertLogContainsRegex(
605 "The primary and secondary nodes are in two different node groups")
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")
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)
620 op
= self
.CopyOpCode(self
.plain_op
)
621 op
.disks
[0].update({constants
.IDISK_ADOPT
: "mock_disk_1"})
624 def testAdoptPlainMissingLv(self
):
625 self
.rpc
.call_lv_list
.return_value
= \
626 self
.RpcResultsBuilder() \
627 .AddSuccessfulNode(self
.master
, {}) \
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")
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)
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")
645 def testAdoptBlock(self
):
646 self
.rpc
.call_bdev_sizes
.return_value
= \
647 self
.RpcResultsBuilder() \
648 .AddSuccessfulNode(self
.master
, {
649 "/dev/disk/block0": 10000
652 op
= self
.CopyOpCode(self
.block_op
)
655 def testAdoptBlockDuplicateNames(self
):
656 op
= self
.CopyOpCode(self
.block_op
,
658 constants
.IDISK_SIZE
: 0,
659 constants
.IDISK_ADOPT
: "/dev/disk/block0"
661 constants
.IDISK_SIZE
: 0,
662 constants
.IDISK_ADOPT
: "/dev/disk/block0"
664 self
.ExecOpCodeExpectOpPrereqError(
665 op
, "Duplicate disk names given for adoption")
667 def testAdoptBlockInvalidNames(self
):
668 op
= self
.CopyOpCode(self
.block_op
,
670 constants
.IDISK_SIZE
: 0,
671 constants
.IDISK_ADOPT
: "/invalid/block0"
673 self
.ExecOpCodeExpectOpPrereqError(
674 op
, "Device node.* lie outside .* and cannot be adopted")
676 def testAdoptBlockMissingDisk(self
):
677 self
.rpc
.call_bdev_sizes
.return_value
= \
678 self
.RpcResultsBuilder() \
679 .AddSuccessfulNode(self
.master
, {}) \
681 op
= self
.CopyOpCode(self
.block_op
)
682 self
.ExecOpCodeExpectOpPrereqError(op
, "Missing block device")
684 def testNoWaitForSyncDrbd(self
):
685 op
= self
.CopyOpCode(self
.drbd_op
,
689 def testNoWaitForSyncPlain(self
):
690 op
= self
.CopyOpCode(self
.plain_op
,
694 def testImportPlainFromGivenSrcNode(self
):
700 name=old_name.example.com
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
)
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")
717 def testImportPlainWithoutSrcNode(self
):
723 name=old_name.example.com
726 self
.rpc
.call_export_list
.return_value
= \
727 self
.RpcResultsBuilder() \
728 .AddSuccessfulNode(self
.master
, {"mock_path": {}}) \
730 self
.rpc
.call_export_info
.return_value
= \
731 self
.RpcResultsBuilder() \
732 .CreateSuccessfulNodeResult(self
.master
, exp_info
)
734 op
= self
.CopyOpCode(self
.plain_op
,
735 mode
=constants
.INSTANCE_IMPORT
,
736 src_path
="mock_path")
739 def testImportPlainCorruptExportInfo(self
):
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")
750 def testImportPlainWrongExportInfoVersion(self
):
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")
763 def testImportPlainWithParametersAndImport(self
):
769 name=old_name.example.com
775 nic0_mac=f6:ab:f4:45:d1:af
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
,
798 objects
.ImportExportStatus(exit_status
=0)
800 self
.rpc
.call_impexp_cleanup
.return_value
= \
801 self
.RpcResultsBuilder() \
802 .CreateSuccessfulNodeResult(self
.master
, True)
804 op
= self
.CopyOpCode(self
.plain_op
,
810 mode
=constants
.INSTANCE_IMPORT
,
811 src_node
=self
.master
.name
)
815 class TestDiskTemplateDiskTypeBijection(TestLUInstanceCreate
):
816 """Tests that one disk template corresponds to exactly one disk type."""
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]
824 def testDiskTemplateLogicalIdBijectionDiskless(self
):
825 op
= self
.CopyOpCode(self
.diskless_op
)
827 instance
= self
.GetSingleInstance()
828 self
.assertEqual(instance
.disk_template
, constants
.DT_DISKLESS
)
829 self
.assertEqual(instance
.disks
, [])
831 def testDiskTemplateLogicalIdBijectionPlain(self
):
832 op
= self
.CopyOpCode(self
.plain_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
)
839 def testDiskTemplateLogicalIdBijectionBlock(self
):
840 self
.rpc
.call_bdev_sizes
.return_value
= \
841 self
.RpcResultsBuilder() \
842 .AddSuccessfulNode(self
.master
, {
843 "/dev/disk/block0": 10000
846 op
= self
.CopyOpCode(self
.block_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
)
853 def testDiskTemplateLogicalIdBijectionDrbd(self
):
854 op
= self
.CopyOpCode(self
.drbd_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
)
861 def testDiskTemplateLogicalIdBijectionFile(self
):
862 op
= self
.CopyOpCode(self
.file_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
)
869 def testDiskTemplateLogicalIdBijectionSharedFile(self
):
870 self
.cluster
.shared_file_storage_dir
= '/tmp'
871 op
= self
.CopyOpCode(self
.shared_file_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
)
878 def testDiskTemplateLogicalIdBijectionGluster(self
):
879 self
.cluster
.gluster_storage_dir
= '/tmp'
880 op
= self
.CopyOpCode(self
.gluster_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
)
887 def testDiskTemplateLogicalIdBijectionRbd(self
):
888 op
= self
.CopyOpCode(self
.rbd_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
)
896 class TestCheckOSVariant(CmdlibTestCase
):
897 def testNoVariantsSupported(self
):
898 os
= self
.cfg
.CreateOs(supported_variants
=[])
899 self
.assertRaises(backend
.RPCFail
, backend
._CheckOSVariant
,
902 def testNoVariantGiven(self
):
903 os
= self
.cfg
.CreateOs(supported_variants
=["default"])
904 self
.assertRaises(backend
.RPCFail
, backend
._CheckOSVariant
,
907 def testWrongVariantGiven(self
):
908 os
= self
.cfg
.CreateOs(supported_variants
=["default"])
909 self
.assertRaises(backend
.RPCFail
, backend
._CheckOSVariant
,
910 os
, "os+wrong_variant")
912 def testOkWithVariant(self
):
913 os
= self
.cfg
.CreateOs(supported_variants
=["default"])
914 backend
._CheckOSVariant(os
, "os+default")
916 def testOkWithoutVariant(self
):
917 os
= self
.cfg
.CreateOs(supported_variants
=[])
918 backend
._CheckOSVariant(os
, "os")
921 class TestCheckTargetNodeIPolicy(TestLUInstanceCreate
):
923 super(TestCheckTargetNodeIPolicy
, self
).setUp()
925 self
.op
= self
.diskless_op
927 self
.instance
= self
.cfg
.AddNewInstance()
928 self
.target_group
= self
.cfg
.AddNewNodeGroup()
929 self
.target_node
= self
.cfg
.AddNewNode(group
=self
.target_group
)
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()
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()
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
)
962 class TestIndexOperations(unittest
.TestCase
):
964 """Test if index operations on containers work as expected."""
966 def testGetIndexFromIdentifierTail(self
):
967 """Check if -1 is translated to tail index."""
968 container
= ['item1134']
970 idx
= instance_utils
.GetIndexFromIdentifier("-1", "test", container
)
971 self
.assertEqual(1, idx
)
973 def testGetIndexFromIdentifierEmpty(self
):
974 """Check if empty containers return 0 as index."""
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
)
982 def testGetIndexFromIdentifierError(self
):
983 """Check if wrong input raises an exception."""
986 self
.assertRaises(errors
.OpPrereqError
,
987 instance_utils
.GetIndexFromIdentifier
,
988 "lala", "test", container
)
990 def testGetIndexFromIdentifierOffByOne(self
):
991 """Check for off-by-one errors."""
994 self
.assertRaises(IndexError, instance_utils
.GetIndexFromIdentifier
,
995 "1", "test", container
)
997 def testGetIndexFromIdentifierOutOfRange(self
):
998 """Check for identifiers out of the container range."""
1001 self
.assertRaises(IndexError, instance_utils
.GetIndexFromIdentifier
,
1002 "-1134", "test", container
)
1003 self
.assertRaises(IndexError, instance_utils
.GetIndexFromIdentifier
,
1004 "1134", "test", container
)
1006 def testInsertItemtoIndex(self
):
1007 """Test if we can insert an item to a container at a specified index."""
1010 instance_utils
.InsertItemToIndex(0, 2, container
)
1011 self
.assertEqual([2], container
)
1013 instance_utils
.InsertItemToIndex(0, 1, container
)
1014 self
.assertEqual([1, 2], container
)
1016 instance_utils
.InsertItemToIndex(-1, 3, container
)
1017 self
.assertEqual([1, 2, 3], container
)
1019 self
.assertRaises(AssertionError, instance_utils
.InsertItemToIndex
, -2,
1022 self
.assertRaises(AssertionError, instance_utils
.InsertItemToIndex
, 4, 1134,
1026 class TestApplyContainerMods(unittest
.TestCase
):
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
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
1043 @param chgdesc: List of applied changes
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
)
1054 def _insertContainerSuccessFn(self
, op
):
1056 inp
= [(op
, -1, "Hello"),
1061 expected
= ["Start", "Hello", "World", "End"]
1062 self
.applyAndAssert(container
, inp
, expected
)
1064 inp
= [(op
, 0, "zero"),
1069 expected
= ["zero", "Start", "Hello", "Added", "World", "four", "End",
1071 self
.applyAndAssert(container
, inp
, expected
)
1073 def _insertContainerErrorFn(self
, op
):
1077 inp
= [(op
, 1, "error"), ]
1078 self
.assertRaises(IndexError, self
.applyAndAssert
, container
, inp
,
1081 inp
= [(op
, -2, "error"), ]
1082 self
.assertRaises(IndexError, self
.applyAndAssert
, container
, inp
,
1085 def _extractContainerSuccessFn(self
, op
):
1086 container
= ["item1", "item2", "item3", "item4", "item5"]
1087 inp
= [(op
, -1, None),
1091 expected
= ["item2", "item4"]
1092 chgdesc
= [('test/4', op
),
1096 self
.applyAndAssert(container
, inp
, expected
, chgdesc
)
1098 def _extractContainerErrorFn(self
, op
):
1102 inp
= [(op
, 0, None), ]
1103 self
.assertRaises(IndexError, self
.applyAndAssert
, container
, inp
,
1106 inp
= [(op
, -1, None), ]
1107 self
.assertRaises(IndexError, self
.applyAndAssert
, container
, inp
,
1110 inp
= [(op
, 2, None), ]
1111 self
.assertRaises(IndexError, self
.applyAndAssert
, container
, inp
,
1114 inp
= [(op
, 0, None), ]
1116 self
.assertRaises(AssertionError, self
.applyAndAssert
, container
, inp
,
1119 def testEmptyContainer(self
):
1122 instance_utils
.ApplyContainerMods("test", container
, chgdesc
, [], None,
1123 None, None, None, None)
1124 self
.assertEqual(container
, [])
1125 self
.assertEqual(chgdesc
, [])
1127 def testAddSuccess(self
):
1128 self
._insertContainerSuccessFn(constants
.DDM_ADD
)
1130 def testAddError(self
):
1131 self
._insertContainerErrorFn(constants
.DDM_ADD
)
1133 def testAttachSuccess(self
):
1134 self
._insertContainerSuccessFn(constants
.DDM_ATTACH
)
1136 def testAttachError(self
):
1137 self
._insertContainerErrorFn(constants
.DDM_ATTACH
)
1139 def testRemoveSuccess(self
):
1140 self
._extractContainerSuccessFn(constants
.DDM_REMOVE
)
1142 def testRemoveError(self
):
1143 self
._extractContainerErrorFn(constants
.DDM_REMOVE
)
1145 def testDetachSuccess(self
):
1146 self
._extractContainerSuccessFn(constants
.DDM_DETACH
)
1148 def testDetachError(self
):
1149 self
._extractContainerErrorFn(constants
.DDM_DETACH
)
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"),
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
, [])
1164 for idx
in [-2, len(container
) + 1]:
1165 mods
= instance_utils
.PrepareContainerMods([
1166 (constants
.DDM_MODIFY
, idx
, "error"),
1168 self
.assertRaises(IndexError, instance_utils
.ApplyContainerMods
,
1169 "test", container
, None, mods
, None, None, None, None,
1173 def _CreateTestFn(idx
, params
, private
):
1174 private
.data
= ("add", idx
, params
)
1175 return ((100 * idx
, params
), [
1176 ("test/%s" % idx
, hex(idx
)),
1180 def _AttachTestFn(idx
, params
, private
):
1181 private
.data
= ("attach", idx
, params
)
1182 return ((100 * idx
, params
), [
1183 ("test/%s" % idx
, hex(idx
)),
1187 def _ModifyTestFn(idx
, item
, params
, private
):
1188 private
.data
= ("modify", idx
, params
)
1190 ("test/%s" % idx
, "modify %s" % params
),
1194 def _RemoveTestFn(idx
, item
, private
):
1195 private
.data
= ("remove", idx
, item
)
1198 def _DetachTestFn(idx
, item
, private
):
1199 private
.data
= ("detach", idx
, item
)
1201 def testAddWithCreateFunction(self
):
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"),
1216 instance_utils
.ApplyContainerMods("test", container
, chgdesc
, mods
,
1217 self
._CreateTestFn
, self
._AttachTestFn
,
1218 self
._ModifyTestFn
, self
._RemoveTestFn
,
1220 self
.assertEqual(container
, [
1225 self
.assertEqual(chgdesc
, [
1230 ("test/2", "remove"),
1231 ("test/2", "modify foobar"),
1232 ("test/2", "remove"),
1234 ("test/2", "detach"),
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"),
1244 ("remove", 2, (100, "World")),
1245 ("modify", 2, "foobar"),
1246 ("remove", 2, (300, "End")),
1248 ("detach", 2, (000, "Hello")),
1249 ("attach", 0, "Hello"),
1253 class _FakeConfigForGenDiskTemplate(ConfigMock
):
1255 super(_FakeConfigForGenDiskTemplate
, self
).__init__()
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()
1262 def GenerateUniqueID(self
, ec_id
):
1263 return "ec%s-uq%s" % (ec_id
, self
._unique_id
.next())
1265 def AllocateDRBDMinor(self
, nodes
, disk
):
1266 return [self
._drbd_minor
.next()
1269 def AllocatePort(self
):
1270 return self
._port
.next()
1272 def GenerateDRBDSecret(self
, ec_id
):
1273 return "ec%s-secret%s" % (ec_id
, self
._secret
.next())
1276 class TestGenerateDiskTemplate(CmdlibTestCase
):
1278 super(TestGenerateDiskTemplate
, self
).setUp()
1280 self
.cfg
= _FakeConfigForGenDiskTemplate()
1281 self
.cluster
.enabled_disk_templates
= list(constants
.DISK_TEMPLATES
)
1283 self
.nodegroup
= self
.cfg
.AddNewNodeGroup(name
="ng")
1285 self
.lu
= self
.GetMockLU()
1288 def GetDiskParams():
1289 return copy
.deepcopy(constants
.DISK_DT_DEFAULTS
)
1291 def testWrongDiskTemplate(self
):
1292 gdt
= instance_storage
.GenerateDiskTemplate
1293 disk_template
= "##unknown##"
1295 assert disk_template
not in constants
.DISK_TEMPLATES
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())
1302 def testDiskless(self
):
1303 gdt
= instance_storage
.GenerateDiskTemplate
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
, [])
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
1316 map(lambda params
: utils
.ForceDictType(params
,
1317 constants
.IDISK_PARAMS_TYPES
),
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())
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())
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)
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
))
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
)])
1349 def testPlain(self
):
1351 constants
.IDISK_SIZE
: 1024,
1352 constants
.IDISK_MODE
: constants
.DISK_RDWR
,
1354 constants
.IDISK_SIZE
: 4096,
1355 constants
.IDISK_VG
: "othervg",
1356 constants
.IDISK_MODE
: constants
.DISK_RDWR
,
1359 result
= self
._TestTrivialDisk(constants
.DT_PLAIN
, disk_info
, 3,
1362 self
.assertEqual(map(operator
.attrgetter("logical_id"), result
), [
1363 ("xenvg", "ec1-uq0.disk3"),
1364 ("othervg", "ec1-uq1.disk4"),
1366 self
.assertEqual(map(operator
.attrgetter("nodes"), result
), [
1367 ["node21741.example.com"], ["node21741.example.com"]])
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)
1378 for disk_template
in constants
.DTS_FILEBASED
:
1380 constants
.IDISK_SIZE
: 80 * 1024,
1381 constants
.IDISK_MODE
: constants
.DISK_RDONLY
,
1383 constants
.IDISK_SIZE
: 4096,
1384 constants
.IDISK_MODE
: constants
.DISK_RDWR
,
1386 constants
.IDISK_SIZE
: 6 * 1024,
1387 constants
.IDISK_MODE
: constants
.DISK_RDWR
,
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
)
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
)
1401 self
.assertEqual(map(operator
.attrgetter("logical_id"), result
),
1403 self
.assertEqual(map(operator
.attrgetter("nodes"), result
), [
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"]])
1411 self
.assertEqual(map(operator
.attrgetter("nodes"), result
), [
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)
1421 def testBlock(self
):
1423 constants
.IDISK_SIZE
: 8 * 1024,
1424 constants
.IDISK_MODE
: constants
.DISK_RDWR
,
1425 constants
.IDISK_ADOPT
: "/tmp/some/block/dev",
1428 result
= self
._TestTrivialDisk(constants
.DT_BLOCK
, disk_info
, 10,
1431 self
.assertEqual(map(operator
.attrgetter("logical_id"), result
), [
1432 (constants
.BLOCKDEV_DRIVER_MANUAL
, "/tmp/some/block/dev"),
1434 self
.assertEqual(map(operator
.attrgetter("nodes"), result
), [[]])
1438 constants
.IDISK_SIZE
: 8 * 1024,
1439 constants
.IDISK_MODE
: constants
.DISK_RDONLY
,
1441 constants
.IDISK_SIZE
: 100 * 1024,
1442 constants
.IDISK_MODE
: constants
.DISK_RDWR
,
1445 result
= self
._TestTrivialDisk(constants
.DT_RBD
, disk_info
, 0,
1448 self
.assertEqual(map(operator
.attrgetter("logical_id"), result
), [
1449 ("rbd", "ec1-uq0.rbd.disk0"),
1450 ("rbd", "ec1-uq1.rbd.disk1"),
1452 self
.assertEqual(map(operator
.attrgetter("nodes"), result
), [[], []])
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
]
1460 constants
.IDISK_SIZE
: 1024,
1461 constants
.IDISK_MODE
: constants
.DISK_RDWR
,
1463 constants
.IDISK_SIZE
: 100 * 1024,
1464 constants
.IDISK_MODE
: constants
.DISK_RDONLY
,
1465 constants
.IDISK_METAVG
: "metavg",
1467 constants
.IDISK_SIZE
: 4096,
1468 constants
.IDISK_MODE
: constants
.DISK_RDWR
,
1469 constants
.IDISK_VG
: "vgxyz",
1475 (self
.lu
.cfg
.GetVGName(), "ec1-uq0.disk0_data"),
1476 (drbd8_default_metavg
, "ec1-uq0.disk0_meta"),
1478 (self
.lu
.cfg
.GetVGName(), "ec1-uq1.disk1_data"),
1479 ("metavg", "ec1-uq1.disk1_meta"),
1481 ("vgxyz", "ec1-uq2.disk2_data"),
1482 (drbd8_default_metavg
, "ec1-uq2.disk2_meta"),
1485 exp_nodes
= ["node1334.example.com", "node12272.example.com"]
1487 assert len(exp_logical_ids
) == len(disk_info
)
1489 map(lambda params
: utils
.ForceDictType(params
,
1490 constants
.IDISK_PARAMS_TYPES
),
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())
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())
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
])
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
)
1516 self
.assertEqual(map(operator
.attrgetter("logical_id"), disk
.children
),
1517 exp_logical_ids
[idx
])
1518 self
.assertEqual(disk
.nodes
, exp_nodes
)
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
)
1524 self
._CheckIvNames(result
, 0, len(disk_info
))
1525 config
._UpdateIvNames(0, result
)
1526 self
._CheckIvNames(result
, 0, len(disk_info
))
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"),
1538 class _DiskPauseTracker
:
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
))
1546 self
.history
.extend((i
.logical_id
, i
.size
, pause
)
1549 return (True, [True] * len(disks
))
1552 class _ConfigForDiskWipe
:
1553 def __init__(self
, exp_node_uuid
, disks
):
1554 self
._exp_node_uuid
= exp_node_uuid
1557 def GetNodeName(self
, node_uuid
):
1558 assert node_uuid
== self
._exp_node_uuid
1559 return "name.of.expected.node"
1561 def GetInstanceDisks(self
, _
):
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
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
))
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
))
1580 class _DiskWipeProgressTracker
:
1581 def __init__(self
, start_offset
):
1582 self
._start_offset
= start_offset
1585 def __call__(self
, (disk
, _
), offset
, size
):
1586 assert isinstance(offset
, (long, int))
1587 assert isinstance(size
, (long, int))
1589 max_chunk_size
= (disk
.size
/ 100.0 * constants
.MIN_WIPE_CHUNK_PERCENT
)
1591 assert offset
>= self
._start_offset
1592 assert (offset
+ size
) <= disk
.size
1595 assert size
<= constants
.MAX_WIPE_CHUNK
1596 assert size
<= max_chunk_size
1598 assert offset
== self
._start_offset
or disk
.logical_id
in self
.progress
1600 # Keep track of progress
1601 cur_progress
= self
.progress
.setdefault(disk
.logical_id
, self
._start_offset
)
1603 assert cur_progress
== offset
1606 self
.progress
[disk
.logical_id
] += size
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")
1618 def testPauseFailure(self
):
1619 node_name
= "node1372.example.com"
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"),
1627 lu
= _FakeLU(rpc
=_RpcForDiskWipe(node_name
, self
._FailingPauseCb
,
1629 cfg
=_ConfigForDiskWipe(node_name
, disks
))
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
])
1636 self
.assertRaises(errors
.OpExecError
, instance_create
.WipeDisks
, lu
, inst
)
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)
1643 def testFailingWipe(self
):
1644 node_uuid
= "node13445-uuid"
1645 pt
= _DiskPauseTracker()
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"),
1656 lu
= _FakeLU(rpc
=_RpcForDiskWipe(node_uuid
, pt
, self
._FailingWipeCb
),
1657 cfg
=_ConfigForDiskWipe(node_uuid
, disks
))
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
])
1665 instance_create
.WipeDisks(lu
, inst
)
1666 except errors
.OpExecError
, err
:
1667 self
.assertTrue(str(err
), "Could not wipe disk 0 at offset 0 ")
1669 self
.fail("Did not raise exception")
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),
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
)
1686 lu
= _FakeLU(rpc
=_RpcForDiskWipe(node_name
, pauset
, progresst
),
1687 cfg
=_ConfigForDiskWipe(node_name
, disks
))
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
])
1694 return (lu
, instance
, pauset
, progresst
)
1696 def testNormalWipe(self
):
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"),
1708 (lu
, inst
, pauset
, progresst
) = self
._PrepareWipeTest(0, disks
)
1710 instance_create
.WipeDisks(lu
, inst
)
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),
1723 # Ensure the complete disk has been wiped
1724 self
.assertEqual(progresst
.progress
,
1725 dict((i
.logical_id
, i
.size
) for i
in disks
))
1727 def testWipeWithStartOffset(self
):
1728 for start_offset
in [0, 280, 8895, 1563204]:
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"),
1736 (lu
, inst
, pauset
, progresst
) = \
1737 self
._PrepareWipeTest(start_offset
, disks
)
1739 # Test start offset with only one disk
1740 instance_create
.WipeDisks(lu
, inst
,
1741 disks
=[(1, disks
[1], start_offset
)])
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),
1748 self
.assertEqual(progresst
.progress
, {
1749 "disk1": disks
[1].size
,
1753 class TestCheckOpportunisticLocking(unittest
.TestCase
):
1754 class OpTest(opcodes
.OpCode
):
1756 ("opportunistic_locking", False, ht
.TBool
, None),
1757 ("iallocator", None, ht
.TMaybe(ht
.TNonEmptyString
), "")
1761 def _MakeOp(cls
, **kwargs
):
1762 op
= cls
.OpTest(**kwargs
)
1766 def testMissingAttributes(self
):
1767 self
.assertRaises(AttributeError, instance
.CheckOpportunisticLocking
,
1770 def testDefaults(self
):
1772 instance
.CheckOpportunisticLocking(op
)
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
)
1783 instance
.CheckOpportunisticLocking(op
)
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")
1791 def testRemoveInst(self
):
1792 inst
= self
.cfg
.AddNewInstance(disks
=[])
1793 op
= opcodes
.OpInstanceRemove(instance_name
=inst
.name
)
1797 class TestLUInstanceMove(CmdlibTestCase
):
1799 super(TestLUInstanceMove
, self
).setUp()
1801 self
.node
= self
.cfg
.AddNewNode()
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",
1808 self
.rpc
.call_blockdev_remove
.return_value
= \
1809 self
.RpcResultsBuilder() \
1810 .CreateSuccessfulNodeResult(self
.master
, "")
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
1818 def ImpExpStatus(node_uuid
, name
):
1819 return self
.RpcResultsBuilder() \
1820 .CreateSuccessfulNodeResult(node_uuid
,
1821 [objects
.ImportExportStatus(
1824 self
.rpc
.call_impexp_status
.side_effect
= ImpExpStatus
1826 def ImpExpCleanup(node_uuid
, name
):
1827 return self
.RpcResultsBuilder() \
1828 .CreateSuccessfulNodeResult(node_uuid
)
1829 self
.rpc
.call_impexp_cleanup
.side_effect
= ImpExpCleanup
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")
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"
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 .*")
1851 def testMoveStoppedInstance(self
):
1852 inst
= self
.cfg
.AddNewInstance()
1853 op
= opcodes
.OpInstanceMove(instance_name
=inst
.name
,
1854 target_node
=self
.node
.name
)
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}, ))) \
1864 self
.rpc
.call_instance_start
.return_value
= \
1865 self
.RpcResultsBuilder() \
1866 .CreateSuccessfulNodeResult(self
.node
, "")
1868 inst
= self
.cfg
.AddNewInstance(admin_state
=constants
.ADMINST_UP
)
1869 op
= opcodes
.OpInstanceMove(instance_name
=inst
.name
,
1870 target_node
=self
.node
.name
)
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}, ))) \
1880 self
.rpc
.call_instance_start
.return_value
= \
1881 self
.RpcResultsBuilder() \
1882 .CreateFailedNodeResult(self
.node
)
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 .*")
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(
1898 recent_output
=["mock output"]
1900 op
= opcodes
.OpInstanceMove(instance_name
=inst
.name
,
1901 target_node
=self
.node
.name
)
1902 self
.ExecOpCodeExpectOpExecError(op
, "Errors during disk copy")
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")
1915 class TestLUInstanceRename(CmdlibTestCase
):
1917 super(TestLUInstanceRename
, self
).setUp()
1919 self
.MockOut(instance_utils
, 'netutils', self
.netutils_mod
)
1921 self
.inst
= self
.cfg
.AddNewInstance()
1923 self
.op
= opcodes
.OpInstanceRename(instance_name
=self
.inst
.name
,
1924 new_name
="new_name.example.com")
1926 def testIpCheckWithoutNameCheck(self
):
1927 op
= self
.CopyOpCode(self
.op
,
1930 self
.ExecOpCodeExpectOpPrereqError(
1931 op
, "IP address check requires a name check")
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")
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")
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))
1953 inst
= self
.cfg
.AddNewInstance(disk_template
=constants
.DT_FILE
)
1954 op
= self
.CopyOpCode(self
.op
,
1955 instance_name
=inst
.name
)
1959 class TestLUInstanceMultiAlloc(CmdlibTestCase
):
1961 super(TestLUInstanceMultiAlloc
, self
).setUp()
1963 self
.inst_op
= opcodes
.OpInstanceCreate(instance_name
="inst.example.com",
1964 disk_template
=constants
.DT_DRBD8
,
1968 hypervisor
=constants
.HT_XEN_HVM
,
1969 mode
=constants
.INSTANCE_CREATE
)
1971 def testInstanceWithIAllocator(self
):
1972 inst
= self
.CopyOpCode(self
.inst_op
,
1974 op
= opcodes
.OpInstanceMultiAlloc(instances
=[inst
])
1975 self
.ExecOpCodeExpectOpPrereqError(
1976 op
, "iallocator are not allowed to be set on instance objects")
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"
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")
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")
2002 def testWithGivenNodes(self
):
2003 snode
= self
.cfg
.AddNewNode()
2004 inst
= self
.CopyOpCode(self
.inst_op
,
2005 pnode
=self
.master
.name
,
2007 op
= opcodes
.OpInstanceMultiAlloc(instances
=[inst
])
2010 def testDryRun(self
):
2011 snode
= self
.cfg
.AddNewNode()
2012 inst
= self
.CopyOpCode(self
.inst_op
,
2013 pnode
=self
.master
.name
,
2015 op
= opcodes
.OpInstanceMultiAlloc(instances
=[inst
],
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
])], [])
2024 inst
= self
.CopyOpCode(self
.inst_op
)
2025 op
= opcodes
.OpInstanceMultiAlloc(instances
=[inst
],
2026 iallocator
="mock_ialloc")
2029 def testManyInstancesWithIAllocator(self
):
2030 snode
= self
.cfg
.AddNewNode()
2032 inst1
= self
.CopyOpCode(self
.inst_op
)
2033 inst2
= self
.CopyOpCode(self
.inst_op
, instance_name
="inst2.example.com")
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
])],
2040 op
= opcodes
.OpInstanceMultiAlloc(instances
=[inst1
, inst2
],
2041 iallocator
="mock_ialloc")
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
])], [])
2049 inst
= self
.CopyOpCode(self
.inst_op
)
2050 op
= opcodes
.OpInstanceMultiAlloc(instances
=[inst
],
2051 iallocator
="mock_ialloc",
2052 opportunistic_locking
=True)
2055 def testFailingIAllocator(self
):
2056 self
.iallocator_cls
.return_value
.success
= False
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")
2065 class TestLUInstanceSetParams(CmdlibTestCase
):
2067 super(TestLUInstanceSetParams
, self
).setUp()
2069 self
.MockOut(instance_set_params
, 'netutils', self
.netutils_mod
)
2070 self
.MockOut(instance_utils
, 'netutils', self
.netutils_mod
)
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
)
2076 self
.running_inst
= \
2077 self
.cfg
.AddNewInstance(admin_state
=constants
.ADMINST_UP
)
2079 opcodes
.OpInstanceSetParams(instance_name
=self
.running_inst
.name
)
2081 ext_disks
= [self
.cfg
.CreateDisk(dev_type
=constants
.DT_EXT
,
2083 constants
.IDISK_PROVIDER
: "pvdr"
2085 self
.ext_storage_inst
= \
2086 self
.cfg
.AddNewInstance(disk_template
=constants
.DT_EXT
,
2088 self
.ext_storage_op
= \
2089 opcodes
.OpInstanceSetParams(instance_name
=self
.ext_storage_inst
.name
)
2091 self
.snode
= self
.cfg
.AddNewNode()
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
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
2105 self
.mocked_disk_uuid
= "mock_uuid_1134"
2106 self
.mocked_disk_name
= "mock_disk_1134"
2108 bootid
= "mock_bootid"
2111 "type": self
.mocked_storage_type
,
2112 "storage_free": self
.mocked_storage_free
2116 "cpu_total": self
.mocked_master_cpu_total
,
2117 "memory_free": self
.mocked_master_memory_free
2120 "cpu_total": self
.mocked_snode_cpu_total
,
2121 "memory_free": self
.mocked_snode_memory_free
2124 self
.rpc
.call_node_info
.return_value
= \
2125 self
.RpcResultsBuilder() \
2126 .AddSuccessfulNode(self
.master
,
2127 (bootid
, storage_info
, (hv_info_master
, ))) \
2128 .AddSuccessfulNode(self
.snode
,
2129 (bootid
, storage_info
, (hv_info_snode
, ))) \
2132 def _InstanceInfo(_
, instance
, __
, ___
):
2133 if instance
in [self
.inst
.name
, self
.ext_storage_inst
.name
]:
2134 return self
.RpcResultsBuilder() \
2135 .CreateSuccessfulNodeResult(self
.master
, None)
2136 elif instance
== self
.running_inst
.name
:
2137 return self
.RpcResultsBuilder() \
2138 .CreateSuccessfulNodeResult(
2140 "memory": self
.mocked_running_inst_memory
,
2141 "vcpus": self
.mocked_running_inst_vcpus
,
2142 "state": self
.mocked_running_inst_state
,
2143 "time": self
.mocked_running_inst_time
2146 raise AssertionError()
2147 self
.rpc
.call_instance_info
.side_effect
= _InstanceInfo
2149 self
.rpc
.call_bridges_exist
.return_value
= \
2150 self
.RpcResultsBuilder() \
2151 .CreateSuccessfulNodeResult(self
.master
, True)
2153 self
.rpc
.call_blockdev_getmirrorstatus
.side_effect
= \
2154 lambda node
, _
: self
.RpcResultsBuilder() \
2155 .CreateSuccessfulNodeResult(node
, [])
2157 self
.rpc
.call_blockdev_shutdown
.side_effect
= \
2158 lambda node
, _
: self
.RpcResultsBuilder() \
2159 .CreateSuccessfulNodeResult(node
, [])
2161 def testNoChanges(self
):
2162 op
= self
.CopyOpCode(self
.op
)
2163 self
.ExecOpCodeExpectOpPrereqError(op
, "No changes submitted")
2165 def testGlobalHvparams(self
):
2166 op
= self
.CopyOpCode(self
.op
,
2167 hvparams
={constants
.HV_MIGRATION_PORT
: 1234})
2168 self
.ExecOpCodeExpectOpPrereqError(
2169 op
, "hypervisor parameters are global and cannot be customized")
2171 def testHvparams(self
):
2172 op
= self
.CopyOpCode(self
.op
,
2173 hvparams
={constants
.HV_BOOT_ORDER
: "cd"})
2176 def testDisksAndDiskTemplate(self
):
2177 op
= self
.CopyOpCode(self
.op
,
2178 disk_template
=constants
.DT_PLAIN
,
2179 disks
=[[constants
.DDM_ADD
, -1, {}]])
2180 self
.ExecOpCodeExpectOpPrereqError(
2181 op
, "Disk template conversion and other disk changes not supported at"
2184 def testDiskTemplateToMirroredNoRemoteNode(self
):
2185 op
= self
.CopyOpCode(self
.op
,
2186 disk_template
=constants
.DT_DRBD8
)
2187 self
.ExecOpCodeExpectOpPrereqError(
2188 op
, "Changing the disk template to a mirrored one requires specifying"
2189 " a secondary node")
2191 def testPrimaryNodeToOldPrimaryNode(self
):
2192 op
= self
.CopyOpCode(self
.op
,
2193 pnode
=self
.master
.name
)
2196 def testPrimaryNodeChange(self
):
2197 node
= self
.cfg
.AddNewNode()
2198 op
= self
.CopyOpCode(self
.op
,
2202 def testPrimaryNodeChangeRunningInstance(self
):
2203 node
= self
.cfg
.AddNewNode()
2204 op
= self
.CopyOpCode(self
.running_op
,
2206 self
.ExecOpCodeExpectOpPrereqError(op
, "Instance is still running")
2208 def testOsChange(self
):
2209 os
= self
.cfg
.CreateOs(supported_variants
=[])
2210 self
.rpc
.call_os_validate
.return_value
= True
2211 op
= self
.CopyOpCode(self
.op
,
2215 def testVCpuChange(self
):
2216 op
= self
.CopyOpCode(self
.op
,
2218 constants
.BE_VCPUS
: 4
2222 def testWrongCpuMask(self
):
2223 op
= self
.CopyOpCode(self
.op
,
2225 constants
.BE_VCPUS
: 4
2228 constants
.HV_CPU_MASK
: "1,2:3,4"
2230 self
.ExecOpCodeExpectOpPrereqError(
2231 op
, "Number of vCPUs .* does not match the CPU mask .*")
2233 def testCorrectCpuMask(self
):
2234 op
= self
.CopyOpCode(self
.op
,
2236 constants
.BE_VCPUS
: 4
2239 constants
.HV_CPU_MASK
: "1,2:3,4:all:1,4"
2243 def testOsParams(self
):
2244 op
= self
.CopyOpCode(self
.op
,
2246 self
.os
.supported_parameters
[0]: "test_param_val"
2250 def testIncreaseMemoryTooMuch(self
):
2251 op
= self
.CopyOpCode(self
.running_op
,
2253 constants
.BE_MAXMEM
:
2254 self
.mocked_master_memory_free
* 2
2256 self
.ExecOpCodeExpectOpPrereqError(
2257 op
, "This change will prevent the instance from starting")
2259 def testIncreaseMemory(self
):
2260 op
= self
.CopyOpCode(self
.running_op
,
2262 constants
.BE_MAXMEM
: self
.mocked_master_memory_free
2266 def testIncreaseMemoryTooMuchForSecondary(self
):
2267 inst
= self
.cfg
.AddNewInstance(admin_state
=constants
.ADMINST_UP
,
2268 disk_template
=constants
.DT_DRBD8
,
2269 secondary_node
=self
.snode
)
2270 self
.rpc
.call_instance_info
.side_effect
= [
2271 self
.RpcResultsBuilder()
2272 .CreateSuccessfulNodeResult(self
.master
,
2275 self
.mocked_snode_memory_free
* 2,
2276 "vcpus": self
.mocked_running_inst_vcpus
,
2277 "state": self
.mocked_running_inst_state
,
2278 "time": self
.mocked_running_inst_time
2281 op
= self
.CopyOpCode(self
.op
,
2282 instance_name
=inst
.name
,
2284 constants
.BE_MAXMEM
:
2285 self
.mocked_snode_memory_free
* 2,
2286 constants
.BE_AUTO_BALANCE
: True
2288 self
.ExecOpCodeExpectOpPrereqError(
2289 op
, "This change will prevent the instance from failover to its"
2292 def testInvalidRuntimeMemory(self
):
2293 op
= self
.CopyOpCode(self
.running_op
,
2294 runtime_mem
=self
.mocked_master_memory_free
* 2)
2295 self
.ExecOpCodeExpectOpPrereqError(
2296 op
, "Instance .* must have memory between .* and .* of memory")
2298 def testIncreaseRuntimeMemory(self
):
2299 op
= self
.CopyOpCode(self
.running_op
,
2300 runtime_mem
=self
.mocked_master_memory_free
,
2302 constants
.BE_MAXMEM
: self
.mocked_master_memory_free
2306 def testAddNicWithPoolIpNoNetwork(self
):
2307 op
= self
.CopyOpCode(self
.op
,
2308 nics
=[(constants
.DDM_ADD
, -1,
2310 constants
.INIC_IP
: constants
.NIC_IP_POOL
2312 self
.ExecOpCodeExpectOpPrereqError(
2313 op
, "If ip=pool, parameter network cannot be none")
2315 def testAddNicWithPoolIp(self
):
2316 net
= self
.cfg
.AddNewNetwork()
2317 self
.cfg
.ConnectNetworkToGroup(net
, self
.group
)
2318 op
= self
.CopyOpCode(self
.op
,
2319 nics
=[(constants
.DDM_ADD
, -1,
2321 constants
.INIC_IP
: constants
.NIC_IP_POOL
,
2322 constants
.INIC_NETWORK
: net
.name
2326 def testAddNicWithInvalidIp(self
):
2327 op
= self
.CopyOpCode(self
.op
,
2328 nics
=[(constants
.DDM_ADD
, -1,
2330 constants
.INIC_IP
: "invalid"
2332 self
.ExecOpCodeExpectOpPrereqError(
2333 op
, "Invalid IP address")
2335 def testAddNic(self
):
2336 op
= self
.CopyOpCode(self
.op
,
2337 nics
=[(constants
.DDM_ADD
, -1, {})])
2340 def testAttachNICs(self
):
2341 msg
= "Attach operation is not supported for NICs"
2342 op
= self
.CopyOpCode(self
.op
,
2343 nics
=[(constants
.DDM_ATTACH
, -1, {})])
2344 self
.ExecOpCodeExpectOpPrereqError(op
, msg
)
2346 def testNoHotplugSupport(self
):
2347 op
= self
.CopyOpCode(self
.op
,
2348 nics
=[(constants
.DDM_ADD
, -1, {})],
2350 self
.rpc
.call_hotplug_supported
.return_value
= \
2351 self
.RpcResultsBuilder() \
2352 .CreateFailedNodeResult(self
.master
)
2353 self
.ExecOpCodeExpectOpPrereqError(op
, "Hotplug is not possible")
2354 self
.assertTrue(self
.rpc
.call_hotplug_supported
.called
)
2356 def testHotplugIfPossible(self
):
2357 op
= self
.CopyOpCode(self
.op
,
2358 nics
=[(constants
.DDM_ADD
, -1, {})],
2359 hotplug_if_possible
=True)
2360 self
.rpc
.call_hotplug_supported
.return_value
= \
2361 self
.RpcResultsBuilder() \
2362 .CreateFailedNodeResult(self
.master
)
2364 self
.assertTrue(self
.rpc
.call_hotplug_supported
.called
)
2365 self
.assertFalse(self
.rpc
.call_hotplug_device
.called
)
2367 def testHotAddNic(self
):
2368 op
= self
.CopyOpCode(self
.op
,
2369 nics
=[(constants
.DDM_ADD
, -1, {})],
2371 self
.rpc
.call_hotplug_supported
.return_value
= \
2372 self
.RpcResultsBuilder() \
2373 .CreateSuccessfulNodeResult(self
.master
)
2375 self
.assertTrue(self
.rpc
.call_hotplug_supported
.called
)
2376 self
.assertTrue(self
.rpc
.call_hotplug_device
.called
)
2378 def testAddNicWithIp(self
):
2379 op
= self
.CopyOpCode(self
.op
,
2380 nics
=[(constants
.DDM_ADD
, -1,
2382 constants
.INIC_IP
: "2.3.1.4"
2386 def testModifyNicRoutedWithoutIp(self
):
2387 op
= self
.CopyOpCode(self
.op
,
2388 nics
=[(constants
.DDM_MODIFY
, 0,
2390 constants
.INIC_NETWORK
: constants
.VALUE_NONE
,
2391 constants
.INIC_MODE
: constants
.NIC_MODE_ROUTED
2393 self
.ExecOpCodeExpectOpPrereqError(
2394 op
, "Cannot set the NIC IP address to None on a routed NIC"
2395 " if not attached to a network")
2397 def testModifyNicSetMac(self
):
2398 op
= self
.CopyOpCode(self
.op
,
2399 nics
=[(constants
.DDM_MODIFY
, 0,
2401 constants
.INIC_MAC
: "0a:12:95:15:bf:75"
2405 def testModifyNicWithPoolIpNoNetwork(self
):
2406 op
= self
.CopyOpCode(self
.op
,
2407 nics
=[(constants
.DDM_MODIFY
, -1,
2409 constants
.INIC_IP
: constants
.NIC_IP_POOL
2411 self
.ExecOpCodeExpectOpPrereqError(
2412 op
, "ip=pool, but no network found")
2414 def testModifyNicSetNet(self
):
2415 old_net
= self
.cfg
.AddNewNetwork()
2416 self
.cfg
.ConnectNetworkToGroup(old_net
, self
.group
)
2417 inst
= self
.cfg
.AddNewInstance(nics
=[
2418 self
.cfg
.CreateNic(network
=old_net
,
2419 ip
="198.51.100.2")])
2421 new_net
= self
.cfg
.AddNewNetwork(mac_prefix
="be")
2422 self
.cfg
.ConnectNetworkToGroup(new_net
, self
.group
)
2423 op
= self
.CopyOpCode(self
.op
,
2424 instance_name
=inst
.name
,
2425 nics
=[(constants
.DDM_MODIFY
, 0,
2427 constants
.INIC_NETWORK
: new_net
.name
2431 def testModifyNicSetLinkWhileConnected(self
):
2432 old_net
= self
.cfg
.AddNewNetwork()
2433 self
.cfg
.ConnectNetworkToGroup(old_net
, self
.group
)
2434 inst
= self
.cfg
.AddNewInstance(nics
=[
2435 self
.cfg
.CreateNic(network
=old_net
)])
2437 op
= self
.CopyOpCode(self
.op
,
2438 instance_name
=inst
.name
,
2439 nics
=[(constants
.DDM_MODIFY
, 0,
2441 constants
.INIC_LINK
: "mock_link"
2443 self
.ExecOpCodeExpectOpPrereqError(
2444 op
, "Not allowed to change link or mode of a NIC that is connected"
2447 def testModifyNicSetNetAndIp(self
):
2448 net
= self
.cfg
.AddNewNetwork(mac_prefix
="be", network
="123.123.123.0/24")
2449 self
.cfg
.ConnectNetworkToGroup(net
, self
.group
)
2450 op
= self
.CopyOpCode(self
.op
,
2451 nics
=[(constants
.DDM_MODIFY
, 0,
2453 constants
.INIC_NETWORK
: net
.name
,
2454 constants
.INIC_IP
: "123.123.123.1"
2458 def testModifyNic(self
):
2459 op
= self
.CopyOpCode(self
.op
,
2460 nics
=[(constants
.DDM_MODIFY
, 0, {})])
2463 def testHotModifyNic(self
):
2464 op
= self
.CopyOpCode(self
.op
,
2465 nics
=[(constants
.DDM_MODIFY
, 0, {})],
2467 self
.rpc
.call_hotplug_supported
.return_value
= \
2468 self
.RpcResultsBuilder() \
2469 .CreateSuccessfulNodeResult(self
.master
)
2471 self
.assertTrue(self
.rpc
.call_hotplug_supported
.called
)
2472 self
.assertTrue(self
.rpc
.call_hotplug_device
.called
)
2474 def testRemoveLastNic(self
):
2475 op
= self
.CopyOpCode(self
.op
,
2476 nics
=[(constants
.DDM_REMOVE
, 0, {})])
2477 self
.ExecOpCodeExpectOpPrereqError(
2478 op
, "violates policy")
2480 def testRemoveNic(self
):
2481 inst
= self
.cfg
.AddNewInstance(nics
=[self
.cfg
.CreateNic(),
2482 self
.cfg
.CreateNic()])
2483 op
= self
.CopyOpCode(self
.op
,
2484 instance_name
=inst
.name
,
2485 nics
=[(constants
.DDM_REMOVE
, 0, {})])
2488 def testDetachNICs(self
):
2489 msg
= "Detach operation is not supported for NICs"
2490 op
= self
.CopyOpCode(self
.op
,
2491 nics
=[(constants
.DDM_DETACH
, -1, {})])
2492 self
.ExecOpCodeExpectOpPrereqError(op
, msg
)
2494 def testHotRemoveNic(self
):
2495 inst
= self
.cfg
.AddNewInstance(nics
=[self
.cfg
.CreateNic(),
2496 self
.cfg
.CreateNic()])
2497 op
= self
.CopyOpCode(self
.op
,
2498 instance_name
=inst
.name
,
2499 nics
=[(constants
.DDM_REMOVE
, 0, {})],
2501 self
.rpc
.call_hotplug_supported
.return_value
= \
2502 self
.RpcResultsBuilder() \
2503 .CreateSuccessfulNodeResult(self
.master
)
2505 self
.assertTrue(self
.rpc
.call_hotplug_supported
.called
)
2506 self
.assertTrue(self
.rpc
.call_hotplug_device
.called
)
2508 def testSetOffline(self
):
2509 op
= self
.CopyOpCode(self
.op
,
2513 def testUnsetOffline(self
):
2514 op
= self
.CopyOpCode(self
.op
,
2518 def testAddDiskInvalidMode(self
):
2519 op
= self
.CopyOpCode(self
.op
,
2520 disks
=[[constants
.DDM_ADD
, -1,
2522 constants
.IDISK_MODE
: "invalid"
2524 self
.ExecOpCodeExpectOpPrereqError(
2525 op
, "Invalid disk access mode 'invalid'")
2527 def testAddDiskMissingSize(self
):
2528 op
= self
.CopyOpCode(self
.op
,
2529 disks
=[[constants
.DDM_ADD
, -1, {}]])
2530 self
.ExecOpCodeExpectOpPrereqError(
2531 op
, "Required disk parameter 'size' missing")
2533 def testAddDiskInvalidSize(self
):
2534 op
= self
.CopyOpCode(self
.op
,
2535 disks
=[[constants
.DDM_ADD
, -1,
2537 constants
.IDISK_SIZE
: "invalid"
2539 self
.ExecOpCodeExpectException(
2540 op
, errors
.TypeEnforcementError
, "is not a valid size")
2542 def testAddDiskUnknownParam(self
):
2543 op
= self
.CopyOpCode(self
.op
,
2544 disks
=[[constants
.DDM_ADD
, -1,
2546 "uuid": self
.mocked_disk_uuid
2548 self
.ExecOpCodeExpectException(
2549 op
, errors
.TypeEnforcementError
, "Unknown parameter 'uuid'")
2551 def testAddDiskRunningInstanceNoWaitForSync(self
):
2552 op
= self
.CopyOpCode(self
.running_op
,
2553 disks
=[[constants
.DDM_ADD
, -1,
2555 constants
.IDISK_SIZE
: 1024
2557 wait_for_sync
=False)
2559 self
.assertFalse(self
.rpc
.call_blockdev_shutdown
.called
)
2561 def testAddDiskDownInstance(self
):
2562 op
= self
.CopyOpCode(self
.op
,
2563 disks
=[[constants
.DDM_ADD
, -1,
2565 constants
.IDISK_SIZE
: 1024
2568 self
.assertTrue(self
.rpc
.call_blockdev_shutdown
.called
)
2570 def testAddDiskIndexBased(self
):
2571 SPECIFIC_SIZE
= 435 * 4
2572 insertion_index
= len(self
.inst
.disks
)
2573 op
= self
.CopyOpCode(self
.op
,
2574 disks
=[[constants
.DDM_ADD
, insertion_index
,
2576 constants
.IDISK_SIZE
: SPECIFIC_SIZE
2579 self
.assertEqual(len(self
.inst
.disks
), insertion_index
+ 1)
2580 new_disk
= self
.cfg
.GetDisk(self
.inst
.disks
[insertion_index
])
2581 self
.assertEqual(new_disk
.size
, SPECIFIC_SIZE
)
2583 def testAddDiskHugeIndex(self
):
2584 op
= self
.CopyOpCode(self
.op
,
2585 disks
=[[constants
.DDM_ADD
, 5,
2587 constants
.IDISK_SIZE
: 1024
2589 self
.ExecOpCodeExpectException(
2590 op
, IndexError, "Got disk index.*but there are only.*"
2593 def testAddExtDisk(self
):
2594 op
= self
.CopyOpCode(self
.ext_storage_op
,
2595 disks
=[[constants
.DDM_ADD
, -1,
2597 constants
.IDISK_SIZE
: 1024
2599 self
.ExecOpCodeExpectOpPrereqError(op
,
2600 "Missing provider for template 'ext'")
2602 op
= self
.CopyOpCode(self
.ext_storage_op
,
2603 disks
=[[constants
.DDM_ADD
, -1,
2605 constants
.IDISK_SIZE
: 1024,
2606 constants
.IDISK_PROVIDER
: "bla"
2610 def testAddDiskDownInstanceNoWaitForSync(self
):
2611 op
= self
.CopyOpCode(self
.op
,
2612 disks
=[[constants
.DDM_ADD
, -1,
2614 constants
.IDISK_SIZE
: 1024
2616 wait_for_sync
=False)
2617 self
.ExecOpCodeExpectOpPrereqError(
2618 op
, "Can't add a disk to an instance with deactivated disks"
2619 " and --no-wait-for-sync given")
2621 def testAddDiskRunningInstance(self
):
2622 op
= self
.CopyOpCode(self
.running_op
,
2623 disks
=[[constants
.DDM_ADD
, -1,
2625 constants
.IDISK_SIZE
: 1024
2629 self
.assertFalse(self
.rpc
.call_blockdev_shutdown
.called
)
2631 def testAddDiskNoneName(self
):
2632 op
= self
.CopyOpCode(self
.op
,
2633 disks
=[[constants
.DDM_ADD
, -1,
2635 constants
.IDISK_SIZE
: 1024,
2636 constants
.IDISK_NAME
: constants
.VALUE_NONE
2640 def testHotAddDisk(self
):
2641 self
.rpc
.call_blockdev_assemble
.return_value
= \
2642 self
.RpcResultsBuilder() \
2643 .CreateSuccessfulNodeResult(self
.master
, ("/dev/mocked_path",
2644 "/var/run/ganeti/instance-disks/mocked_d",
2646 op
= self
.CopyOpCode(self
.op
,
2647 disks
=[[constants
.DDM_ADD
, -1,
2649 constants
.IDISK_SIZE
: 1024,
2652 self
.rpc
.call_hotplug_supported
.return_value
= \
2653 self
.RpcResultsBuilder() \
2654 .CreateSuccessfulNodeResult(self
.master
)
2656 self
.assertTrue(self
.rpc
.call_hotplug_supported
.called
)
2657 self
.assertTrue(self
.rpc
.call_blockdev_create
.called
)
2658 self
.assertTrue(self
.rpc
.call_blockdev_assemble
.called
)
2659 self
.assertTrue(self
.rpc
.call_hotplug_device
.called
)
2661 def testAttachDiskWrongParams(self
):
2662 msg
= "Only one argument is permitted in attach op, either name or uuid"
2663 op
= self
.CopyOpCode(self
.op
,
2664 disks
=[[constants
.DDM_ATTACH
, -1,
2666 constants
.IDISK_SIZE
: 1134
2669 self
.ExecOpCodeExpectOpPrereqError(op
, msg
)
2670 op
= self
.CopyOpCode(self
.op
,
2671 disks
=[[constants
.DDM_ATTACH
, -1,
2674 constants
.IDISK_NAME
: "1134",
2677 self
.ExecOpCodeExpectOpPrereqError(op
, msg
)
2678 op
= self
.CopyOpCode(self
.op
,
2679 disks
=[[constants
.DDM_ATTACH
, -1,
2682 constants
.IDISK_SIZE
: 1134,
2685 self
.ExecOpCodeExpectOpPrereqError(op
, msg
)
2687 def testAttachDiskWrongTemplate(self
):
2688 msg
= "Instance has '%s' template while disk has '%s' template" % \
2689 (constants
.DT_PLAIN
, constants
.DT_BLOCK
)
2690 self
.cfg
.AddOrphanDisk(name
=self
.mocked_disk_name
,
2691 dev_type
=constants
.DT_BLOCK
)
2692 op
= self
.CopyOpCode(self
.op
,
2693 disks
=[[constants
.DDM_ATTACH
, -1,
2695 constants
.IDISK_NAME
: self
.mocked_disk_name
2698 self
.ExecOpCodeExpectOpPrereqError(op
, msg
)
2700 def testAttachDiskWrongNodes(self
):
2701 msg
= "Disk nodes are \['mock_node_1134'\]"
2703 self
.cfg
.AddOrphanDisk(name
=self
.mocked_disk_name
,
2704 primary_node
="mock_node_1134")
2705 op
= self
.CopyOpCode(self
.op
,
2706 disks
=[[constants
.DDM_ATTACH
, -1,
2708 constants
.IDISK_NAME
: self
.mocked_disk_name
2711 self
.ExecOpCodeExpectOpPrereqError(op
, msg
)
2713 def testAttachDiskRunningInstance(self
):
2714 self
.cfg
.AddOrphanDisk(name
=self
.mocked_disk_name
,
2715 primary_node
=self
.master
.uuid
)
2716 self
.rpc
.call_blockdev_assemble
.return_value
= \
2717 self
.RpcResultsBuilder() \
2718 .CreateSuccessfulNodeResult(self
.master
,
2719 ("/dev/mocked_path",
2720 "/var/run/ganeti/instance-disks/mocked_d",
2722 op
= self
.CopyOpCode(self
.running_op
,
2723 disks
=[[constants
.DDM_ATTACH
, -1,
2725 constants
.IDISK_NAME
: self
.mocked_disk_name
2729 self
.assertTrue(self
.rpc
.call_blockdev_assemble
.called
)
2730 self
.assertFalse(self
.rpc
.call_blockdev_shutdown
.called
)
2732 def testAttachDiskRunningInstanceNoWaitForSync(self
):
2733 self
.cfg
.AddOrphanDisk(name
=self
.mocked_disk_name
,
2734 primary_node
=self
.master
.uuid
)
2735 self
.rpc
.call_blockdev_assemble
.return_value
= \
2736 self
.RpcResultsBuilder() \
2737 .CreateSuccessfulNodeResult(self
.master
,
2738 ("/dev/mocked_path",
2739 "/var/run/ganeti/instance-disks/mocked_d",
2741 op
= self
.CopyOpCode(self
.running_op
,
2742 disks
=[[constants
.DDM_ATTACH
, -1,
2744 constants
.IDISK_NAME
: self
.mocked_disk_name
2746 wait_for_sync
=False)
2748 self
.assertTrue(self
.rpc
.call_blockdev_assemble
.called
)
2749 self
.assertFalse(self
.rpc
.call_blockdev_shutdown
.called
)
2751 def testAttachDiskDownInstance(self
):
2752 self
.cfg
.AddOrphanDisk(name
=self
.mocked_disk_name
,
2753 primary_node
=self
.master
.uuid
)
2754 op
= self
.CopyOpCode(self
.op
,
2755 disks
=[[constants
.DDM_ATTACH
, -1,
2757 constants
.IDISK_NAME
: self
.mocked_disk_name
2761 self
.assertTrue(self
.rpc
.call_blockdev_assemble
.called
)
2762 self
.assertTrue(self
.rpc
.call_blockdev_shutdown
.called
)
2764 def testAttachDiskDownInstanceNoWaitForSync(self
):
2765 self
.cfg
.AddOrphanDisk(name
=self
.mocked_disk_name
)
2766 op
= self
.CopyOpCode(self
.op
,
2767 disks
=[[constants
.DDM_ATTACH
, -1,
2769 constants
.IDISK_NAME
: self
.mocked_disk_name
2771 wait_for_sync
=False)
2772 self
.ExecOpCodeExpectOpPrereqError(
2773 op
, "Can't attach a disk to an instance with deactivated disks"
2774 " and --no-wait-for-sync given.")
2776 def testHotAttachDisk(self
):
2777 self
.cfg
.AddOrphanDisk(name
=self
.mocked_disk_name
,
2778 primary_node
=self
.master
.uuid
)
2779 self
.rpc
.call_blockdev_assemble
.return_value
= \
2780 self
.RpcResultsBuilder() \
2781 .CreateSuccessfulNodeResult(self
.master
,
2782 ("/dev/mocked_path",
2783 "/var/run/ganeti/instance-disks/mocked_d",
2785 op
= self
.CopyOpCode(self
.op
,
2786 disks
=[[constants
.DDM_ATTACH
, -1,
2788 constants
.IDISK_NAME
: self
.mocked_disk_name
2791 self
.rpc
.call_hotplug_supported
.return_value
= \
2792 self
.RpcResultsBuilder() \
2793 .CreateSuccessfulNodeResult(self
.master
)
2795 self
.assertTrue(self
.rpc
.call_hotplug_supported
.called
)
2796 self
.assertTrue(self
.rpc
.call_blockdev_assemble
.called
)
2797 self
.assertTrue(self
.rpc
.call_hotplug_device
.called
)
2799 def testHotRemoveDisk(self
):
2800 inst
= self
.cfg
.AddNewInstance(disks
=[self
.cfg
.CreateDisk(),
2801 self
.cfg
.CreateDisk()])
2802 op
= self
.CopyOpCode(self
.op
,
2803 instance_name
=inst
.name
,
2804 disks
=[[constants
.DDM_REMOVE
, -1,
2807 self
.rpc
.call_hotplug_supported
.return_value
= \
2808 self
.RpcResultsBuilder() \
2809 .CreateSuccessfulNodeResult(self
.master
)
2811 self
.assertTrue(self
.rpc
.call_hotplug_supported
.called
)
2812 self
.assertTrue(self
.rpc
.call_hotplug_device
.called
)
2813 self
.assertTrue(self
.rpc
.call_blockdev_shutdown
.called
)
2814 self
.assertTrue(self
.rpc
.call_blockdev_remove
.called
)
2816 def testHotDetachDisk(self
):
2817 inst
= self
.cfg
.AddNewInstance(disks
=[self
.cfg
.CreateDisk(),
2818 self
.cfg
.CreateDisk()])
2819 op
= self
.CopyOpCode(self
.op
,
2820 instance_name
=inst
.name
,
2821 disks
=[[constants
.DDM_DETACH
, -1,
2824 self
.rpc
.call_hotplug_supported
.return_value
= \
2825 self
.RpcResultsBuilder() \
2826 .CreateSuccessfulNodeResult(self
.master
)
2828 self
.assertTrue(self
.rpc
.call_hotplug_supported
.called
)
2829 self
.assertTrue(self
.rpc
.call_hotplug_device
.called
)
2830 self
.assertTrue(self
.rpc
.call_blockdev_shutdown
.called
)
2832 def testDetachAttachFileBasedDisk(self
):
2833 """Detach and re-attach a disk from a file-based instance."""
2834 # Create our disk and calculate the path where it is stored, its name, as
2835 # well as the expected path where it will be moved.
2836 mock_disk
= self
.cfg
.CreateDisk(
2837 name
='mock_disk_1134', dev_type
=constants
.DT_FILE
,
2838 logical_id
=('loop', '/tmp/instance/disk'), primary_node
=self
.master
.uuid
)
2840 # Create a file-based instance
2841 file_disk
= self
.cfg
.CreateDisk(
2842 dev_type
=constants
.DT_FILE
,
2843 logical_id
=('loop', '/tmp/instance/disk2'))
2844 inst
= self
.cfg
.AddNewInstance(name
='instance',
2845 disk_template
=constants
.DT_FILE
,
2846 disks
=[file_disk
, mock_disk
],
2849 # Detach the disk and assert that it has been moved to the upper directory
2850 op
= self
.CopyOpCode(self
.op
,
2851 instance_name
=inst
.name
,
2852 disks
=[[constants
.DDM_DETACH
, -1,