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.DISK_TEMPLATES_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.LD_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.LD_LV:
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 # FIXME: file storage dir not configurable in qa
140 # Note that this works for both file and sharedfile, and this is intended.
141 filestorage = pathutils.DEFAULT_FILE_STORAGE_DIR
142 idir = os.path.join(filestorage, 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 TestInstanceAddDiskless(nodes):
281 """gnt-instance add -t diskless"""
282 assert len(nodes) == 1
283 if constants.DT_DISKLESS in qa_config.GetEnabledDiskTemplates():
284 return CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_DISKLESS)
287 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
288 def TestInstanceRemove(instance):
289 """gnt-instance remove"""
290 AssertCommand(["gnt-instance", "remove", "-f", instance.name])
293 @InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
294 def TestInstanceStartup(instance):
295 """gnt-instance startup"""
296 AssertCommand(["gnt-instance", "startup", instance.name])
299 @InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
300 def TestInstanceShutdown(instance):
301 """gnt-instance shutdown"""
302 AssertCommand(["gnt-instance", "shutdown", instance.name])
305 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
306 def TestInstanceReboot(instance):
307 """gnt-instance reboot"""
308 options = qa_config.get("options", {})
309 reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
311 for rtype in reboot_types:
312 AssertCommand(["gnt-instance", "reboot", "--type=%s" % rtype, name])
314 AssertCommand(["gnt-instance", "shutdown", name])
315 qa_utils.RunInstanceCheck(instance, False)
316 AssertCommand(["gnt-instance", "reboot", name])
318 master = qa_config.GetMasterNode()
319 cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", name]
320 result_output = qa_utils.GetCommandOutput(master.primary,
321 utils.ShellQuoteArgs(cmd))
322 AssertEqual(result_output.strip(), constants.INSTST_RUNNING)
325 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
326 def TestInstanceReinstall(instance):
327 """gnt-instance reinstall"""
328 if instance.disk_template == constants.DT_DISKLESS:
329 print qa_utils.FormatInfo("Test not supported for diskless instances")
332 AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
334 # Test with non-existant OS definition
335 AssertCommand(["gnt-instance", "reinstall", "-f",
336 "--os-type=NonExistantOsForQa",
341 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
342 def TestInstanceRenameAndBack(rename_source, rename_target):
343 """gnt-instance rename
345 This must leave the instance with the original name, not the target
349 CheckSsconfInstanceList(rename_source)
351 # first do a rename to a different actual name, expecting it to fail
352 qa_utils.AddToEtcHosts(["meeeeh-not-exists", rename_target])
354 AssertCommand(["gnt-instance", "rename", rename_source, rename_target],
356 CheckSsconfInstanceList(rename_source)
358 qa_utils.RemoveFromEtcHosts(["meeeeh-not-exists", rename_target])
360 info = GetInstanceInfo(rename_source)
362 # Check instance volume tags correctly updated. Note that this check is lvm
363 # specific, so we skip it for non-lvm-based instances.
364 # FIXME: This will need updating when instances will be able to have
365 # different disks living on storage pools with etherogeneous storage types.
366 # FIXME: This check should be put inside the disk/storage class themselves,
367 # rather than explicitly called here.
368 if info["storage-type"] == constants.ST_LVM_VG:
369 # In the lvm world we can check for tags on the logical volume
370 tags_cmd = ("lvs -o tags --noheadings %s | grep " %
371 (" ".join(info["volumes"]), ))
373 # Other storage types don't have tags, so we use an always failing command,
374 # to make sure it never gets executed
377 # and now rename instance to rename_target...
378 AssertCommand(["gnt-instance", "rename", rename_source, rename_target])
379 CheckSsconfInstanceList(rename_target)
380 qa_utils.RunInstanceCheck(rename_source, False)
381 qa_utils.RunInstanceCheck(rename_target, False)
383 # NOTE: tags might not be the exactly as the instance name, due to
384 # charset restrictions; hence the test might be flaky
385 if (rename_source != rename_target and
386 info["storage-type"] == constants.ST_LVM_VG):
387 for node in info["nodes"]:
388 AssertCommand(tags_cmd + rename_source, node=node, fail=True)
389 AssertCommand(tags_cmd + rename_target, node=node, fail=False)
392 AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
393 CheckSsconfInstanceList(rename_source)
394 qa_utils.RunInstanceCheck(rename_target, False)
396 if (rename_source != rename_target and
397 info["storage-type"] == constants.ST_LVM_VG):
398 for node in info["nodes"]:
399 AssertCommand(tags_cmd + rename_source, node=node, fail=False)
400 AssertCommand(tags_cmd + rename_target, node=node, fail=True)
403 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
404 def TestInstanceFailover(instance):
405 """gnt-instance failover"""
406 if not IsFailoverSupported(instance):
407 print qa_utils.FormatInfo("Instance doesn't support failover, skipping"
411 cmd = ["gnt-instance", "failover", "--force", instance.name]
415 qa_utils.RunInstanceCheck(instance, True)
421 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
422 def TestInstanceMigrate(instance, toggle_always_failover=True):
423 """gnt-instance migrate"""
424 if not IsMigrationSupported(instance):
425 print qa_utils.FormatInfo("Instance doesn't support migration, skipping"
429 cmd = ["gnt-instance", "migrate", "--force", instance.name]
430 af_par = constants.BE_ALWAYS_FAILOVER
431 af_field = "be/" + constants.BE_ALWAYS_FAILOVER
432 af_init_val = _GetBoolInstanceField(instance.name, af_field)
436 # TODO: Verify the choice between failover and migration
437 qa_utils.RunInstanceCheck(instance, True)
439 # ... and back (possibly with always_failover toggled)
440 if toggle_always_failover:
441 AssertCommand(["gnt-instance", "modify", "-B",
442 ("%s=%s" % (af_par, not af_init_val)),
445 # TODO: Verify the choice between failover and migration
446 qa_utils.RunInstanceCheck(instance, True)
447 if toggle_always_failover:
448 AssertCommand(["gnt-instance", "modify", "-B",
449 ("%s=%s" % (af_par, af_init_val)), instance.name])
451 # TODO: Split into multiple tests
452 AssertCommand(["gnt-instance", "shutdown", instance.name])
453 qa_utils.RunInstanceCheck(instance, False)
454 AssertCommand(cmd, fail=True)
455 AssertCommand(["gnt-instance", "migrate", "--force", "--allow-failover",
457 AssertCommand(["gnt-instance", "start", instance.name])
459 # @InstanceCheck enforces the check that the instance is running
460 qa_utils.RunInstanceCheck(instance, True)
462 AssertCommand(["gnt-instance", "modify", "-B",
464 (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)),
468 qa_utils.RunInstanceCheck(instance, True)
469 # TODO: Verify that a failover has been done instead of a migration
471 # TODO: Verify whether the default value is restored here (not hardcoded)
472 AssertCommand(["gnt-instance", "modify", "-B",
474 (constants.BE_ALWAYS_FAILOVER, constants.VALUE_FALSE)),
478 qa_utils.RunInstanceCheck(instance, True)
481 def TestInstanceInfo(instance):
482 """gnt-instance info"""
483 AssertCommand(["gnt-instance", "info", instance.name])
486 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
487 def TestInstanceModify(instance):
488 """gnt-instance modify"""
489 default_hv = qa_config.GetDefaultHypervisor()
491 # Assume /sbin/init exists on all systems
492 test_kernel = "/sbin/init"
493 test_initrd = test_kernel
495 orig_maxmem = qa_config.get(constants.BE_MAXMEM)
496 orig_minmem = qa_config.get(constants.BE_MINMEM)
497 #orig_bridge = qa_config.get("bridge", "xen-br0")
500 ["-B", "%s=128" % constants.BE_MINMEM],
501 ["-B", "%s=128" % constants.BE_MAXMEM],
502 ["-B", "%s=%s,%s=%s" % (constants.BE_MINMEM, orig_minmem,
503 constants.BE_MAXMEM, orig_maxmem)],
504 ["-B", "%s=2" % constants.BE_VCPUS],
505 ["-B", "%s=1" % constants.BE_VCPUS],
506 ["-B", "%s=%s" % (constants.BE_VCPUS, constants.VALUE_DEFAULT)],
507 ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)],
508 ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_DEFAULT)],
510 ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, test_kernel)],
511 ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, constants.VALUE_DEFAULT)],
514 #["--bridge", "xen-br1"],
515 #["--bridge", orig_bridge],
518 if default_hv == constants.HT_XEN_PVM:
520 ["-H", "%s=%s" % (constants.HV_INITRD_PATH, test_initrd)],
521 ["-H", "no_%s" % (constants.HV_INITRD_PATH, )],
522 ["-H", "%s=%s" % (constants.HV_INITRD_PATH, constants.VALUE_DEFAULT)],
524 elif default_hv == constants.HT_XEN_HVM:
526 ["-H", "%s=acn" % constants.HV_BOOT_ORDER],
527 ["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
531 AssertCommand(["gnt-instance", "modify"] + alist + [instance.name])
534 AssertCommand(["gnt-instance", "modify", instance.name], fail=True)
536 # Marking offline while instance is running must fail...
537 AssertCommand(["gnt-instance", "modify", "--offline", instance.name],
540 # ...while making it online is ok, and should work
541 AssertCommand(["gnt-instance", "modify", "--online", instance.name])
544 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
545 def TestInstanceModifyPrimaryAndBack(instance, currentnode, othernode):
546 """gnt-instance modify --new-primary
548 This will leave the instance on its original primary node, not other node.
551 if instance.disk_template != constants.DT_FILE:
552 print qa_utils.FormatInfo("Test only supported for the file disk template")
555 cluster_name = qa_config.get("name")
558 current = currentnode.primary
559 other = othernode.primary
561 # FIXME: the qa doesn't have a customizable file storage dir parameter. As
562 # such for now we use the default.
563 filestorage = pathutils.DEFAULT_FILE_STORAGE_DIR
564 disk = os.path.join(filestorage, name)
566 AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name],
568 AssertCommand(["gnt-instance", "shutdown", name])
569 AssertCommand(["scp", "-oGlobalKnownHostsFile=%s" %
570 pathutils.SSH_KNOWN_HOSTS_FILE,
571 "-oCheckHostIp=no", "-oStrictHostKeyChecking=yes",
572 "-oHashKnownHosts=no", "-oHostKeyAlias=%s" % cluster_name,
573 "-r", disk, "%s:%s" % (other, filestorage)], node=current)
574 AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name])
575 AssertCommand(["gnt-instance", "startup", name])
578 AssertCommand(["gnt-instance", "shutdown", name])
579 AssertCommand(["rm", "-rf", disk], node=other)
580 AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % current, name])
581 AssertCommand(["gnt-instance", "startup", name])
584 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
585 def TestInstanceStoppedModify(instance):
586 """gnt-instance modify (stopped instance)"""
589 # Instance was not marked offline; try marking it online once more
590 AssertCommand(["gnt-instance", "modify", "--online", name])
592 # Mark instance as offline
593 AssertCommand(["gnt-instance", "modify", "--offline", name])
595 # When the instance is offline shutdown should only work with --force,
596 # while start should never work
597 AssertCommand(["gnt-instance", "shutdown", name], fail=True)
598 AssertCommand(["gnt-instance", "shutdown", "--force", name])
599 AssertCommand(["gnt-instance", "start", name], fail=True)
600 AssertCommand(["gnt-instance", "start", "--force", name], fail=True)
602 # Also do offline to offline
603 AssertCommand(["gnt-instance", "modify", "--offline", name])
606 AssertCommand(["gnt-instance", "modify", "--online", name])
609 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
610 def TestInstanceConvertDiskToPlain(instance, inodes):
611 """gnt-instance modify -t"""
614 template = instance.disk_template
615 if template != constants.DT_DRBD8:
616 print qa_utils.FormatInfo("Unsupported template %s, skipping conversion"
620 assert len(inodes) == 2
621 AssertCommand(["gnt-instance", "modify", "-t", constants.DT_PLAIN, name])
622 AssertCommand(["gnt-instance", "modify", "-t", constants.DT_DRBD8,
623 "-n", inodes[1].primary, name])
626 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
627 def TestInstanceModifyDisks(instance):
628 """gnt-instance modify --disk"""
629 if not IsDiskSupported(instance):
630 print qa_utils.FormatInfo("Instance doesn't support disks, skipping test")
633 disk_conf = qa_config.GetDiskOptions()[-1]
634 size = disk_conf.get("size")
636 build_cmd = lambda arg: ["gnt-instance", "modify", "--disk", arg, name]
637 if qa_config.AreSpindlesSupported():
638 spindles = disk_conf.get("spindles")
639 spindles_supported = True
641 # Any number is good for spindles in this case
643 spindles_supported = False
644 AssertCommand(build_cmd("add:size=%s,spindles=%s" % (size, spindles)),
645 fail=not spindles_supported)
646 AssertCommand(build_cmd("add:size=%s" % size),
647 fail=spindles_supported)
648 # Exactly one of the above commands has succeded, so we need one remove
649 AssertCommand(build_cmd("remove"))
652 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
653 def TestInstanceGrowDisk(instance):
654 """gnt-instance grow-disk"""
655 if instance.disk_template == constants.DT_DISKLESS:
656 print qa_utils.FormatInfo("Test not supported for diskless instances")
660 disks = qa_config.GetDiskOptions()
661 all_size = [d.get("size") for d in disks]
662 all_grow = [d.get("growth") for d in disks]
665 # missing disk sizes but instance grow disk has been enabled,
666 # let's set fixed/nomimal growth
667 all_grow = ["128M" for _ in all_size]
669 for idx, (size, grow) in enumerate(zip(all_size, all_grow)):
670 # succeed in grow by amount
671 AssertCommand(["gnt-instance", "grow-disk", name, str(idx), grow])
672 # fail in grow to the old size
673 AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
675 # succeed to grow to old size + 2 * growth
676 int_size = utils.ParseUnit(size)
677 int_grow = utils.ParseUnit(grow)
678 AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
679 str(int_size + 2 * int_grow)])
682 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
683 def TestInstanceDeviceNames(instance):
684 if instance.disk_template == constants.DT_DISKLESS:
685 print qa_utils.FormatInfo("Test not supported for diskless instances")
689 for dev_type in ["disk", "net"]:
690 if dev_type == "disk":
691 options = ",size=512M"
692 if qa_config.AreSpindlesSupported():
693 options += ",spindles=1"
696 # succeed in adding a device named 'test_device'
697 AssertCommand(["gnt-instance", "modify",
698 "--%s=-1:add,name=test_device%s" % (dev_type, options),
700 # succeed in removing the 'test_device'
701 AssertCommand(["gnt-instance", "modify",
702 "--%s=test_device:remove" % dev_type,
704 # fail to add two devices with the same name
705 AssertCommand(["gnt-instance", "modify",
706 "--%s=-1:add,name=test_device%s" % (dev_type, options),
707 "--%s=-1:add,name=test_device%s" % (dev_type, options),
709 # fail to add a device with invalid name
710 AssertCommand(["gnt-instance", "modify",
711 "--%s=-1:add,name=2%s" % (dev_type, options),
714 disks = qa_config.GetDiskOptions()
715 disk_names = [d.get("name") for d in disks]
716 for idx, disk_name in enumerate(disk_names):
717 # Refer to disk by idx
718 AssertCommand(["gnt-instance", "modify",
719 "--disk=%s:modify,name=renamed" % idx,
721 # Refer to by name and rename to original name
722 AssertCommand(["gnt-instance", "modify",
723 "--disk=renamed:modify,name=%s" % disk_name,
726 # fail in renaming to disks to the same name
727 AssertCommand(["gnt-instance", "modify",
728 "--disk=0:modify,name=same_name",
729 "--disk=1:modify,name=same_name",
733 def TestInstanceList():
734 """gnt-instance list"""
735 qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
738 def TestInstanceListFields():
739 """gnt-instance list-fields"""
740 qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
743 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
744 def TestInstanceConsole(instance):
745 """gnt-instance console"""
746 AssertCommand(["gnt-instance", "console", "--show-cmd", instance.name])
749 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
750 def TestReplaceDisks(instance, curr_nodes, other_nodes):
751 """gnt-instance replace-disks"""
753 cmd = ["gnt-instance", "replace-disks"]
755 cmd.append(instance.name)
758 if not IsDiskReplacingSupported(instance):
759 print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
763 # Currently all supported templates have one primary and one secondary node
764 assert len(curr_nodes) == 2
765 snode = curr_nodes[1]
766 assert len(other_nodes) == 1
767 othernode = other_nodes[0]
769 options = qa_config.get("options", {})
770 use_ialloc = options.get("use-iallocators", True)
774 # A placeholder; the actual command choice depends on use_ialloc
776 # Restore the original secondary
777 ["--new-secondary=%s" % snode.primary],
781 data = ["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT]
783 data = ["--new-secondary=%s" % othernode.primary]
784 AssertCommand(buildcmd(data))
786 AssertCommand(buildcmd(["-a"]))
787 AssertCommand(["gnt-instance", "stop", instance.name])
788 AssertCommand(buildcmd(["-a"]), fail=True)
789 AssertCommand(["gnt-instance", "activate-disks", instance.name])
790 AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
792 AssertCommand(buildcmd(["-a"]))
793 AssertCommand(["gnt-instance", "start", instance.name])
796 def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
798 """Execute gnt-instance recreate-disks and check the result
800 @param cmdargs: Arguments (instance name excluded)
801 @param instance: Instance to operate on
802 @param fail: True if the command is expected to fail
803 @param check: If True and fail is False, check that the disks work
804 @prama destroy: If True, destroy the old disks first
808 _DestroyInstanceDisks(instance)
809 AssertCommand((["gnt-instance", "recreate-disks"] + cmdargs +
810 [instance.name]), fail)
811 if not fail and check:
812 # Quick check that the disks are there
813 AssertCommand(["gnt-instance", "activate-disks", instance.name])
814 AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
816 AssertCommand(["gnt-instance", "deactivate-disks", instance.name])
819 def _BuildRecreateDisksOpts(en_disks, with_spindles, with_growth,
822 if spindles_supported:
824 build_spindles_opt = (lambda disk:
826 (disk["spindles"] + disk["spindles-growth"]))
828 build_spindles_opt = (lambda disk:
829 ",spindles=%s" % disk["spindles"])
831 build_spindles_opt = (lambda _: ",spindles=1")
833 build_spindles_opt = (lambda _: "")
835 build_size_opt = (lambda disk:
836 "size=%s" % (utils.ParseUnit(disk["size"]) +
837 utils.ParseUnit(disk["growth"])))
839 build_size_opt = (lambda disk: "size=%s" % disk["size"])
840 build_disk_opt = (lambda (idx, disk):
841 "--disk=%s:%s%s" % (idx, build_size_opt(disk),
842 build_spindles_opt(disk)))
843 return map(build_disk_opt, en_disks)
846 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
847 def TestRecreateDisks(instance, inodes, othernodes):
848 """gnt-instance recreate-disks
850 @param instance: Instance to work on
851 @param inodes: List of the current nodes of the instance
852 @param othernodes: list/tuple of nodes where to temporarily recreate disks
855 options = qa_config.get("options", {})
856 use_ialloc = options.get("use-iallocators", True)
857 other_seq = ":".join([n.primary for n in othernodes])
858 orig_seq = ":".join([n.primary for n in inodes])
859 # These fail because the instance is running
860 _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
862 _AssertRecreateDisks(["-I", "hail"], instance, fail=True, destroy=False)
864 _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
865 AssertCommand(["gnt-instance", "stop", instance.name])
866 # Disks exist: this should fail
867 _AssertRecreateDisks([], instance, fail=True, destroy=False)
868 # Unsupported spindles parameters: fail
869 if not qa_config.AreSpindlesSupported():
870 _AssertRecreateDisks(["--disk=0:spindles=2"], instance,
871 fail=True, destroy=False)
872 # Recreate disks in place
873 _AssertRecreateDisks([], instance)
876 _AssertRecreateDisks(["-I", "hail"], instance)
877 # Move disks somewhere else
878 _AssertRecreateDisks(["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT],
881 _AssertRecreateDisks(["-n", other_seq], instance)
883 _AssertRecreateDisks(["-n", orig_seq], instance)
884 # Recreate resized disks
885 # One of the two commands fails because either spindles are given when they
886 # should not or vice versa
887 alldisks = qa_config.GetDiskOptions()
888 spindles_supported = qa_config.AreSpindlesSupported()
889 disk_opts = _BuildRecreateDisksOpts(enumerate(alldisks), True, True,
891 _AssertRecreateDisks(disk_opts, instance, destroy=True,
892 fail=not spindles_supported)
893 disk_opts = _BuildRecreateDisksOpts(enumerate(alldisks), False, True,
895 _AssertRecreateDisks(disk_opts, instance, destroy=False,
896 fail=spindles_supported)
897 # Recreate the disks one by one (with the original size)
898 for (idx, disk) in enumerate(alldisks):
899 # Only the first call should destroy all the disk
901 # Again, one of the two commands is expected to fail
902 disk_opts = _BuildRecreateDisksOpts([(idx, disk)], True, False,
904 _AssertRecreateDisks(disk_opts, instance, destroy=destroy, check=False,
905 fail=not spindles_supported)
906 disk_opts = _BuildRecreateDisksOpts([(idx, disk)], False, False,
908 _AssertRecreateDisks(disk_opts, instance, destroy=False, check=False,
909 fail=spindles_supported)
910 # This and InstanceCheck decoration check that the disks are working
911 AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
912 AssertCommand(["gnt-instance", "start", instance.name])
915 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
916 def TestInstanceExport(instance, node):
917 """gnt-backup export -n ..."""
919 AssertCommand(["gnt-backup", "export", "-n", node.primary, name])
920 return qa_utils.ResolveInstanceName(name)
923 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
924 def TestInstanceExportWithRemove(instance, node):
925 """gnt-backup export --remove-instance"""
926 AssertCommand(["gnt-backup", "export", "-n", node.primary,
927 "--remove-instance", instance.name])
930 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
931 def TestInstanceExportNoTarget(instance):
932 """gnt-backup export (without target node, should fail)"""
933 AssertCommand(["gnt-backup", "export", instance.name], fail=True)
936 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
937 def TestInstanceImport(newinst, node, expnode, name):
938 """gnt-backup import"""
939 templ = constants.DT_PLAIN
940 cmd = (["gnt-backup", "import",
941 "--disk-template=%s" % templ,
943 "--src-node=%s" % expnode.primary,
944 "--src-dir=%s/%s" % (pathutils.EXPORT_DIR, name),
945 "--node=%s" % node.primary] +
946 GetGenericAddParameters(newinst, templ,
947 force_mac=constants.VALUE_GENERATE))
948 cmd.append(newinst.name)
950 newinst.SetDiskTemplate(templ)
953 def TestBackupList(expnode):
954 """gnt-backup list"""
955 AssertCommand(["gnt-backup", "list", "--node=%s" % expnode.primary])
957 qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
958 namefield=None, test_unknown=False)
961 def TestBackupListFields():
962 """gnt-backup list-fields"""
963 qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
966 def TestRemoveInstanceOfflineNode(instance, snode, set_offline, set_online):
967 """gnt-instance remove with an off-line node
969 @param instance: instance
970 @param snode: secondary node, to be set offline
971 @param set_offline: function to call to set the node off-line
972 @param set_online: function to call to set the node on-line
975 info = GetInstanceInfo(instance.name)
978 TestInstanceRemove(instance)
982 # Clean up the disks on the offline node, if necessary
983 if instance.disk_template not in constants.DTS_EXT_MIRROR:
984 # FIXME: abstract the cleanup inside the disks
985 if info["storage-type"] == constants.ST_LVM_VG:
986 for minor in info["drbd-minors"][snode.primary]:
987 # DRBD 8.3 syntax comes first, then DRBD 8.4 syntax. The 8.4 syntax
988 # relies on the fact that we always create a resources for each minor,
989 # and that this resources is always named resource{minor}.
990 # As 'drbdsetup 0 down' does return success (even though that's invalid
991 # syntax), we always have to perform both commands and ignore the
993 drbd_shutdown_cmd = \
994 "(drbdsetup %d down >/dev/null 2>&1;" \
995 " drbdsetup down resource%d >/dev/null 2>&1) || /bin/true" % \
997 AssertCommand(drbd_shutdown_cmd, node=snode)
998 AssertCommand(["lvremove", "-f"] + info["volumes"], node=snode)
999 elif info["storage-type"] == constants.ST_FILE:
1000 filestorage = pathutils.DEFAULT_FILE_STORAGE_DIR
1001 disk = os.path.join(filestorage, instance.name)
1002 AssertCommand(["rm", "-rf", disk], node=snode)
1005 def TestInstanceCreationRestrictedByDiskTemplates():
1006 """Test adding instances for disabled disk templates."""
1007 enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
1008 nodes = qa_config.AcquireManyNodes(2)
1010 # Setup the cluster with the enabled_disk_templates
1012 ["gnt-cluster", "modify",
1013 "--enabled-disk-template=%s" %
1014 ",".join(enabled_disk_templates)],
1017 # Test instance creation for enabled disk templates
1018 for disk_template in enabled_disk_templates:
1019 instance = CreateInstanceByDiskTemplate(nodes, disk_template, fail=False)
1020 TestInstanceRemove(instance)
1023 # Test that instance creation fails for disabled disk templates
1024 disabled_disk_templates = list(constants.DISK_TEMPLATES
1025 - set(enabled_disk_templates))
1026 for disk_template in disabled_disk_templates:
1027 instance = CreateInstanceByDiskTemplate(nodes, disk_template, fail=True)
1029 # Test instance creation for after disabling enabled disk templates
1030 if (len(enabled_disk_templates) > 1):
1031 # Partition the disk templates, enable them separately and check if the
1032 # disabled ones cannot be used by instances.
1033 middle = len(enabled_disk_templates) / 2
1034 templates1 = enabled_disk_templates[:middle]
1035 templates2 = enabled_disk_templates[middle:]
1037 for (enabled, disabled) in [(templates1, templates2),
1038 (templates2, templates1)]:
1039 AssertCommand(["gnt-cluster", "modify",
1040 "--enabled-disk-template=%s" %
1043 for disk_template in disabled:
1044 CreateInstanceByDiskTemplate(nodes, disk_template, fail=True)
1045 elif (len(enabled_disk_templates) == 1):
1046 # If only one disk template is enabled in the QA config, we have to enable
1047 # some other templates in order to test if the disabling the only enabled
1048 # disk template prohibits creating instances of that template.
1049 other_disk_templates = list(
1050 set([constants.DT_DISKLESS, constants.DT_BLOCK]) -
1051 set(enabled_disk_templates))
1052 AssertCommand(["gnt-cluster", "modify",
1053 "--enabled-disk-template=%s" %
1054 ",".join(other_disk_templates)],
1056 CreateInstanceByDiskTemplate(nodes, enabled_disk_templates[0], fail=True)
1058 raise qa_error.Error("Please enable at least one disk template"
1059 " in your QA setup.")
1061 # Restore initially enabled disk templates
1062 AssertCommand(["gnt-cluster", "modify",
1063 "--enabled-disk-template=%s" %
1064 ",".join(enabled_disk_templates)],