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.
30 from ganeti import utils
31 from ganeti import constants
32 from ganeti import query
33 from ganeti import pathutils
39 from qa_utils import AssertIn, AssertCommand, AssertEqual
40 from qa_utils import InstanceCheck, INST_DOWN, INST_UP, FIRST_ARG, RETURN_VALUE
43 def _GetDiskStatePath(disk):
44 return "/sys/block/%s/device/state" % disk
47 def _GetGenericAddParameters(inst, disk_template, force_mac=None):
49 params.append("%s=%s,%s=%s" % (constants.BE_MINMEM,
50 qa_config.get(constants.BE_MINMEM),
52 qa_config.get(constants.BE_MAXMEM)))
54 if disk_template != constants.DT_DISKLESS:
55 for idx, disk in enumerate(qa_config.GetDiskOptions()):
56 size = disk.get("size")
57 name = disk.get("name")
58 params.extend(["--disk", "%s:size=%s,name=%s" % (idx, size, name)])
60 # Set static MAC address if configured
64 nic0_mac = inst.GetNicMacAddr(0, None)
67 params.extend(["--net", "0:mac=%s" % nic0_mac])
72 def _CreateInstanceByDiskTemplateRaw(nodes_spec, disk_template, fail=False):
73 """Creates an instance with the given disk template on the given nodes(s).
74 Note that this function does not check if enough nodes are given for
75 the respective disk template.
77 @type nodes_spec: string
78 @param nodes_spec: string specification of one node (by node name) or several
79 nodes according to the requirements of the disk template
80 @type disk_template: string
81 @param disk_template: the disk template to be used by the instance
82 @return: the created instance
85 instance = qa_config.AcquireInstance()
87 cmd = (["gnt-instance", "add",
88 "--os-type=%s" % qa_config.get("os"),
89 "--disk-template=%s" % disk_template,
90 "--node=%s" % nodes_spec] +
91 _GetGenericAddParameters(instance, disk_template))
92 cmd.append(instance.name)
94 AssertCommand(cmd, fail=fail)
97 _CheckSsconfInstanceList(instance.name)
98 instance.SetDiskTemplate(disk_template)
105 # Handle the case where creation is expected to fail
111 def _CreateInstanceByDiskTemplateOneNode(nodes, disk_template, fail=False):
112 """Creates an instance using the given disk template for disk templates
113 for which one given node is sufficient. These templates are for example:
114 plain, diskless, file, sharedfile, blockdev, rados.
116 @type nodes: list of nodes
117 @param nodes: a list of nodes, whose first element is used to create the
119 @type disk_template: string
120 @param disk_template: the disk template to be used by the instance
121 @return: the created instance
124 assert len(nodes) > 0
125 return _CreateInstanceByDiskTemplateRaw(nodes[0].primary, disk_template,
129 def _CreateInstanceDrbd8(nodes, fail=False):
130 """Creates an instance using disk template 'drbd' on the given nodes.
132 @type nodes: list of nodes
133 @param nodes: nodes to be used by the instance
134 @return: the created instance
137 assert len(nodes) > 1
138 return _CreateInstanceByDiskTemplateRaw(
139 ":".join(map(operator.attrgetter("primary"), nodes)),
140 constants.DT_DRBD8, fail=fail)
143 def CreateInstanceByDiskTemplate(nodes, disk_template, fail=False):
144 """Given a disk template, this function creates an instance using
145 the template. It uses the required number of nodes depending on
146 the disk template. This function is intended to be used by tests
147 that don't care about the specifics of the instance other than
148 that it uses the given disk template.
150 Note: If you use this function, make sure to call
151 'TestInstanceRemove' at the end of your tests to avoid orphaned
152 instances hanging around and interfering with the following tests.
154 @type nodes: list of nodes
155 @param nodes: the list of the nodes on which the instance will be placed;
156 it needs to have sufficiently many elements for the given
158 @type disk_template: string
159 @param disk_template: the disk template to be used by the instance
160 @return: the created instance
163 if disk_template == constants.DT_DRBD8:
164 return _CreateInstanceDrbd8(nodes, fail=fail)
165 elif disk_template in [constants.DT_DISKLESS, constants.DT_PLAIN,
167 return _CreateInstanceByDiskTemplateOneNode(nodes, disk_template, fail=fail)
169 # FIXME: This assumes that for all other disk templates, we only need one
170 # node and no disk template specific parameters. This else-branch is
171 # currently only used in cases where we expect failure. Extend it when
172 # QA needs for these templates change.
173 return _CreateInstanceByDiskTemplateOneNode(nodes, disk_template, fail=fail)
176 def _GetInstanceInfo(instance):
177 """Return information about the actual state of an instance.
179 @type instance: string
180 @param instance: the instance name
181 @return: a dictionary with the following keys:
182 - "nodes": instance nodes, a list of strings
183 - "volumes": instance volume IDs, a list of strings
184 - "drbd-minors": DRBD minors used by the instance, a dictionary where
185 keys are nodes, and values are lists of integers (or an empty
186 dictionary for non-DRBD instances)
187 - "disk-template": instance disk template
188 - "storage-type": storage type associated with the instance disk template
191 node_elem = r"([^,()]+)(?:\s+\([^)]+\))?"
192 # re_nodelist matches a list of nodes returned by gnt-instance info, e.g.:
194 # node2.fqdn,node3.fqdn
195 # node4.fqdn (group mygroup, group UUID 01234567-abcd-0123-4567-0123456789ab)
196 # FIXME This works with no more than 2 secondaries
197 re_nodelist = re.compile(node_elem + "(?:," + node_elem + ")?$")
199 info = qa_utils.GetObjectInfo(["gnt-instance", "info", instance])[0]
201 for nodeinfo in info["Nodes"]:
202 if "primary" in nodeinfo:
203 nodes.append(nodeinfo["primary"])
204 elif "secondaries" in nodeinfo:
205 nodestr = nodeinfo["secondaries"]
207 m = re_nodelist.match(nodestr)
209 nodes.extend(filter(None, m.groups()))
211 nodes.append(nodestr)
213 disk_template = info["Disk template"]
214 if not disk_template:
215 raise qa_error.Error("Can't get instance disk template")
216 storage_type = constants.DISK_TEMPLATES_STORAGE_TYPE[disk_template]
218 re_drbdnode = re.compile(r"^([^\s,]+),\s+minor=([0-9]+)$")
221 for (count, diskinfo) in enumerate(info["Disks"]):
222 (dtype, _) = diskinfo["disk/%s" % count].split(",", 1)
223 if dtype == constants.LD_DRBD8:
224 for child in diskinfo["child devices"]:
225 vols.append(child["logical_id"])
226 for key in ["nodeA", "nodeB"]:
227 m = re_drbdnode.match(diskinfo[key])
229 raise qa_error.Error("Cannot parse DRBD info: %s" % diskinfo[key])
231 minor = int(m.group(2))
232 minorlist = drbd_min.setdefault(node, [])
233 minorlist.append(minor)
234 elif dtype == constants.LD_LV:
235 vols.append(diskinfo["logical_id"])
238 assert len(nodes) < 2 or vols
242 "drbd-minors": drbd_min,
243 "disk-template": disk_template,
244 "storage-type": storage_type,
248 def _DestroyInstanceDisks(instance):
249 """Remove all the backend disks of an instance.
251 This is used to simulate HW errors (dead nodes, broken disks...); the
252 configuration of the instance is not affected.
253 @type instance: dictionary
254 @param instance: the instance
257 info = _GetInstanceInfo(instance.name)
258 # FIXME: destruction/removal should be part of the disk class
259 if info["storage-type"] == constants.ST_LVM_VG:
260 vols = info["volumes"]
261 for node in info["nodes"]:
262 AssertCommand(["lvremove", "-f"] + vols, node=node)
263 elif info["storage-type"] == constants.ST_FILE:
264 # FIXME: file storage dir not configurable in qa
265 # Note that this works for both file and sharedfile, and this is intended.
266 filestorage = pathutils.DEFAULT_FILE_STORAGE_DIR
267 idir = os.path.join(filestorage, instance.name)
268 for node in info["nodes"]:
269 AssertCommand(["rm", "-rf", idir], node=node)
270 elif info["storage-type"] == constants.ST_DISKLESS:
274 def _GetInstanceField(instance, field):
275 """Get the value of a field of an instance.
277 @type instance: string
278 @param instance: Instance name
280 @param field: Name of the field
284 master = qa_config.GetMasterNode()
285 infocmd = utils.ShellQuoteArgs(["gnt-instance", "list", "--no-headers",
286 "--units", "m", "-o", field, instance])
287 return qa_utils.GetCommandOutput(master.primary, infocmd).strip()
290 def _GetBoolInstanceField(instance, field):
291 """Get the Boolean value of a field of an instance.
293 @type instance: string
294 @param instance: Instance name
296 @param field: Name of the field
300 info_out = _GetInstanceField(instance, field)
303 elif info_out == "N":
306 raise qa_error.Error("Field %s of instance %s has a non-Boolean value:"
307 " %s" % (field, instance, info_out))
310 def _GetNumInstanceField(instance, field):
311 """Get a numeric value of a field of an instance.
313 @type instance: string
314 @param instance: Instance name
316 @param field: Name of the field
320 info_out = _GetInstanceField(instance, field)
325 ret = float(info_out)
327 raise qa_error.Error("Field %s of instance %s has a non-numeric value:"
328 " %s" % (field, instance, info_out))
332 def GetInstanceSpec(instance, spec):
333 """Return the current spec for the given parameter.
335 @type instance: string
336 @param instance: Instance name
338 @param spec: one of the supported parameters: "mem-size", "cpu-count",
339 "disk-count", "disk-size", "nic-count"
341 @return: (minspec, maxspec); minspec and maxspec can be different only for
346 "mem-size": ["be/minmem", "be/maxmem"],
347 "cpu-count": ["vcpus"],
348 "disk-count": ["disk.count"],
349 "disk-size": ["disk.size/ "],
350 "nic-count": ["nic.count"],
352 # For disks, first we need the number of disks
353 if spec == "disk-size":
354 (numdisk, _) = GetInstanceSpec(instance, "disk-count")
355 fields = ["disk.size/%s" % k for k in range(0, numdisk)]
357 assert spec in specmap, "%s not in %s" % (spec, specmap)
358 fields = specmap[spec]
359 values = [_GetNumInstanceField(instance, f) for f in fields]
360 return (min(values), max(values))
363 def IsFailoverSupported(instance):
364 return instance.disk_template in constants.DTS_MIRRORED
367 def IsMigrationSupported(instance):
368 return instance.disk_template in constants.DTS_MIRRORED
371 def IsDiskReplacingSupported(instance):
372 return instance.disk_template == constants.DT_DRBD8
375 def TestInstanceAddWithPlainDisk(nodes, fail=False):
376 """gnt-instance add -t plain"""
377 if constants.DT_PLAIN in qa_config.GetEnabledDiskTemplates():
378 instance = _CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_PLAIN,
381 qa_utils.RunInstanceCheck(instance, True)
385 @InstanceCheck(None, INST_UP, RETURN_VALUE)
386 def TestInstanceAddWithDrbdDisk(nodes):
387 """gnt-instance add -t drbd"""
388 if constants.DT_DRBD8 in qa_config.GetEnabledDiskTemplates():
389 return _CreateInstanceDrbd8(nodes)
392 @InstanceCheck(None, INST_UP, RETURN_VALUE)
393 def TestInstanceAddFile(nodes):
394 """gnt-instance add -t file"""
395 assert len(nodes) == 1
396 if constants.DT_FILE in qa_config.GetEnabledDiskTemplates():
397 return _CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_FILE)
400 @InstanceCheck(None, INST_UP, RETURN_VALUE)
401 def TestInstanceAddDiskless(nodes):
402 """gnt-instance add -t diskless"""
403 assert len(nodes) == 1
404 if constants.DT_FILE in qa_config.GetEnabledDiskTemplates():
405 return _CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_DISKLESS)
408 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
409 def TestInstanceRemove(instance):
410 """gnt-instance remove"""
411 AssertCommand(["gnt-instance", "remove", "-f", instance.name])
414 @InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
415 def TestInstanceStartup(instance):
416 """gnt-instance startup"""
417 AssertCommand(["gnt-instance", "startup", instance.name])
420 @InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
421 def TestInstanceShutdown(instance):
422 """gnt-instance shutdown"""
423 AssertCommand(["gnt-instance", "shutdown", instance.name])
426 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
427 def TestInstanceReboot(instance):
428 """gnt-instance reboot"""
429 options = qa_config.get("options", {})
430 reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
432 for rtype in reboot_types:
433 AssertCommand(["gnt-instance", "reboot", "--type=%s" % rtype, name])
435 AssertCommand(["gnt-instance", "shutdown", name])
436 qa_utils.RunInstanceCheck(instance, False)
437 AssertCommand(["gnt-instance", "reboot", name])
439 master = qa_config.GetMasterNode()
440 cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", name]
441 result_output = qa_utils.GetCommandOutput(master.primary,
442 utils.ShellQuoteArgs(cmd))
443 AssertEqual(result_output.strip(), constants.INSTST_RUNNING)
446 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
447 def TestInstanceReinstall(instance):
448 """gnt-instance reinstall"""
449 if instance.disk_template == constants.DT_DISKLESS:
450 print qa_utils.FormatInfo("Test not supported for diskless instances")
453 AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
455 # Test with non-existant OS definition
456 AssertCommand(["gnt-instance", "reinstall", "-f",
457 "--os-type=NonExistantOsForQa",
462 def _ReadSsconfInstanceList():
463 """Reads ssconf_instance_list from the master node.
466 master = qa_config.GetMasterNode()
468 ssconf_path = utils.PathJoin(pathutils.DATA_DIR,
469 "ssconf_%s" % constants.SS_INSTANCE_LIST)
471 cmd = ["cat", qa_utils.MakeNodePath(master, ssconf_path)]
473 return qa_utils.GetCommandOutput(master.primary,
474 utils.ShellQuoteArgs(cmd)).splitlines()
477 def _CheckSsconfInstanceList(instance):
478 """Checks if a certain instance is in the ssconf instance list.
480 @type instance: string
481 @param instance: Instance name
484 AssertIn(qa_utils.ResolveInstanceName(instance),
485 _ReadSsconfInstanceList())
488 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
489 def TestInstanceRenameAndBack(rename_source, rename_target):
490 """gnt-instance rename
492 This must leave the instance with the original name, not the target
496 _CheckSsconfInstanceList(rename_source)
498 # first do a rename to a different actual name, expecting it to fail
499 qa_utils.AddToEtcHosts(["meeeeh-not-exists", rename_target])
501 AssertCommand(["gnt-instance", "rename", rename_source, rename_target],
503 _CheckSsconfInstanceList(rename_source)
505 qa_utils.RemoveFromEtcHosts(["meeeeh-not-exists", rename_target])
507 info = _GetInstanceInfo(rename_source)
509 # Check instance volume tags correctly updated. Note that this check is lvm
510 # specific, so we skip it for non-lvm-based instances.
511 # FIXME: This will need updating when instances will be able to have
512 # different disks living on storage pools with etherogeneous storage types.
513 # FIXME: This check should be put inside the disk/storage class themselves,
514 # rather than explicitly called here.
515 if info["storage-type"] == constants.ST_LVM_VG:
516 # In the lvm world we can check for tags on the logical volume
517 tags_cmd = ("lvs -o tags --noheadings %s | grep " %
518 (" ".join(info["volumes"]), ))
520 # Other storage types don't have tags, so we use an always failing command,
521 # to make sure it never gets executed
524 # and now rename instance to rename_target...
525 AssertCommand(["gnt-instance", "rename", rename_source, rename_target])
526 _CheckSsconfInstanceList(rename_target)
527 qa_utils.RunInstanceCheck(rename_source, False)
528 qa_utils.RunInstanceCheck(rename_target, False)
530 # NOTE: tags might not be the exactly as the instance name, due to
531 # charset restrictions; hence the test might be flaky
532 if (rename_source != rename_target and
533 info["storage-type"] == constants.ST_LVM_VG):
534 for node in info["nodes"]:
535 AssertCommand(tags_cmd + rename_source, node=node, fail=True)
536 AssertCommand(tags_cmd + rename_target, node=node, fail=False)
539 AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
540 _CheckSsconfInstanceList(rename_source)
541 qa_utils.RunInstanceCheck(rename_target, False)
543 if (rename_source != rename_target and
544 info["storage-type"] == constants.ST_LVM_VG):
545 for node in info["nodes"]:
546 AssertCommand(tags_cmd + rename_source, node=node, fail=False)
547 AssertCommand(tags_cmd + rename_target, node=node, fail=True)
550 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
551 def TestInstanceFailover(instance):
552 """gnt-instance failover"""
553 if not IsFailoverSupported(instance):
554 print qa_utils.FormatInfo("Instance doesn't support failover, skipping"
558 cmd = ["gnt-instance", "failover", "--force", instance.name]
562 qa_utils.RunInstanceCheck(instance, True)
568 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
569 def TestInstanceMigrate(instance, toggle_always_failover=True):
570 """gnt-instance migrate"""
571 if not IsMigrationSupported(instance):
572 print qa_utils.FormatInfo("Instance doesn't support migration, skipping"
576 cmd = ["gnt-instance", "migrate", "--force", instance.name]
577 af_par = constants.BE_ALWAYS_FAILOVER
578 af_field = "be/" + constants.BE_ALWAYS_FAILOVER
579 af_init_val = _GetBoolInstanceField(instance.name, af_field)
583 # TODO: Verify the choice between failover and migration
584 qa_utils.RunInstanceCheck(instance, True)
586 # ... and back (possibly with always_failover toggled)
587 if toggle_always_failover:
588 AssertCommand(["gnt-instance", "modify", "-B",
589 ("%s=%s" % (af_par, not af_init_val)),
592 # TODO: Verify the choice between failover and migration
593 qa_utils.RunInstanceCheck(instance, True)
594 if toggle_always_failover:
595 AssertCommand(["gnt-instance", "modify", "-B",
596 ("%s=%s" % (af_par, af_init_val)), instance.name])
598 # TODO: Split into multiple tests
599 AssertCommand(["gnt-instance", "shutdown", instance.name])
600 qa_utils.RunInstanceCheck(instance, False)
601 AssertCommand(cmd, fail=True)
602 AssertCommand(["gnt-instance", "migrate", "--force", "--allow-failover",
604 AssertCommand(["gnt-instance", "start", instance.name])
606 # @InstanceCheck enforces the check that the instance is running
607 qa_utils.RunInstanceCheck(instance, True)
609 AssertCommand(["gnt-instance", "modify", "-B",
611 (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)),
615 qa_utils.RunInstanceCheck(instance, True)
616 # TODO: Verify that a failover has been done instead of a migration
618 # TODO: Verify whether the default value is restored here (not hardcoded)
619 AssertCommand(["gnt-instance", "modify", "-B",
621 (constants.BE_ALWAYS_FAILOVER, constants.VALUE_FALSE)),
625 qa_utils.RunInstanceCheck(instance, True)
628 def TestInstanceInfo(instance):
629 """gnt-instance info"""
630 AssertCommand(["gnt-instance", "info", instance.name])
633 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
634 def TestInstanceModify(instance):
635 """gnt-instance modify"""
636 default_hv = qa_config.GetDefaultHypervisor()
638 # Assume /sbin/init exists on all systems
639 test_kernel = "/sbin/init"
640 test_initrd = test_kernel
642 orig_maxmem = qa_config.get(constants.BE_MAXMEM)
643 orig_minmem = qa_config.get(constants.BE_MINMEM)
644 #orig_bridge = qa_config.get("bridge", "xen-br0")
647 ["-B", "%s=128" % constants.BE_MINMEM],
648 ["-B", "%s=128" % constants.BE_MAXMEM],
649 ["-B", "%s=%s,%s=%s" % (constants.BE_MINMEM, orig_minmem,
650 constants.BE_MAXMEM, orig_maxmem)],
651 ["-B", "%s=2" % constants.BE_VCPUS],
652 ["-B", "%s=1" % constants.BE_VCPUS],
653 ["-B", "%s=%s" % (constants.BE_VCPUS, constants.VALUE_DEFAULT)],
654 ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)],
655 ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_DEFAULT)],
657 ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, test_kernel)],
658 ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, constants.VALUE_DEFAULT)],
661 #["--bridge", "xen-br1"],
662 #["--bridge", orig_bridge],
665 if default_hv == constants.HT_XEN_PVM:
667 ["-H", "%s=%s" % (constants.HV_INITRD_PATH, test_initrd)],
668 ["-H", "no_%s" % (constants.HV_INITRD_PATH, )],
669 ["-H", "%s=%s" % (constants.HV_INITRD_PATH, constants.VALUE_DEFAULT)],
671 elif default_hv == constants.HT_XEN_HVM:
673 ["-H", "%s=acn" % constants.HV_BOOT_ORDER],
674 ["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
678 AssertCommand(["gnt-instance", "modify"] + alist + [instance.name])
681 AssertCommand(["gnt-instance", "modify", instance.name], fail=True)
683 # Marking offline while instance is running must fail...
684 AssertCommand(["gnt-instance", "modify", "--offline", instance.name],
687 # ...while making it online is ok, and should work
688 AssertCommand(["gnt-instance", "modify", "--online", instance.name])
691 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
692 def TestInstanceModifyPrimaryAndBack(instance, currentnode, othernode):
693 """gnt-instance modify --new-primary
695 This will leave the instance on its original primary node, not other node.
698 if instance.disk_template != constants.DT_FILE:
699 print qa_utils.FormatInfo("Test only supported for the file disk template")
702 cluster_name = qa_config.get("name")
705 current = currentnode.primary
706 other = othernode.primary
708 # FIXME: the qa doesn't have a customizable file storage dir parameter. As
709 # such for now we use the default.
710 filestorage = pathutils.DEFAULT_FILE_STORAGE_DIR
711 disk = os.path.join(filestorage, name)
713 AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name],
715 AssertCommand(["gnt-instance", "shutdown", name])
716 AssertCommand(["scp", "-oGlobalKnownHostsFile=%s" %
717 pathutils.SSH_KNOWN_HOSTS_FILE,
718 "-oCheckHostIp=no", "-oStrictHostKeyChecking=yes",
719 "-oHashKnownHosts=no", "-oHostKeyAlias=%s" % cluster_name,
720 "-r", disk, "%s:%s" % (other, filestorage)], node=current)
721 AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name])
722 AssertCommand(["gnt-instance", "startup", name])
725 AssertCommand(["gnt-instance", "shutdown", name])
726 AssertCommand(["rm", "-rf", disk], node=other)
727 AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % current, name])
728 AssertCommand(["gnt-instance", "startup", name])
731 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
732 def TestInstanceStoppedModify(instance):
733 """gnt-instance modify (stopped instance)"""
736 # Instance was not marked offline; try marking it online once more
737 AssertCommand(["gnt-instance", "modify", "--online", name])
739 # Mark instance as offline
740 AssertCommand(["gnt-instance", "modify", "--offline", name])
742 # When the instance is offline shutdown should only work with --force,
743 # while start should never work
744 AssertCommand(["gnt-instance", "shutdown", name], fail=True)
745 AssertCommand(["gnt-instance", "shutdown", "--force", name])
746 AssertCommand(["gnt-instance", "start", name], fail=True)
747 AssertCommand(["gnt-instance", "start", "--force", name], fail=True)
749 # Also do offline to offline
750 AssertCommand(["gnt-instance", "modify", "--offline", name])
753 AssertCommand(["gnt-instance", "modify", "--online", name])
756 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
757 def TestInstanceConvertDiskToPlain(instance, inodes):
758 """gnt-instance modify -t"""
761 template = instance.disk_template
762 if template != constants.DT_DRBD8:
763 print qa_utils.FormatInfo("Unsupported template %s, skipping conversion"
767 assert len(inodes) == 2
768 AssertCommand(["gnt-instance", "modify", "-t", constants.DT_PLAIN, name])
769 AssertCommand(["gnt-instance", "modify", "-t", constants.DT_DRBD8,
770 "-n", inodes[1].primary, name])
773 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
774 def TestInstanceGrowDisk(instance):
775 """gnt-instance grow-disk"""
776 if qa_config.GetExclusiveStorage():
777 print qa_utils.FormatInfo("Test not supported with exclusive_storage")
780 if instance.disk_template == constants.DT_DISKLESS:
781 print qa_utils.FormatInfo("Test not supported for diskless instances")
785 disks = qa_config.GetDiskOptions()
786 all_size = [d.get("size") for d in disks]
787 all_grow = [d.get("growth") for d in disks]
790 # missing disk sizes but instance grow disk has been enabled,
791 # let's set fixed/nomimal growth
792 all_grow = ["128M" for _ in all_size]
794 for idx, (size, grow) in enumerate(zip(all_size, all_grow)):
795 # succeed in grow by amount
796 AssertCommand(["gnt-instance", "grow-disk", name, str(idx), grow])
797 # fail in grow to the old size
798 AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
800 # succeed to grow to old size + 2 * growth
801 int_size = utils.ParseUnit(size)
802 int_grow = utils.ParseUnit(grow)
803 AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
804 str(int_size + 2 * int_grow)])
807 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
808 def TestInstanceDeviceNames(instance):
809 if instance.disk_template == constants.DT_DISKLESS:
810 print qa_utils.FormatInfo("Test not supported for diskless instances")
814 for dev_type in ["disk", "net"]:
815 if dev_type == "disk":
816 options = ",size=512M"
819 # succeed in adding a device named 'test_device'
820 AssertCommand(["gnt-instance", "modify",
821 "--%s=-1:add,name=test_device%s" % (dev_type, options),
823 # succeed in removing the 'test_device'
824 AssertCommand(["gnt-instance", "modify",
825 "--%s=test_device:remove" % dev_type,
827 # fail to add two devices with the same name
828 AssertCommand(["gnt-instance", "modify",
829 "--%s=-1:add,name=test_device%s" % (dev_type, options),
830 "--%s=-1:add,name=test_device%s" % (dev_type, options),
832 # fail to add a device with invalid name
833 AssertCommand(["gnt-instance", "modify",
834 "--%s=-1:add,name=2%s" % (dev_type, options),
837 disks = qa_config.GetDiskOptions()
838 disk_names = [d.get("name") for d in disks]
839 for idx, disk_name in enumerate(disk_names):
840 # Refer to disk by idx
841 AssertCommand(["gnt-instance", "modify",
842 "--disk=%s:modify,name=renamed" % idx,
844 # Refer to by name and rename to original name
845 AssertCommand(["gnt-instance", "modify",
846 "--disk=renamed:modify,name=%s" % disk_name,
849 # fail in renaming to disks to the same name
850 AssertCommand(["gnt-instance", "modify",
851 "--disk=0:modify,name=same_name",
852 "--disk=1:modify,name=same_name",
856 def TestInstanceList():
857 """gnt-instance list"""
858 qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
861 def TestInstanceListFields():
862 """gnt-instance list-fields"""
863 qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
866 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
867 def TestInstanceConsole(instance):
868 """gnt-instance console"""
869 AssertCommand(["gnt-instance", "console", "--show-cmd", instance.name])
872 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
873 def TestReplaceDisks(instance, curr_nodes, other_nodes):
874 """gnt-instance replace-disks"""
876 cmd = ["gnt-instance", "replace-disks"]
878 cmd.append(instance.name)
881 if not IsDiskReplacingSupported(instance):
882 print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
886 # Currently all supported templates have one primary and one secondary node
887 assert len(curr_nodes) == 2
888 snode = curr_nodes[1]
889 assert len(other_nodes) == 1
890 othernode = other_nodes[0]
892 options = qa_config.get("options", {})
893 use_ialloc = options.get("use-iallocators", True)
897 # A placeholder; the actual command choice depends on use_ialloc
899 # Restore the original secondary
900 ["--new-secondary=%s" % snode.primary],
904 data = ["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT]
906 data = ["--new-secondary=%s" % othernode.primary]
907 AssertCommand(buildcmd(data))
909 AssertCommand(buildcmd(["-a"]))
910 AssertCommand(["gnt-instance", "stop", instance.name])
911 AssertCommand(buildcmd(["-a"]), fail=True)
912 AssertCommand(["gnt-instance", "activate-disks", instance.name])
913 AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
915 AssertCommand(buildcmd(["-a"]))
916 AssertCommand(["gnt-instance", "start", instance.name])
919 def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
921 """Execute gnt-instance recreate-disks and check the result
923 @param cmdargs: Arguments (instance name excluded)
924 @param instance: Instance to operate on
925 @param fail: True if the command is expected to fail
926 @param check: If True and fail is False, check that the disks work
927 @prama destroy: If True, destroy the old disks first
931 _DestroyInstanceDisks(instance)
932 AssertCommand((["gnt-instance", "recreate-disks"] + cmdargs +
933 [instance.name]), fail)
934 if not fail and check:
935 # Quick check that the disks are there
936 AssertCommand(["gnt-instance", "activate-disks", instance.name])
937 AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
939 AssertCommand(["gnt-instance", "deactivate-disks", instance.name])
942 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
943 def TestRecreateDisks(instance, inodes, othernodes):
944 """gnt-instance recreate-disks
946 @param instance: Instance to work on
947 @param inodes: List of the current nodes of the instance
948 @param othernodes: list/tuple of nodes where to temporarily recreate disks
951 options = qa_config.get("options", {})
952 use_ialloc = options.get("use-iallocators", True)
953 other_seq = ":".join([n.primary for n in othernodes])
954 orig_seq = ":".join([n.primary for n in inodes])
955 # These fail because the instance is running
956 _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
958 _AssertRecreateDisks(["-I", "hail"], instance, fail=True, destroy=False)
960 _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
961 AssertCommand(["gnt-instance", "stop", instance.name])
962 # Disks exist: this should fail
963 _AssertRecreateDisks([], instance, fail=True, destroy=False)
964 # Recreate disks in place
965 _AssertRecreateDisks([], instance)
968 _AssertRecreateDisks(["-I", "hail"], instance)
969 # Move disks somewhere else
970 _AssertRecreateDisks(["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT],
973 _AssertRecreateDisks(["-n", other_seq], instance)
975 _AssertRecreateDisks(["-n", orig_seq], instance, check=False)
976 # This and InstanceCheck decoration check that the disks are working
977 AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
978 AssertCommand(["gnt-instance", "start", instance.name])
981 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
982 def TestInstanceExport(instance, node):
983 """gnt-backup export -n ..."""
985 AssertCommand(["gnt-backup", "export", "-n", node.primary, name])
986 return qa_utils.ResolveInstanceName(name)
989 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
990 def TestInstanceExportWithRemove(instance, node):
991 """gnt-backup export --remove-instance"""
992 AssertCommand(["gnt-backup", "export", "-n", node.primary,
993 "--remove-instance", instance.name])
996 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
997 def TestInstanceExportNoTarget(instance):
998 """gnt-backup export (without target node, should fail)"""
999 AssertCommand(["gnt-backup", "export", instance.name], fail=True)
1002 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
1003 def TestInstanceImport(newinst, node, expnode, name):
1004 """gnt-backup import"""
1005 templ = constants.DT_PLAIN
1006 cmd = (["gnt-backup", "import",
1007 "--disk-template=%s" % templ,
1009 "--src-node=%s" % expnode.primary,
1010 "--src-dir=%s/%s" % (pathutils.EXPORT_DIR, name),
1011 "--node=%s" % node.primary] +
1012 _GetGenericAddParameters(newinst, templ,
1013 force_mac=constants.VALUE_GENERATE))
1014 cmd.append(newinst.name)
1016 newinst.SetDiskTemplate(templ)
1019 def TestBackupList(expnode):
1020 """gnt-backup list"""
1021 AssertCommand(["gnt-backup", "list", "--node=%s" % expnode.primary])
1023 qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
1024 namefield=None, test_unknown=False)
1027 def TestBackupListFields():
1028 """gnt-backup list-fields"""
1029 qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
1032 def TestRemoveInstanceOfflineNode(instance, snode, set_offline, set_online):
1033 """gnt-instance remove with an off-line node
1035 @param instance: instance
1036 @param snode: secondary node, to be set offline
1037 @param set_offline: function to call to set the node off-line
1038 @param set_online: function to call to set the node on-line
1041 info = _GetInstanceInfo(instance.name)
1044 TestInstanceRemove(instance)
1048 # Clean up the disks on the offline node, if necessary
1049 if instance.disk_template not in constants.DTS_EXT_MIRROR:
1050 # FIXME: abstract the cleanup inside the disks
1051 if info["storage-type"] == constants.ST_LVM_VG:
1052 for minor in info["drbd-minors"][snode.primary]:
1053 AssertCommand(["drbdsetup", str(minor), "down"], node=snode)
1054 AssertCommand(["lvremove", "-f"] + info["volumes"], node=snode)
1055 elif info["storage-type"] == constants.ST_FILE:
1056 filestorage = pathutils.DEFAULT_FILE_STORAGE_DIR
1057 disk = os.path.join(filestorage, instance.name)
1058 AssertCommand(["rm", "-rf", disk], node=snode)
1061 def TestInstanceCreationRestrictedByDiskTemplates():
1062 """Test adding instances for disbled disk templates."""
1063 enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
1064 nodes = qa_config.AcquireManyNodes(2)
1066 # Setup the cluster with the enabled_disk_templates
1068 ["gnt-cluster", "modify",
1069 "--enabled-disk-template=%s" %
1070 ",".join(enabled_disk_templates)],
1073 # Test instance creation for enabled disk templates
1074 for disk_template in enabled_disk_templates:
1075 instance = CreateInstanceByDiskTemplate(nodes, disk_template, False)
1076 TestInstanceRemove(instance)
1078 # Test that instance creation fails for disabled disk templates
1079 disabled_disk_templates = list(constants.DISK_TEMPLATES
1080 - set(enabled_disk_templates))
1081 for disk_template in disabled_disk_templates:
1082 instance = CreateInstanceByDiskTemplate(nodes, disk_template, True)
1084 # Test instance creation for after disabling enabled disk templates
1085 if (len(enabled_disk_templates) > 1):
1086 # Partition the disk templates, enable them separately and check if the
1087 # disabled ones cannot be used by instances.
1088 middle = len(enabled_disk_templates) / 2
1089 templates1 = enabled_disk_templates[:middle]
1090 templates2 = enabled_disk_templates[middle:]
1092 for (enabled, disabled) in [(templates1, templates2),
1093 (templates2, templates1)]:
1094 AssertCommand(["gnt-cluster", "modify",
1095 "--enabled-disk-template=%s" %
1098 for disk_template in disabled:
1099 CreateInstanceByDiskTemplate(nodes, disk_template, True)
1100 elif (len(enabled_disk_templates) == 1):
1101 # If only one disk template is enabled in the QA config, we have to enable
1102 # some of the disabled disk templates in order to test if the disabling the
1103 # only enabled disk template prohibits creating instances of that template.
1104 AssertCommand(["gnt-cluster", "modify",
1105 "--enabled-disk-template=%s" %
1106 ",".join(disabled_disk_templates)],
1108 CreateInstanceByDiskTemplate(nodes, enabled_disk_templates[0], True)
1110 raise qa_error.Error("Please enable at least one disk template"
1111 " in your QA setup.")
1113 # Restore initially enabled disk templates
1114 AssertCommand(["gnt-cluster", "modify",
1115 "--enabled-disk-template=%s" %
1116 ",".join(enabled_disk_templates)],