Add more detach/attach sequence tests
[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 self.mocked_disk_uuid = "mock_uuid_1134"
2106 self.mocked_disk_name = "mock_disk_1134"
2107
2108 bootid = "mock_bootid"
2109 storage_info = [
2110 {
2111 "type": self.mocked_storage_type,
2112 "storage_free": self.mocked_storage_free
2113 }
2114 ]
2115 hv_info_master = {
2116 "cpu_total": self.mocked_master_cpu_total,
2117 "memory_free": self.mocked_master_memory_free
2118 }
2119 hv_info_snode = {
2120 "cpu_total": self.mocked_snode_cpu_total,
2121 "memory_free": self.mocked_snode_memory_free
2122 }
2123
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, ))) \
2130 .Build()
2131
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(
2139 self.master, {
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
2144 })
2145 else:
2146 raise AssertionError()
2147 self.rpc.call_instance_info.side_effect = _InstanceInfo
2148
2149 self.rpc.call_bridges_exist.return_value = \
2150 self.RpcResultsBuilder() \
2151 .CreateSuccessfulNodeResult(self.master, True)
2152
2153 self.rpc.call_blockdev_getmirrorstatus.side_effect = \
2154 lambda node, _: self.RpcResultsBuilder() \
2155 .CreateSuccessfulNodeResult(node, [])
2156
2157 self.rpc.call_blockdev_shutdown.side_effect = \
2158 lambda node, _: self.RpcResultsBuilder() \
2159 .CreateSuccessfulNodeResult(node, [])
2160
2161 def testNoChanges(self):
2162 op = self.CopyOpCode(self.op)
2163 self.ExecOpCodeExpectOpPrereqError(op, "No changes submitted")
2164
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")
2170
2171 def testHvparams(self):
2172 op = self.CopyOpCode(self.op,
2173 hvparams={constants.HV_BOOT_ORDER: "cd"})
2174 self.ExecOpCode(op)
2175
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"
2182 " the same time")
2183
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")
2190
2191 def testPrimaryNodeToOldPrimaryNode(self):
2192 op = self.CopyOpCode(self.op,
2193 pnode=self.master.name)
2194 self.ExecOpCode(op)
2195
2196 def testPrimaryNodeChange(self):
2197 node = self.cfg.AddNewNode()
2198 op = self.CopyOpCode(self.op,
2199 pnode=node.name)
2200 self.ExecOpCode(op)
2201
2202 def testPrimaryNodeChangeRunningInstance(self):
2203 node = self.cfg.AddNewNode()
2204 op = self.CopyOpCode(self.running_op,
2205 pnode=node.name)
2206 self.ExecOpCodeExpectOpPrereqError(op, "Instance is still running")
2207
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,
2212 os_name=os.name)
2213 self.ExecOpCode(op)
2214
2215 def testVCpuChange(self):
2216 op = self.CopyOpCode(self.op,
2217 beparams={
2218 constants.BE_VCPUS: 4
2219 })
2220 self.ExecOpCode(op)
2221
2222 def testWrongCpuMask(self):
2223 op = self.CopyOpCode(self.op,
2224 beparams={
2225 constants.BE_VCPUS: 4
2226 },
2227 hvparams={
2228 constants.HV_CPU_MASK: "1,2:3,4"
2229 })
2230 self.ExecOpCodeExpectOpPrereqError(
2231 op, "Number of vCPUs .* does not match the CPU mask .*")
2232
2233 def testCorrectCpuMask(self):
2234 op = self.CopyOpCode(self.op,
2235 beparams={
2236 constants.BE_VCPUS: 4
2237 },
2238 hvparams={
2239 constants.HV_CPU_MASK: "1,2:3,4:all:1,4"
2240 })
2241 self.ExecOpCode(op)
2242
2243 def testOsParams(self):
2244 op = self.CopyOpCode(self.op,
2245 osparams={
2246 self.os.supported_parameters[0]: "test_param_val"
2247 })
2248 self.ExecOpCode(op)
2249
2250 def testIncreaseMemoryTooMuch(self):
2251 op = self.CopyOpCode(self.running_op,
2252 beparams={
2253 constants.BE_MAXMEM:
2254 self.mocked_master_memory_free * 2
2255 })
2256 self.ExecOpCodeExpectOpPrereqError(
2257 op, "This change will prevent the instance from starting")
2258
2259 def testIncreaseMemory(self):
2260 op = self.CopyOpCode(self.running_op,
2261 beparams={
2262 constants.BE_MAXMEM: self.mocked_master_memory_free
2263 })
2264 self.ExecOpCode(op)
2265
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,
2273 {
2274 "memory":
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
2279 })]
2280
2281 op = self.CopyOpCode(self.op,
2282 instance_name=inst.name,
2283 beparams={
2284 constants.BE_MAXMEM:
2285 self.mocked_snode_memory_free * 2,
2286 constants.BE_AUTO_BALANCE: True
2287 })
2288 self.ExecOpCodeExpectOpPrereqError(
2289 op, "This change will prevent the instance from failover to its"
2290 " secondary node")
2291
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")
2297
2298 def testIncreaseRuntimeMemory(self):
2299 op = self.CopyOpCode(self.running_op,
2300 runtime_mem=self.mocked_master_memory_free,
2301 beparams={
2302 constants.BE_MAXMEM: self.mocked_master_memory_free
2303 })
2304 self.ExecOpCode(op)
2305
2306 def testAddNicWithPoolIpNoNetwork(self):
2307 op = self.CopyOpCode(self.op,
2308 nics=[(constants.DDM_ADD, -1,
2309 {
2310 constants.INIC_IP: constants.NIC_IP_POOL
2311 })])
2312 self.ExecOpCodeExpectOpPrereqError(
2313 op, "If ip=pool, parameter network cannot be none")
2314
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,
2320 {
2321 constants.INIC_IP: constants.NIC_IP_POOL,
2322 constants.INIC_NETWORK: net.name
2323 })])
2324 self.ExecOpCode(op)
2325
2326 def testAddNicWithInvalidIp(self):
2327 op = self.CopyOpCode(self.op,
2328 nics=[(constants.DDM_ADD, -1,
2329 {
2330 constants.INIC_IP: "invalid"
2331 })])
2332 self.ExecOpCodeExpectOpPrereqError(
2333 op, "Invalid IP address")
2334
2335 def testAddNic(self):
2336 op = self.CopyOpCode(self.op,
2337 nics=[(constants.DDM_ADD, -1, {})])
2338 self.ExecOpCode(op)
2339
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)
2345
2346 def testNoHotplugSupport(self):
2347 op = self.CopyOpCode(self.op,
2348 nics=[(constants.DDM_ADD, -1, {})],
2349 hotplug=True)
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)
2355
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)
2363 self.ExecOpCode(op)
2364 self.assertTrue(self.rpc.call_hotplug_supported.called)
2365 self.assertFalse(self.rpc.call_hotplug_device.called)
2366
2367 def testHotAddNic(self):
2368 op = self.CopyOpCode(self.op,
2369 nics=[(constants.DDM_ADD, -1, {})],
2370 hotplug=True)
2371 self.rpc.call_hotplug_supported.return_value = \
2372 self.RpcResultsBuilder() \
2373 .CreateSuccessfulNodeResult(self.master)
2374 self.ExecOpCode(op)
2375 self.assertTrue(self.rpc.call_hotplug_supported.called)
2376 self.assertTrue(self.rpc.call_hotplug_device.called)
2377
2378 def testAddNicWithIp(self):
2379 op = self.CopyOpCode(self.op,
2380 nics=[(constants.DDM_ADD, -1,
2381 {
2382 constants.INIC_IP: "2.3.1.4"
2383 })])
2384 self.ExecOpCode(op)
2385
2386 def testModifyNicRoutedWithoutIp(self):
2387 op = self.CopyOpCode(self.op,
2388 nics=[(constants.DDM_MODIFY, 0,
2389 {
2390 constants.INIC_NETWORK: constants.VALUE_NONE,
2391 constants.INIC_MODE: constants.NIC_MODE_ROUTED
2392 })])
2393 self.ExecOpCodeExpectOpPrereqError(
2394 op, "Cannot set the NIC IP address to None on a routed NIC"
2395 " if not attached to a network")
2396
2397 def testModifyNicSetMac(self):
2398 op = self.CopyOpCode(self.op,
2399 nics=[(constants.DDM_MODIFY, 0,
2400 {
2401 constants.INIC_MAC: "0a:12:95:15:bf:75"
2402 })])
2403 self.ExecOpCode(op)
2404
2405 def testModifyNicWithPoolIpNoNetwork(self):
2406 op = self.CopyOpCode(self.op,
2407 nics=[(constants.DDM_MODIFY, -1,
2408 {
2409 constants.INIC_IP: constants.NIC_IP_POOL
2410 })])
2411 self.ExecOpCodeExpectOpPrereqError(
2412 op, "ip=pool, but no network found")
2413
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")])
2420
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,
2426 {
2427 constants.INIC_NETWORK: new_net.name
2428 })])
2429 self.ExecOpCode(op)
2430
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)])
2436
2437 op = self.CopyOpCode(self.op,
2438 instance_name=inst.name,
2439 nics=[(constants.DDM_MODIFY, 0,
2440 {
2441 constants.INIC_LINK: "mock_link"
2442 })])
2443 self.ExecOpCodeExpectOpPrereqError(
2444 op, "Not allowed to change link or mode of a NIC that is connected"
2445 " to a network")
2446
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,
2452 {
2453 constants.INIC_NETWORK: net.name,
2454 constants.INIC_IP: "123.123.123.1"
2455 })])
2456 self.ExecOpCode(op)
2457
2458 def testModifyNic(self):
2459 op = self.CopyOpCode(self.op,
2460 nics=[(constants.DDM_MODIFY, 0, {})])
2461 self.ExecOpCode(op)
2462
2463 def testHotModifyNic(self):
2464 op = self.CopyOpCode(self.op,
2465 nics=[(constants.DDM_MODIFY, 0, {})],
2466 hotplug=True)
2467 self.rpc.call_hotplug_supported.return_value = \
2468 self.RpcResultsBuilder() \
2469 .CreateSuccessfulNodeResult(self.master)
2470 self.ExecOpCode(op)
2471 self.assertTrue(self.rpc.call_hotplug_supported.called)
2472 self.assertTrue(self.rpc.call_hotplug_device.called)
2473
2474 def testRemoveLastNic(self):
2475 op = self.CopyOpCode(self.op,
2476 nics=[(constants.DDM_REMOVE, 0, {})])
2477 self.ExecOpCodeExpectOpPrereqError(
2478 op, "violates policy")
2479
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, {})])
2486 self.ExecOpCode(op)
2487
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)
2493
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, {})],
2500 hotplug=True)
2501 self.rpc.call_hotplug_supported.return_value = \
2502 self.RpcResultsBuilder() \
2503 .CreateSuccessfulNodeResult(self.master)
2504 self.ExecOpCode(op)
2505 self.assertTrue(self.rpc.call_hotplug_supported.called)
2506 self.assertTrue(self.rpc.call_hotplug_device.called)
2507
2508 def testSetOffline(self):
2509 op = self.CopyOpCode(self.op,
2510 offline=True)
2511 self.ExecOpCode(op)
2512
2513 def testUnsetOffline(self):
2514 op = self.CopyOpCode(self.op,
2515 offline=False)
2516 self.ExecOpCode(op)
2517
2518 def testAddDiskInvalidMode(self):
2519 op = self.CopyOpCode(self.op,
2520 disks=[[constants.DDM_ADD, -1,
2521 {
2522 constants.IDISK_MODE: "invalid"
2523 }]])
2524 self.ExecOpCodeExpectOpPrereqError(
2525 op, "Invalid disk access mode 'invalid'")
2526
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")
2532
2533 def testAddDiskInvalidSize(self):
2534 op = self.CopyOpCode(self.op,
2535 disks=[[constants.DDM_ADD, -1,
2536 {
2537 constants.IDISK_SIZE: "invalid"
2538 }]])
2539 self.ExecOpCodeExpectException(
2540 op, errors.TypeEnforcementError, "is not a valid size")
2541
2542 def testAddDiskUnknownParam(self):
2543 op = self.CopyOpCode(self.op,
2544 disks=[[constants.DDM_ADD, -1,
2545 {
2546 "uuid": self.mocked_disk_uuid
2547 }]])
2548 self.ExecOpCodeExpectException(
2549 op, errors.TypeEnforcementError, "Unknown parameter 'uuid'")
2550
2551 def testAddDiskRunningInstanceNoWaitForSync(self):
2552 op = self.CopyOpCode(self.running_op,
2553 disks=[[constants.DDM_ADD, -1,
2554 {
2555 constants.IDISK_SIZE: 1024
2556 }]],
2557 wait_for_sync=False)
2558 self.ExecOpCode(op)
2559 self.assertFalse(self.rpc.call_blockdev_shutdown.called)
2560
2561 def testAddDiskDownInstance(self):
2562 op = self.CopyOpCode(self.op,
2563 disks=[[constants.DDM_ADD, -1,
2564 {
2565 constants.IDISK_SIZE: 1024
2566 }]])
2567 self.ExecOpCode(op)
2568 self.assertTrue(self.rpc.call_blockdev_shutdown.called)
2569
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,
2575 {
2576 constants.IDISK_SIZE: SPECIFIC_SIZE
2577 }]])
2578 self.ExecOpCode(op)
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)
2582
2583 def testAddDiskHugeIndex(self):
2584 op = self.CopyOpCode(self.op,
2585 disks=[[constants.DDM_ADD, 5,
2586 {
2587 constants.IDISK_SIZE: 1024
2588 }]])
2589 self.ExecOpCodeExpectException(
2590 op, IndexError, "Got disk index.*but there are only.*"
2591 )
2592
2593 def testAddExtDisk(self):
2594 op = self.CopyOpCode(self.ext_storage_op,
2595 disks=[[constants.DDM_ADD, -1,
2596 {
2597 constants.IDISK_SIZE: 1024
2598 }]])
2599 self.ExecOpCodeExpectOpPrereqError(op,
2600 "Missing provider for template 'ext'")
2601
2602 op = self.CopyOpCode(self.ext_storage_op,
2603 disks=[[constants.DDM_ADD, -1,
2604 {
2605 constants.IDISK_SIZE: 1024,
2606 constants.IDISK_PROVIDER: "bla"
2607 }]])
2608 self.ExecOpCode(op)
2609
2610 def testAddDiskDownInstanceNoWaitForSync(self):
2611 op = self.CopyOpCode(self.op,
2612 disks=[[constants.DDM_ADD, -1,
2613 {
2614 constants.IDISK_SIZE: 1024
2615 }]],
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")
2620
2621 def testAddDiskRunningInstance(self):
2622 op = self.CopyOpCode(self.running_op,
2623 disks=[[constants.DDM_ADD, -1,
2624 {
2625 constants.IDISK_SIZE: 1024
2626 }]])
2627 self.ExecOpCode(op)
2628
2629 self.assertFalse(self.rpc.call_blockdev_shutdown.called)
2630
2631 def testAddDiskNoneName(self):
2632 op = self.CopyOpCode(self.op,
2633 disks=[[constants.DDM_ADD, -1,
2634 {
2635 constants.IDISK_SIZE: 1024,
2636 constants.IDISK_NAME: constants.VALUE_NONE
2637 }]])
2638 self.ExecOpCode(op)
2639
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",
2645 None))
2646 op = self.CopyOpCode(self.op,
2647 disks=[[constants.DDM_ADD, -1,
2648 {
2649 constants.IDISK_SIZE: 1024,
2650 }]],
2651 hotplug=True)
2652 self.rpc.call_hotplug_supported.return_value = \
2653 self.RpcResultsBuilder() \
2654 .CreateSuccessfulNodeResult(self.master)
2655 self.ExecOpCode(op)
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)
2660
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,
2665 {
2666 constants.IDISK_SIZE: 1134
2667 }]],
2668 )
2669 self.ExecOpCodeExpectOpPrereqError(op, msg)
2670 op = self.CopyOpCode(self.op,
2671 disks=[[constants.DDM_ATTACH, -1,
2672 {
2673 'uuid': "1134",
2674 constants.IDISK_NAME: "1134",
2675 }]],
2676 )
2677 self.ExecOpCodeExpectOpPrereqError(op, msg)
2678 op = self.CopyOpCode(self.op,
2679 disks=[[constants.DDM_ATTACH, -1,
2680 {
2681 'uuid': "1134",
2682 constants.IDISK_SIZE: 1134,
2683 }]],
2684 )
2685 self.ExecOpCodeExpectOpPrereqError(op, msg)
2686
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, dev_type=constants.DT_BLOCK)
2691 op = self.CopyOpCode(self.op,
2692 disks=[[constants.DDM_ATTACH, -1,
2693 {
2694 constants.IDISK_NAME: self.mocked_disk_name
2695 }]],
2696 )
2697 self.ExecOpCodeExpectOpPrereqError(op, msg)
2698
2699 def testAttachDiskWrongNodes(self):
2700 msg = "Disk nodes are \['mock_node_1134'\]"
2701
2702 self.cfg.AddOrphanDisk(name=self.mocked_disk_name, primary_node="mock_node_1134")
2703 op = self.CopyOpCode(self.op,
2704 disks=[[constants.DDM_ATTACH, -1,
2705 {
2706 constants.IDISK_NAME: self.mocked_disk_name
2707 }]],
2708 )
2709 self.ExecOpCodeExpectOpPrereqError(op, msg)
2710
2711 def testAttachDiskRunningInstance(self):
2712 self.cfg.AddOrphanDisk(name=self.mocked_disk_name, primary_node=self.master.uuid)
2713 self.rpc.call_blockdev_assemble.return_value = \
2714 self.RpcResultsBuilder() \
2715 .CreateSuccessfulNodeResult(self.master,
2716 ("/dev/mocked_path",
2717 "/var/run/ganeti/instance-disks/mocked_d",
2718 None))
2719 op = self.CopyOpCode(self.running_op,
2720 disks=[[constants.DDM_ATTACH, -1,
2721 {
2722 constants.IDISK_NAME: self.mocked_disk_name
2723 }]],
2724 )
2725 self.ExecOpCode(op)
2726 self.assertTrue(self.rpc.call_blockdev_assemble.called)
2727 self.assertFalse(self.rpc.call_blockdev_shutdown.called)
2728
2729 def testAttachDiskRunningInstanceNoWaitForSync(self):
2730 self.cfg.AddOrphanDisk(name=self.mocked_disk_name, primary_node=self.master.uuid)
2731 self.rpc.call_blockdev_assemble.return_value = \
2732 self.RpcResultsBuilder() \
2733 .CreateSuccessfulNodeResult(self.master,
2734 ("/dev/mocked_path",
2735 "/var/run/ganeti/instance-disks/mocked_d",
2736 None))
2737 op = self.CopyOpCode(self.running_op,
2738 disks=[[constants.DDM_ATTACH, -1,
2739 {
2740 constants.IDISK_NAME: self.mocked_disk_name
2741 }]],
2742 wait_for_sync=False)
2743 self.ExecOpCode(op)
2744 self.assertTrue(self.rpc.call_blockdev_assemble.called)
2745 self.assertFalse(self.rpc.call_blockdev_shutdown.called)
2746
2747 def testAttachDiskDownInstance(self):
2748 self.cfg.AddOrphanDisk(name=self.mocked_disk_name, primary_node=self.master.uuid)
2749 op = self.CopyOpCode(self.op,
2750 disks=[[constants.DDM_ATTACH, -1,
2751 {
2752 constants.IDISK_NAME: self.mocked_disk_name
2753 }]])
2754 self.ExecOpCode(op)
2755
2756 self.assertTrue(self.rpc.call_blockdev_assemble.called)
2757 self.assertTrue(self.rpc.call_blockdev_shutdown.called)
2758
2759 def testAttachDiskDownInstanceNoWaitForSync(self):
2760 self.cfg.AddOrphanDisk(name=self.mocked_disk_name)
2761 op = self.CopyOpCode(self.op,
2762 disks=[[constants.DDM_ATTACH, -1,
2763 {
2764 constants.IDISK_NAME: self.mocked_disk_name
2765 }]],
2766 wait_for_sync=False)
2767 self.ExecOpCodeExpectOpPrereqError(
2768 op, "Can't attach a disk to an instance with deactivated disks"
2769 " and --no-wait-for-sync given.")
2770
2771 def testHotAttachDisk(self):
2772 self.cfg.AddOrphanDisk(name=self.mocked_disk_name, primary_node=self.master.uuid)
2773 self.rpc.call_blockdev_assemble.return_value = \
2774 self.RpcResultsBuilder() \
2775 .CreateSuccessfulNodeResult(self.master,
2776 ("/dev/mocked_path",
2777 "/var/run/ganeti/instance-disks/mocked_d",
2778 None))
2779 op = self.CopyOpCode(self.op,
2780 disks=[[constants.DDM_ATTACH, -1,
2781 {
2782 constants.IDISK_NAME: self.mocked_disk_name
2783 }]],
2784 hotplug=True)
2785 self.rpc.call_hotplug_supported.return_value = \
2786 self.RpcResultsBuilder() \
2787 .CreateSuccessfulNodeResult(self.master)
2788 self.ExecOpCode(op)
2789 self.assertTrue(self.rpc.call_hotplug_supported.called)
2790 self.assertTrue(self.rpc.call_blockdev_assemble.called)
2791 self.assertTrue(self.rpc.call_hotplug_device.called)
2792
2793 def testHotRemoveDisk(self):
2794 inst = self.cfg.AddNewInstance(disks=[self.cfg.CreateDisk(),
2795 self.cfg.CreateDisk()])
2796 op = self.CopyOpCode(self.op,
2797 instance_name=inst.name,
2798 disks=[[constants.DDM_REMOVE, -1,
2799 {}]],
2800 hotplug=True)
2801 self.rpc.call_hotplug_supported.return_value = \
2802 self.RpcResultsBuilder() \
2803 .CreateSuccessfulNodeResult(self.master)
2804 self.ExecOpCode(op)
2805 self.assertTrue(self.rpc.call_hotplug_supported.called)
2806 self.assertTrue(self.rpc.call_hotplug_device.called)
2807 self.assertTrue(self.rpc.call_blockdev_shutdown.called)
2808 self.assertTrue(self.rpc.call_blockdev_remove.called)
2809
2810 def testHotDetachDisk(self):
2811 inst = self.cfg.AddNewInstance(disks=[self.cfg.CreateDisk(),
2812 self.cfg.CreateDisk()])
2813 op = self.CopyOpCode(self.op,
2814 instance_name=inst.name,
2815 disks=[[constants.DDM_DETACH, -1,
2816 {}]],
2817 hotplug=True)
2818 self.rpc.call_hotplug_supported.return_value = \
2819 self.RpcResultsBuilder() \
2820 .CreateSuccessfulNodeResult(self.master)
2821 self.ExecOpCode(op)
2822 self.assertTrue(self.rpc.call_hotplug_supported.called)
2823 self.assertTrue(self.rpc.call_hotplug_device.called)
2824 self.assertTrue(self.rpc.call_blockdev_shutdown.called)
2825
2826 def testDetachAttachFileBasedDisk(self):
2827 """Detach and re-attach a disk from a file-based instance."""
2828 # Create our disk and calculate the path where it is stored, its name, as
2829 # well as the expected path where it will be moved.
2830 mock_disk = self.cfg.CreateDisk(
2831 name='mock_disk_1134', dev_type=constants.DT_FILE,
2832 logical_id=('loop', '/tmp/instance/disk'), primary_node=self.master.uuid)
2833
2834 # Create a file-based instance
2835 file_disk = self.cfg.CreateDisk(
2836 dev_type=constants.DT_FILE,
2837 logical_id=('loop', '/tmp/instance/disk2'))
2838 inst = self.cfg.AddNewInstance(name='instance',
2839 disk_template=constants.DT_FILE,
2840 disks=[file_disk, mock_disk],
2841 )
2842
2843 # Detach the disk and assert that it has been moved to the upper directory
2844 op = self.CopyOpCode(self.op,
2845 instance_name=inst.name,
2846 disks=[[constants.DDM_DETACH, -1,
2847 {}]],
2848 )
2849 self.ExecOpCode(op)
2850 mock_disk = self.cfg.GetDiskInfo(mock_disk.uuid)
2851 self.assertEqual('/tmp/disk', mock_disk.logical_id[1])
2852
2853 # Re-attach the disk and assert that it has been moved to the original