4 # Copyright (C) 2007, 2011, 2012, 2013 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 """Instance related QA tests.
29 from ganeti import utils
30 from ganeti import constants
31 from ganeti import query
32 from ganeti import pathutils
38 from qa_utils import AssertCommand, AssertEqual
39 from qa_utils import InstanceCheck, INST_DOWN, INST_UP, FIRST_ARG, RETURN_VALUE
40 from qa_instance_utils import CheckSsconfInstanceList, \
41 CreateInstanceDrbd8, \
42 CreateInstanceByDiskTemplate, \
43 CreateInstanceByDiskTemplateOneNode, \
44 GetGenericAddParameters
47 def _GetDiskStatePath(disk):
48 return "/sys/block/%s/device/state" % disk
51 def GetInstanceInfo(instance):
52 """Return information about the actual state of an instance.
54 @type instance: string
55 @param instance: the instance name
56 @return: a dictionary with the following keys:
57 - "nodes": instance nodes, a list of strings
58 - "volumes": instance volume IDs, a list of strings
59 - "drbd-minors": DRBD minors used by the instance, a dictionary where
60 keys are nodes, and values are lists of integers (or an empty
61 dictionary for non-DRBD instances)
62 - "disk-template": instance disk template
63 - "storage-type": storage type associated with the instance disk template
66 node_elem = r"([^,()]+)(?:\s+\([^)]+\))?"
67 # re_nodelist matches a list of nodes returned by gnt-instance info, e.g.:
69 # node2.fqdn,node3.fqdn
70 # node4.fqdn (group mygroup, group UUID 01234567-abcd-0123-4567-0123456789ab)
71 # FIXME This works with no more than 2 secondaries
72 re_nodelist = re.compile(node_elem + "(?:," + node_elem + ")?$")
74 info = qa_utils.GetObjectInfo(["gnt-instance", "info", instance])[0]
76 for nodeinfo in info["Nodes"]:
77 if "primary" in nodeinfo:
78 nodes.append(nodeinfo["primary"])
79 elif "secondaries" in nodeinfo:
80 nodestr = nodeinfo["secondaries"]
82 m = re_nodelist.match(nodestr)
84 nodes.extend(filter(None, m.groups()))
88 disk_template = info["Disk template"]
90 raise qa_error.Error("Can't get instance disk template")
91 storage_type = constants.MAP_DISK_TEMPLATE_STORAGE_TYPE[disk_template]
93 re_drbdnode = re.compile(r"^([^\s,]+),\s+minor=([0-9]+)$")
96 for (count, diskinfo) in enumerate(info["Disks"]):
97 (dtype, _) = diskinfo["disk/%s" % count].split(",", 1)
98 if dtype == constants.DT_DRBD8:
99 for child in diskinfo["child devices"]:
100 vols.append(child["logical_id"])
101 for key in ["nodeA", "nodeB"]:
102 m = re_drbdnode.match(diskinfo[key])
104 raise qa_error.Error("Cannot parse DRBD info: %s" % diskinfo[key])
106 minor = int(m.group(2))
107 minorlist = drbd_min.setdefault(node, [])
108 minorlist.append(minor)
109 elif dtype == constants.DT_PLAIN:
110 vols.append(diskinfo["logical_id"])
113 assert len(nodes) < 2 or vols
117 "drbd-minors": drbd_min,
118 "disk-template": disk_template,
119 "storage-type": storage_type,
123 def _DestroyInstanceDisks(instance):
124 """Remove all the backend disks of an instance.
126 This is used to simulate HW errors (dead nodes, broken disks...); the
127 configuration of the instance is not affected.
128 @type instance: dictionary
129 @param instance: the instance
132 info = GetInstanceInfo(instance.name)
133 # FIXME: destruction/removal should be part of the disk class
134 if info["storage-type"] == constants.ST_LVM_VG:
135 vols = info["volumes"]
136 for node in info["nodes"]:
137 AssertCommand(["lvremove", "-f"] + vols, node=node)
138 elif info["storage-type"] == constants.ST_FILE:
139 # Note that this works for both file and sharedfile, and this is intended.
140 storage_dir = qa_config.get("file-storage-dir",
141 pathutils.DEFAULT_FILE_STORAGE_DIR)
142 idir = os.path.join(storage_dir, instance.name)
143 for node in info["nodes"]:
144 AssertCommand(["rm", "-rf", idir], node=node)
145 elif info["storage-type"] == constants.ST_DISKLESS:
149 def _GetInstanceField(instance, field):
150 """Get the value of a field of an instance.
152 @type instance: string
153 @param instance: Instance name
155 @param field: Name of the field
159 master = qa_config.GetMasterNode()
160 infocmd = utils.ShellQuoteArgs(["gnt-instance", "list", "--no-headers",
161 "--units", "m", "-o", field, instance])
162 return qa_utils.GetCommandOutput(master.primary, infocmd).strip()
165 def _GetBoolInstanceField(instance, field):
166 """Get the Boolean value of a field of an instance.
168 @type instance: string
169 @param instance: Instance name
171 @param field: Name of the field
175 info_out = _GetInstanceField(instance, field)
178 elif info_out == "N":
181 raise qa_error.Error("Field %s of instance %s has a non-Boolean value:"
182 " %s" % (field, instance, info_out))
185 def _GetNumInstanceField(instance, field):
186 """Get a numeric value of a field of an instance.
188 @type instance: string
189 @param instance: Instance name
191 @param field: Name of the field
195 info_out = _GetInstanceField(instance, field)
200 ret = float(info_out)
202 raise qa_error.Error("Field %s of instance %s has a non-numeric value:"
203 " %s" % (field, instance, info_out))
207 def GetInstanceSpec(instance, spec):
208 """Return the current spec for the given parameter.
210 @type instance: string
211 @param instance: Instance name
213 @param spec: one of the supported parameters: "memory-size", "cpu-count",
214 "disk-count", "disk-size", "nic-count"
216 @return: (minspec, maxspec); minspec and maxspec can be different only for
221 "memory-size": ["be/minmem", "be/maxmem"],
222 "cpu-count": ["vcpus"],
223 "disk-count": ["disk.count"],
224 "disk-size": ["disk.size/ "],
225 "nic-count": ["nic.count"],
227 # For disks, first we need the number of disks
228 if spec == "disk-size":
229 (numdisk, _) = GetInstanceSpec(instance, "disk-count")
230 fields = ["disk.size/%s" % k for k in range(0, numdisk)]
232 assert spec in specmap, "%s not in %s" % (spec, specmap)
233 fields = specmap[spec]
234 values = [_GetNumInstanceField(instance, f) for f in fields]
235 return (min(values), max(values))
238 def IsFailoverSupported(instance):
239 return instance.disk_template in constants.DTS_MIRRORED
242 def IsMigrationSupported(instance):
243 return instance.disk_template in constants.DTS_MIRRORED
246 def IsDiskReplacingSupported(instance):
247 return instance.disk_template == constants.DT_DRBD8
250 def IsDiskSupported(instance):
251 return instance.disk_template != constants.DT_DISKLESS
254 def TestInstanceAddWithPlainDisk(nodes, fail=False):
255 """gnt-instance add -t plain"""
256 if constants.DT_PLAIN in qa_config.GetEnabledDiskTemplates():
257 instance = CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_PLAIN,
260 qa_utils.RunInstanceCheck(instance, True)
264 @InstanceCheck(None, INST_UP, RETURN_VALUE)
265 def TestInstanceAddWithDrbdDisk(nodes):
266 """gnt-instance add -t drbd"""
267 if constants.DT_DRBD8 in qa_config.GetEnabledDiskTemplates():
268 return CreateInstanceDrbd8(nodes)
271 @InstanceCheck(None, INST_UP, RETURN_VALUE)
272 def TestInstanceAddFile(nodes):
273 """gnt-instance add -t file"""
274 assert len(nodes) == 1
275 if constants.DT_FILE in qa_config.GetEnabledDiskTemplates():
276 return CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_FILE)
279 @InstanceCheck(None, INST_UP, RETURN_VALUE)
280 def TestInstanceAddSharedFile(nodes):
281 """gnt-instance add -t sharedfile"""
282 assert len(nodes) == 1
283 if constants.DT_SHARED_FILE in qa_config.GetEnabledDiskTemplates():
284 return CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_SHARED_FILE)
287 @InstanceCheck(None, INST_UP, RETURN_VALUE)
288 def TestInstanceAddDiskless(nodes):
289 """gnt-instance add -t diskless"""
290 assert len(nodes) == 1
291 if constants.DT_DISKLESS in qa_config.GetEnabledDiskTemplates():
292 return CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_DISKLESS)
295 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
296 def TestInstanceRemove(instance):
297 """gnt-instance remove"""
298 AssertCommand(["gnt-instance", "remove", "-f", instance.name])
301 @InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
302 def TestInstanceStartup(instance):
303 """gnt-instance startup"""
304 AssertCommand(["gnt-instance", "startup", instance.name])
307 @InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
308 def TestInstanceShutdown(instance):
309 """gnt-instance shutdown"""
310 AssertCommand(["gnt-instance", "shutdown", instance.name])
313 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
314 def TestInstanceReboot(instance):
315 """gnt-instance reboot"""
316 options = qa_config.get("options", {})
317 reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
319 for rtype in reboot_types:
320 AssertCommand(["gnt-instance", "reboot", "--type=%s" % rtype, name])
322 AssertCommand(["gnt-instance", "shutdown", name])
323 qa_utils.RunInstanceCheck(instance, False)
324 AssertCommand(["gnt-instance", "reboot", name])
326 master = qa_config.GetMasterNode()
327 cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", name]
328 result_output = qa_utils.GetCommandOutput(master.primary,
329 utils.ShellQuoteArgs(cmd))
330 AssertEqual(result_output.strip(), constants.INSTST_RUNNING)
333 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
334 def TestInstanceReinstall(instance):
335 """gnt-instance reinstall"""
336 if instance.disk_template == constants.DT_DISKLESS:
337 print qa_utils.FormatInfo("Test not supported for diskless instances")
340 AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
342 # Test with non-existant OS definition
343 AssertCommand(["gnt-instance", "reinstall", "-f",
344 "--os-type=NonExistantOsForQa",
349 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
350 def TestInstanceRenameAndBack(rename_source, rename_target):
351 """gnt-instance rename
353 This must leave the instance with the original name, not the target
357 CheckSsconfInstanceList(rename_source)
359 # first do a rename to a different actual name, expecting it to fail
360 qa_utils.AddToEtcHosts(["meeeeh-not-exists", rename_target])
362 AssertCommand(["gnt-instance", "rename", rename_source, rename_target],
364 CheckSsconfInstanceList(rename_source)
366 qa_utils.RemoveFromEtcHosts(["meeeeh-not-exists", rename_target])
368 info = GetInstanceInfo(rename_source)
370 # Check instance volume tags correctly updated. Note that this check is lvm
371 # specific, so we skip it for non-lvm-based instances.
372 # FIXME: This will need updating when instances will be able to have
373 # different disks living on storage pools with etherogeneous storage types.
374 # FIXME: This check should be put inside the disk/storage class themselves,
375 # rather than explicitly called here.
376 if info["storage-type"] == constants.ST_LVM_VG:
377 # In the lvm world we can check for tags on the logical volume
378 tags_cmd = ("lvs -o tags --noheadings %s | grep " %
379 (" ".join(info["volumes"]), ))
381 # Other storage types don't have tags, so we use an always failing command,
382 # to make sure it never gets executed
385 # and now rename instance to rename_target...
386 AssertCommand(["gnt-instance", "rename", rename_source, rename_target])
387 CheckSsconfInstanceList(rename_target)
388 qa_utils.RunInstanceCheck(rename_source, False)
389 qa_utils.RunInstanceCheck(rename_target, False)
391 # NOTE: tags might not be the exactly as the instance name, due to
392 # charset restrictions; hence the test might be flaky
393 if (rename_source != rename_target and
394 info["storage-type"] == constants.ST_LVM_VG):
395 for node in info["nodes"]:
396 AssertCommand(tags_cmd + rename_source, node=node, fail=True)
397 AssertCommand(tags_cmd + rename_target, node=node, fail=False)
400 AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
401 CheckSsconfInstanceList(rename_source)
402 qa_utils.RunInstanceCheck(rename_target, False)
404 if (rename_source != rename_target and
405 info["storage-type"] == constants.ST_LVM_VG):
406 for node in info["nodes"]:
407 AssertCommand(tags_cmd + rename_source, node=node, fail=False)
408 AssertCommand(tags_cmd + rename_target, node=node, fail=True)
411 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
412 def TestInstanceFailover(instance):
413 """gnt-instance failover"""
414 if not IsFailoverSupported(instance):
415 print qa_utils.FormatInfo("Instance doesn't support failover, skipping"
419 cmd = ["gnt-instance", "failover", "--force", instance.name]
423 qa_utils.RunInstanceCheck(instance, True)
429 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
430 def TestInstanceMigrate(instance, toggle_always_failover=True):
431 """gnt-instance migrate"""
432 if not IsMigrationSupported(instance):
433 print qa_utils.FormatInfo("Instance doesn't support migration, skipping"
437 cmd = ["gnt-instance", "migrate", "--force", instance.name]
438 af_par = constants.BE_ALWAYS_FAILOVER
439 af_field = "be/" + constants.BE_ALWAYS_FAILOVER
440 af_init_val = _GetBoolInstanceField(instance.name, af_field)
444 # TODO: Verify the choice between failover and migration
445 qa_utils.RunInstanceCheck(instance, True)
447 # ... and back (possibly with always_failover toggled)
448 if toggle_always_failover:
449 AssertCommand(["gnt-instance", "modify", "-B",
450 ("%s=%s" % (af_par, not af_init_val)),
453 # TODO: Verify the choice between failover and migration
454 qa_utils.RunInstanceCheck(instance, True)
455 if toggle_always_failover:
456 AssertCommand(["gnt-instance", "modify", "-B",
457 ("%s=%s" % (af_par, af_init_val)), instance.name])
459 # TODO: Split into multiple tests
460 AssertCommand(["gnt-instance", "shutdown", instance.name])
461 qa_utils.RunInstanceCheck(instance, False)
462 AssertCommand(cmd, fail=True)
463 AssertCommand(["gnt-instance", "migrate", "--force", "--allow-failover",
465 AssertCommand(["gnt-instance", "start", instance.name])
467 # @InstanceCheck enforces the check that the instance is running
468 qa_utils.RunInstanceCheck(instance, True)
470 AssertCommand(["gnt-instance", "modify", "-B",
472 (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)),
476 qa_utils.RunInstanceCheck(instance, True)
477 # TODO: Verify that a failover has been done instead of a migration
479 # TODO: Verify whether the default value is restored here (not hardcoded)
480 AssertCommand(["gnt-instance", "modify", "-B",
482 (constants.BE_ALWAYS_FAILOVER, constants.VALUE_FALSE)),
486 qa_utils.RunInstanceCheck(instance, True)
489 def TestInstanceInfo(instance):
490 """gnt-instance info"""
491 AssertCommand(["gnt-instance", "info", instance.name])
494 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
495 def TestInstanceModify(instance):
496 """gnt-instance modify"""
497 default_hv = qa_config.GetDefaultHypervisor()
499 # Assume /sbin/init exists on all systems
500 test_kernel = "/sbin/init"
501 test_initrd = test_kernel
503 orig_maxmem = qa_config.get(constants.BE_MAXMEM)
504 orig_minmem = qa_config.get(constants.BE_MINMEM)
505 #orig_bridge = qa_config.get("bridge", "xen-br0")
508 ["-B", "%s=128" % constants.BE_MINMEM],
509 ["-B", "%s=128" % constants.BE_MAXMEM],
510 ["-B", "%s=%s,%s=%s" % (constants.BE_MINMEM, orig_minmem,
511 constants.BE_MAXMEM, orig_maxmem)],
512 ["-B", "%s=2" % constants.BE_VCPUS],
513 ["-B", "%s=1" % constants.BE_VCPUS],
514 ["-B", "%s=%s" % (constants.BE_VCPUS, constants.VALUE_DEFAULT)],
515 ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)],
516 ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_DEFAULT)],
518 ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, test_kernel)],
519 ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, constants.VALUE_DEFAULT)],
522 #["--bridge", "xen-br1"],
523 #["--bridge", orig_bridge],
526 if default_hv == constants.HT_XEN_PVM:
528 ["-H", "%s=%s" % (constants.HV_INITRD_PATH, test_initrd)],
529 ["-H", "no_%s" % (constants.HV_INITRD_PATH, )],
530 ["-H", "%s=%s" % (constants.HV_INITRD_PATH, constants.VALUE_DEFAULT)],
532 elif default_hv == constants.HT_XEN_HVM:
534 ["-H", "%s=acn" % constants.HV_BOOT_ORDER],
535 ["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
537 elif default_hv == constants.HT_KVM and \
538 qa_config.TestEnabled("instance-device-hotplug"):
540 ["--net", "-1:add", "--hotplug"],
541 ["--net", "-1:modify,mac=aa:bb:cc:dd:ee:ff", "--hotplug"],
542 ["--net", "-1:remove", "--hotplug"],
543 ["--disk", "-1:add,size=1G", "--hotplug"],
544 ["--disk", "-1:remove", "--hotplug"],
548 AssertCommand(["gnt-instance", "modify"] + alist + [instance.name])
551 AssertCommand(["gnt-instance", "modify", instance.name], fail=True)
553 # Marking offline while instance is running must fail...
554 AssertCommand(["gnt-instance", "modify", "--offline", instance.name],
557 # ...while making it online is ok, and should work
558 AssertCommand(["gnt-instance", "modify", "--online", instance.name])
561 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
562 def TestInstanceModifyPrimaryAndBack(instance, currentnode, othernode):
563 """gnt-instance modify --new-primary
565 This will leave the instance on its original primary node, not other node.
568 if instance.disk_template != constants.DT_FILE:
569 print qa_utils.FormatInfo("Test only supported for the file disk template")
572 cluster_name = qa_config.get("name")
575 current = currentnode.primary
576 other = othernode.primary
578 filestorage = qa_config.get("file-storage-dir",
579 pathutils.DEFAULT_FILE_STORAGE_DIR)
580 disk = os.path.join(filestorage, name)
582 AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name],
584 AssertCommand(["gnt-instance", "shutdown", name])
585 AssertCommand(["scp", "-oGlobalKnownHostsFile=%s" %
586 pathutils.SSH_KNOWN_HOSTS_FILE,
587 "-oCheckHostIp=no", "-oStrictHostKeyChecking=yes",
588 "-oHashKnownHosts=no", "-oHostKeyAlias=%s" % cluster_name,
589 "-r", disk, "%s:%s" % (other, filestorage)], node=current)
590 AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name])
591 AssertCommand(["gnt-instance", "startup", name])
594 AssertCommand(["gnt-instance", "shutdown", name])
595 AssertCommand(["rm", "-rf", disk], node=other)
596 AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % current, name])
597 AssertCommand(["gnt-instance", "startup", name])
600 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
601 def TestInstanceStoppedModify(instance):
602 """gnt-instance modify (stopped instance)"""
605 # Instance was not marked offline; try marking it online once more
606 AssertCommand(["gnt-instance", "modify", "--online", name])
608 # Mark instance as offline
609 AssertCommand(["gnt-instance", "modify", "--offline", name])
611 # When the instance is offline shutdown should only work with --force,
612 # while start should never work
613 AssertCommand(["gnt-instance", "shutdown", name], fail=True)
614 AssertCommand(["gnt-instance", "shutdown", "--force", name])
615 AssertCommand(["gnt-instance", "start", name], fail=True)
616 AssertCommand(["gnt-instance", "start", "--force", name], fail=True)
618 # Also do offline to offline
619 AssertCommand(["gnt-instance", "modify", "--offline", name])
622 AssertCommand(["gnt-instance", "modify", "--online", name])
625 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
626 def TestInstanceConvertDiskToPlain(instance, inodes):
627 """gnt-instance modify -t"""
630 template = instance.disk_template
631 if template != constants.DT_DRBD8:
632 print qa_utils.FormatInfo("Unsupported template %s, skipping conversion"
636 assert len(inodes) == 2
637 AssertCommand(["gnt-instance", "modify", "-t", constants.DT_PLAIN, name])
638 AssertCommand(["gnt-instance", "modify", "-t", constants.DT_DRBD8,
639 "-n", inodes[1].primary, name])
642 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
643 def TestInstanceModifyDisks(instance):
644 """gnt-instance modify --disk"""
645 if not IsDiskSupported(instance):
646 print qa_utils.FormatInfo("Instance doesn't support disks, skipping test")
649 disk_conf = qa_config.GetDiskOptions()[-1]
650 size = disk_conf.get("size")
652 build_cmd = lambda arg: ["gnt-instance", "modify", "--disk", arg, name]
653 if qa_config.AreSpindlesSupported():
654 spindles = disk_conf.get("spindles")
655 spindles_supported = True
657 # Any number is good for spindles in this case
659 spindles_supported = False
660 AssertCommand(build_cmd("add:size=%s,spindles=%s" % (size, spindles)),
661 fail=not spindles_supported)
662 AssertCommand(build_cmd("add:size=%s" % size),
663 fail=spindles_supported)
664 # Exactly one of the above commands has succeded, so we need one remove
665 AssertCommand(build_cmd("remove"))
668 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
669 def TestInstanceGrowDisk(instance):
670 """gnt-instance grow-disk"""
671 if instance.disk_template == constants.DT_DISKLESS:
672 print qa_utils.FormatInfo("Test not supported for diskless instances")
676 disks = qa_config.GetDiskOptions()
677 all_size = [d.get("size") for d in disks]
678 all_grow = [d.get("growth") for d in disks]
681 # missing disk sizes but instance grow disk has been enabled,
682 # let's set fixed/nomimal growth
683 all_grow = ["128M" for _ in all_size]
685 for idx, (size, grow) in enumerate(zip(all_size, all_grow)):
686 # succeed in grow by amount
687 AssertCommand(["gnt-instance", "grow-disk", name, str(idx), grow])
688 # fail in grow to the old size
689 AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
691 # succeed to grow to old size + 2 * growth
692 int_size = utils.ParseUnit(size)
693 int_grow = utils.ParseUnit(grow)
694 AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
695 str(int_size + 2 * int_grow)])
698 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
699 def TestInstanceDeviceNames(instance):
700 if instance.disk_template == constants.DT_DISKLESS:
701 print qa_utils.FormatInfo("Test not supported for diskless instances")
705 for dev_type in ["disk", "net"]:
706 if dev_type == "disk":
707 options = ",size=512M"
708 if qa_config.AreSpindlesSupported():
709 options += ",spindles=1"
712 # succeed in adding a device named 'test_device'
713 AssertCommand(["gnt-instance", "modify",
714 "--%s=-1:add,name=test_device%s" % (dev_type, options),
716 # succeed in removing the 'test_device'
717 AssertCommand(["gnt-instance", "modify",
718 "--%s=test_device:remove" % dev_type,
720 # fail to add two devices with the same name
721 AssertCommand(["gnt-instance", "modify",
722 "--%s=-1:add,name=test_device%s" % (dev_type, options),
723 "--%s=-1:add,name=test_device%s" % (dev_type, options),
725 # fail to add a device with invalid name
726 AssertCommand(["gnt-instance", "modify",
727 "--%s=-1:add,name=2%s" % (dev_type, options),
730 disks = qa_config.GetDiskOptions()
731 disk_names = [d.get("name") for d in disks]
732 for idx, disk_name in enumerate(disk_names):
733 # Refer to disk by idx
734 AssertCommand(["gnt-instance", "modify",
735 "--disk=%s:modify,name=renamed" % idx,
737 # Refer to by name and rename to original name
738 AssertCommand(["gnt-instance", "modify",
739 "--disk=renamed:modify,name=%s" % disk_name,
742 # fail in renaming to disks to the same name
743 AssertCommand(["gnt-instance", "modify",
744 "--disk=0:modify,name=same_name",
745 "--disk=1:modify,name=same_name",
749 def TestInstanceList():
750 """gnt-instance list"""
751 qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
754 def TestInstanceListFields():
755 """gnt-instance list-fields"""
756 qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
759 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
760 def TestInstanceConsole(instance):
761 """gnt-instance console"""
762 AssertCommand(["gnt-instance", "console", "--show-cmd", instance.name])
765 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
766 def TestReplaceDisks(instance, curr_nodes, other_nodes):
767 """gnt-instance replace-disks"""
769 cmd = ["gnt-instance", "replace-disks"]
771 cmd.append(instance.name)
774 if not IsDiskReplacingSupported(instance):
775 print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
779 # Currently all supported templates have one primary and one secondary node
780 assert len(curr_nodes) == 2
781 snode = curr_nodes[1]
782 assert len(other_nodes) == 1
783 othernode = other_nodes[0]
785 options = qa_config.get("options", {})
786 use_ialloc = options.get("use-iallocators", True)
790 # A placeholder; the actual command choice depends on use_ialloc
792 # Restore the original secondary
793 ["--new-secondary=%s" % snode.primary],
797 data = ["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT]
799 data = ["--new-secondary=%s" % othernode.primary]
800 AssertCommand(buildcmd(data))
802 AssertCommand(buildcmd(["-a"]))
803 AssertCommand(["gnt-instance", "stop", instance.name])
804 AssertCommand(buildcmd(["-a"]), fail=True)
805 AssertCommand(["gnt-instance", "activate-disks", instance.name])
806 AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
808 AssertCommand(buildcmd(["-a"]))
809 AssertCommand(["gnt-instance", "start", instance.name])
812 def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
814 """Execute gnt-instance recreate-disks and check the result
816 @param cmdargs: Arguments (instance name excluded)
817 @param instance: Instance to operate on
818 @param fail: True if the command is expected to fail
819 @param check: If True and fail is False, check that the disks work
820 @prama destroy: If True, destroy the old disks first
824 _DestroyInstanceDisks(instance)
825 AssertCommand((["gnt-instance", "recreate-disks"] + cmdargs +
826 [instance.name]), fail)
827 if not fail and check:
828 # Quick check that the disks are there
829 AssertCommand(["gnt-instance", "activate-disks", instance.name])
830 AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
832 AssertCommand(["gnt-instance", "deactivate-disks", instance.name])
835 def _BuildRecreateDisksOpts(en_disks, with_spindles, with_growth,
838 if spindles_supported:
840 build_spindles_opt = (lambda disk:
842 (disk["spindles"] + disk["spindles-growth"]))
844 build_spindles_opt = (lambda disk:
845 ",spindles=%s" % disk["spindles"])
847 build_spindles_opt = (lambda _: ",spindles=1")
849 build_spindles_opt = (lambda _: "")
851 build_size_opt = (lambda disk:
852 "size=%s" % (utils.ParseUnit(disk["size"]) +
853 utils.ParseUnit(disk["growth"])))
855 build_size_opt = (lambda disk: "size=%s" % disk["size"])
856 build_disk_opt = (lambda (idx, disk):
857 "--disk=%s:%s%s" % (idx, build_size_opt(disk),
858 build_spindles_opt(disk)))
859 return map(build_disk_opt, en_disks)
862 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
863 def TestRecreateDisks(instance, inodes, othernodes):
864 """gnt-instance recreate-disks
866 @param instance: Instance to work on
867 @param inodes: List of the current nodes of the instance
868 @param othernodes: list/tuple of nodes where to temporarily recreate disks
871 options = qa_config.get("options", {})
872 use_ialloc = options.get("use-iallocators", True)
873 other_seq = ":".join([n.primary for n in othernodes])
874 orig_seq = ":".join([n.primary for n in inodes])
875 # These fail because the instance is running
876 _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
878 _AssertRecreateDisks(["-I", "hail"], instance, fail=True, destroy=False)
880 _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
881 AssertCommand(["gnt-instance", "stop", instance.name])
882 # Disks exist: this should fail
883 _AssertRecreateDisks([], instance, fail=True, destroy=False)
884 # Unsupported spindles parameters: fail
885 if not qa_config.AreSpindlesSupported():
886 _AssertRecreateDisks(["--disk=0:spindles=2"], instance,
887 fail=True, destroy=False)
888 # Recreate disks in place
889 _AssertRecreateDisks([], instance)
892 _AssertRecreateDisks(["-I", "hail"], instance)
893 # Move disks somewhere else
894 _AssertRecreateDisks(["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT],
897 _AssertRecreateDisks(["-n", other_seq], instance)
899 _AssertRecreateDisks(["-n", orig_seq], instance)
900 # Recreate resized disks
901 # One of the two commands fails because either spindles are given when they
902 # should not or vice versa
903 alldisks = qa_config.GetDiskOptions()
904 spindles_supported = qa_config.AreSpindlesSupported()
905 disk_opts = _BuildRecreateDisksOpts(enumerate(alldisks), True, True,
907 _AssertRecreateDisks(disk_opts, instance, destroy=True,
908 fail=not spindles_supported)
909 disk_opts = _BuildRecreateDisksOpts(enumerate(alldisks), False, True,
911 _AssertRecreateDisks(disk_opts, instance, destroy=False,
912 fail=spindles_supported)
913 # Recreate the disks one by one (with the original size)
914 for (idx, disk) in enumerate(alldisks):
915 # Only the first call should destroy all the disk
917 # Again, one of the two commands is expected to fail
918 disk_opts = _BuildRecreateDisksOpts([(idx, disk)], True, False,
920 _AssertRecreateDisks(disk_opts, instance, destroy=destroy, check=False,
921 fail=not spindles_supported)
922 disk_opts = _BuildRecreateDisksOpts([(idx, disk)], False, False,
924 _AssertRecreateDisks(disk_opts, instance, destroy=False, check=False,
925 fail=spindles_supported)
926 # This and InstanceCheck decoration check that the disks are working
927 AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
928 AssertCommand(["gnt-instance", "start", instance.name])
931 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
932 def TestInstanceExport(instance, node):
933 """gnt-backup export -n ..."""
935 # Export does not work for file-based templates, thus we skip the test
936 if instance.disk_template in [constants.DT_FILE, constants.DT_SHARED_FILE]:
938 AssertCommand(["gnt-backup", "export", "-n", node.primary, name])
939 return qa_utils.ResolveInstanceName(name)
942 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
943 def TestInstanceExportWithRemove(instance, node):
944 """gnt-backup export --remove-instance"""
945 AssertCommand(["gnt-backup", "export", "-n", node.primary,
946 "--remove-instance", instance.name])
949 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
950 def TestInstanceExportNoTarget(instance):
951 """gnt-backup export (without target node, should fail)"""
952 AssertCommand(["gnt-backup", "export", instance.name], fail=True)
955 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
956 def TestInstanceImport(newinst, node, expnode, name):
957 """gnt-backup import"""
958 templ = constants.DT_PLAIN
959 if not qa_config.IsTemplateSupported(templ):
961 cmd = (["gnt-backup", "import",
962 "--disk-template=%s" % templ,
964 "--src-node=%s" % expnode.primary,
965 "--src-dir=%s/%s" % (pathutils.EXPORT_DIR, name),
966 "--node=%s" % node.primary] +
967 GetGenericAddParameters(newinst, templ,
968 force_mac=constants.VALUE_GENERATE))
969 cmd.append(newinst.name)
971 newinst.SetDiskTemplate(templ)
974 def TestBackupList(expnode):
975 """gnt-backup list"""
976 AssertCommand(["gnt-backup", "list", "--node=%s" % expnode.primary])
978 qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
979 namefield=None, test_unknown=False)
982 def TestBackupListFields():
983 """gnt-backup list-fields"""
984 qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
987 def TestRemoveInstanceOfflineNode(instance, snode, set_offline, set_online):
988 """gnt-instance remove with an off-line node
990 @param instance: instance
991 @param snode: secondary node, to be set offline
992 @param set_offline: function to call to set the node off-line
993 @param set_online: function to call to set the node on-line
996 info = GetInstanceInfo(instance.name)
999 TestInstanceRemove(instance)
1003 # Clean up the disks on the offline node, if necessary
1004 if instance.disk_template not in constants.DTS_EXT_MIRROR:
1005 # FIXME: abstract the cleanup inside the disks
1006 if info["storage-type"] == constants.ST_LVM_VG:
1007 for minor in info["drbd-minors"][snode.primary]:
1008 # DRBD 8.3 syntax comes first, then DRBD 8.4 syntax. The 8.4 syntax
1009 # relies on the fact that we always create a resources for each minor,
1010 # and that this resources is always named resource{minor}.
1011 # As 'drbdsetup 0 down' does return success (even though that's invalid
1012 # syntax), we always have to perform both commands and ignore the
1014 drbd_shutdown_cmd = \
1015 "(drbdsetup %d down >/dev/null 2>&1;" \
1016 " drbdsetup down resource%d >/dev/null 2>&1) || /bin/true" % \
1018 AssertCommand(drbd_shutdown_cmd, node=snode)
1019 AssertCommand(["lvremove", "-f"] + info["volumes"], node=snode)
1020 elif info["storage-type"] == constants.ST_FILE:
1021 filestorage = qa_config.get("file-storage-dir",
1022 pathutils.DEFAULT_FILE_STORAGE_DIR)
1023 disk = os.path.join(filestorage, instance.name)
1024 AssertCommand(["rm", "-rf", disk], node=snode)
1027 def TestInstanceCreationRestrictedByDiskTemplates():
1028 """Test adding instances for disabled disk templates."""
1029 if qa_config.TestEnabled("cluster-exclusive-storage"):
1030 # These tests are valid only for non-exclusive storage
1033 enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
1034 nodes = qa_config.AcquireManyNodes(2)
1036 # Setup the cluster with the enabled_disk_templates
1038 ["gnt-cluster", "modify",
1039 "--enabled-disk-templates=%s" % ",".join(enabled_disk_templates),
1040 "--ipolicy-disk-templates=%s" % ",".join(enabled_disk_templates)],
1043 # Test instance creation for enabled disk templates
1044 for disk_template in enabled_disk_templates:
1045 instance = CreateInstanceByDiskTemplate(nodes, disk_template, fail=False)
1046 TestInstanceRemove(instance)
1049 # Test that instance creation fails for disabled disk templates
1050 disabled_disk_templates = list(constants.DISK_TEMPLATES
1051 - set(enabled_disk_templates))
1052 for disk_template in disabled_disk_templates:
1053 instance = CreateInstanceByDiskTemplate(nodes, disk_template, fail=True)
1055 # Test instance creation for after disabling enabled disk templates
1056 if (len(enabled_disk_templates) > 1):
1057 # Partition the disk templates, enable them separately and check if the
1058 # disabled ones cannot be used by instances.
1059 middle = len(enabled_disk_templates) / 2
1060 templates1 = enabled_disk_templates[:middle]
1061 templates2 = enabled_disk_templates[middle:]
1063 for (enabled, disabled) in [(templates1, templates2),
1064 (templates2, templates1)]:
1065 AssertCommand(["gnt-cluster", "modify",
1066 "--enabled-disk-templates=%s" % ",".join(enabled),
1067 "--ipolicy-disk-templates=%s" % ",".join(enabled)],
1069 for disk_template in disabled:
1070 CreateInstanceByDiskTemplate(nodes, disk_template, fail=True)
1071 elif (len(enabled_disk_templates) == 1):
1072 # If only one disk template is enabled in the QA config, we have to enable
1073 # some other templates in order to test if the disabling the only enabled
1074 # disk template prohibits creating instances of that template.
1075 other_disk_templates = list(
1076 set([constants.DT_DISKLESS, constants.DT_BLOCK]) -
1077 set(enabled_disk_templates))
1078 AssertCommand(["gnt-cluster", "modify",
1079 "--enabled-disk-templates=%s" %
1080 ",".join(other_disk_templates),
1081 "--ipolicy-disk-templates=%s" %
1082 ",".join(other_disk_templates)],
1084 CreateInstanceByDiskTemplate(nodes, enabled_disk_templates[0], fail=True)
1086 raise qa_error.Error("Please enable at least one disk template"
1087 " in your QA setup.")
1089 # Restore initially enabled disk templates
1090 AssertCommand(["gnt-cluster", "modify",
1091 "--enabled-disk-templates=%s" %
1092 ",".join(enabled_disk_templates),
1093 "--ipolicy-disk-templates=%s" %
1094 ",".join(enabled_disk_templates)],