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