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, size in enumerate(qa_config.get("disk")):
56 params.extend(["--disk", "%s:size=%s" % (idx, size)])
58 # Set static MAC address if configured
62 nic0_mac = inst.GetNicMacAddr(0, None)
65 params.extend(["--net", "0:mac=%s" % nic0_mac])
70 def _DiskTest(node, disk_template, fail=False):
71 instance = qa_config.AcquireInstance()
73 cmd = (["gnt-instance", "add",
74 "--os-type=%s" % qa_config.get("os"),
75 "--disk-template=%s" % disk_template,
77 _GetGenericAddParameters(instance, disk_template))
78 cmd.append(instance.name)
80 AssertCommand(cmd, fail=fail)
83 _CheckSsconfInstanceList(instance.name)
84 instance.SetDiskTemplate(disk_template)
91 # Handle the case where creation is expected to fail
97 def _GetInstanceInfo(instance):
98 """Return information about the actual state of an instance.
100 @type instance: string
101 @param instance: the instance name
102 @return: a dictionary with the following keys:
103 - "nodes": instance nodes, a list of strings
104 - "volumes": instance volume IDs, a list of strings
105 - "drbd-minors": DRBD minors used by the instance, a dictionary where
106 keys are nodes, and values are lists of integers (or an empty
107 dictionary for non-DRBD instances)
108 - "disk-template": instance disk template
109 - "storage-type": storage type associated with the instance disk template
112 node_elem = r"([^,()]+)(?:\s+\([^)]+\))?"
113 # re_nodelist matches a list of nodes returned by gnt-instance info, e.g.:
115 # node2.fqdn,node3.fqdn
116 # node4.fqdn (group mygroup, group UUID 01234567-abcd-0123-4567-0123456789ab)
117 # FIXME This works with no more than 2 secondaries
118 re_nodelist = re.compile(node_elem + "(?:," + node_elem + ")?$")
120 info = qa_utils.GetObjectInfo(["gnt-instance", "info", instance])[0]
122 for nodeinfo in info["Nodes"]:
123 if "primary" in nodeinfo:
124 nodes.append(nodeinfo["primary"])
125 elif "secondaries" in nodeinfo:
126 nodestr = nodeinfo["secondaries"]
128 m = re_nodelist.match(nodestr)
130 nodes.extend(filter(None, m.groups()))
132 nodes.append(nodestr)
134 disk_template = info["Disk template"]
135 if not disk_template:
136 raise qa_error.Error("Can't get instance disk template")
137 storage_type = constants.DISK_TEMPLATES_STORAGE_TYPE[disk_template]
139 re_drbdnode = re.compile(r"^([^\s,]+),\s+minor=([0-9]+)$")
142 for (count, diskinfo) in enumerate(info["Disks"]):
143 (dtype, _) = diskinfo["disk/%s" % count].split(",", 1)
144 if dtype == constants.LD_DRBD8:
145 for child in diskinfo["child devices"]:
146 vols.append(child["logical_id"])
147 for key in ["nodeA", "nodeB"]:
148 m = re_drbdnode.match(diskinfo[key])
150 raise qa_error.Error("Cannot parse DRBD info: %s" % diskinfo[key])
152 minor = int(m.group(2))
153 minorlist = drbd_min.setdefault(node, [])
154 minorlist.append(minor)
155 elif dtype == constants.LD_LV:
156 vols.append(diskinfo["logical_id"])
159 assert len(nodes) < 2 or vols
163 "drbd-minors": drbd_min,
164 "disk-template": disk_template,
165 "storage-type": storage_type,
169 def _DestroyInstanceVolumes(instance):
170 """Remove all the LVM volumes of an instance.
172 This is used to simulate HW errors (dead nodes, broken disks...); the
173 configuration of the instance is not affected.
174 @type instance: dictionary
175 @param instance: the instance
178 info = _GetInstanceInfo(instance.name)
179 vols = info["volumes"]
180 for node in info["nodes"]:
181 AssertCommand(["lvremove", "-f"] + vols, node=node)
184 def _GetInstanceField(instance, field):
185 """Get the value of a field of an instance.
187 @type instance: string
188 @param instance: Instance name
190 @param field: Name of the field
194 master = qa_config.GetMasterNode()
195 infocmd = utils.ShellQuoteArgs(["gnt-instance", "list", "--no-headers",
196 "--units", "m", "-o", field, instance])
197 return qa_utils.GetCommandOutput(master.primary, infocmd).strip()
200 def _GetBoolInstanceField(instance, field):
201 """Get the Boolean value of a field of an instance.
203 @type instance: string
204 @param instance: Instance name
206 @param field: Name of the field
210 info_out = _GetInstanceField(instance, field)
213 elif info_out == "N":
216 raise qa_error.Error("Field %s of instance %s has a non-Boolean value:"
217 " %s" % (field, instance, info_out))
220 def _GetNumInstanceField(instance, field):
221 """Get a numeric value of a field of an instance.
223 @type instance: string
224 @param instance: Instance name
226 @param field: Name of the field
230 info_out = _GetInstanceField(instance, field)
235 ret = float(info_out)
237 raise qa_error.Error("Field %s of instance %s has a non-numeric value:"
238 " %s" % (field, instance, info_out))
242 def GetInstanceSpec(instance, spec):
243 """Return the current spec for the given parameter.
245 @type instance: string
246 @param instance: Instance name
248 @param spec: one of the supported parameters: "mem-size", "cpu-count",
249 "disk-count", "disk-size", "nic-count"
251 @return: (minspec, maxspec); minspec and maxspec can be different only for
256 "mem-size": ["be/minmem", "be/maxmem"],
257 "cpu-count": ["vcpus"],
258 "disk-count": ["disk.count"],
259 "disk-size": ["disk.size/ "],
260 "nic-count": ["nic.count"],
262 # For disks, first we need the number of disks
263 if spec == "disk-size":
264 (numdisk, _) = GetInstanceSpec(instance, "disk-count")
265 fields = ["disk.size/%s" % k for k in range(0, numdisk)]
267 assert spec in specmap, "%s not in %s" % (spec, specmap)
268 fields = specmap[spec]
269 values = [_GetNumInstanceField(instance, f) for f in fields]
270 return (min(values), max(values))
273 def IsFailoverSupported(instance):
274 return instance.disk_template in constants.DTS_MIRRORED
277 def IsMigrationSupported(instance):
278 return instance.disk_template in constants.DTS_MIRRORED
281 def IsDiskReplacingSupported(instance):
282 return instance.disk_template == constants.DT_DRBD8
285 def TestInstanceAddWithPlainDisk(nodes, fail=False):
286 """gnt-instance add -t plain"""
287 assert len(nodes) == 1
288 instance = _DiskTest(nodes[0].primary, constants.DT_PLAIN, fail=fail)
290 qa_utils.RunInstanceCheck(instance, True)
294 @InstanceCheck(None, INST_UP, RETURN_VALUE)
295 def TestInstanceAddWithDrbdDisk(nodes):
296 """gnt-instance add -t drbd"""
297 assert len(nodes) == 2
298 return _DiskTest(":".join(map(operator.attrgetter("primary"), nodes)),
302 @InstanceCheck(None, INST_UP, RETURN_VALUE)
303 def TestInstanceAddFile(nodes):
304 """gnt-instance add -t file"""
305 assert len(nodes) == 1
306 return _DiskTest(nodes[0].primary, constants.DT_FILE)
309 @InstanceCheck(None, INST_UP, RETURN_VALUE)
310 def TestInstanceAddDiskless(nodes):
311 """gnt-instance add -t diskless"""
312 assert len(nodes) == 1
313 return _DiskTest(nodes[0].primary, constants.DT_DISKLESS)
316 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
317 def TestInstanceRemove(instance):
318 """gnt-instance remove"""
319 AssertCommand(["gnt-instance", "remove", "-f", instance.name])
322 @InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
323 def TestInstanceStartup(instance):
324 """gnt-instance startup"""
325 AssertCommand(["gnt-instance", "startup", instance.name])
328 @InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
329 def TestInstanceShutdown(instance):
330 """gnt-instance shutdown"""
331 AssertCommand(["gnt-instance", "shutdown", instance.name])
334 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
335 def TestInstanceReboot(instance):
336 """gnt-instance reboot"""
337 options = qa_config.get("options", {})
338 reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
340 for rtype in reboot_types:
341 AssertCommand(["gnt-instance", "reboot", "--type=%s" % rtype, name])
343 AssertCommand(["gnt-instance", "shutdown", name])
344 qa_utils.RunInstanceCheck(instance, False)
345 AssertCommand(["gnt-instance", "reboot", name])
347 master = qa_config.GetMasterNode()
348 cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", name]
349 result_output = qa_utils.GetCommandOutput(master.primary,
350 utils.ShellQuoteArgs(cmd))
351 AssertEqual(result_output.strip(), constants.INSTST_RUNNING)
354 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
355 def TestInstanceReinstall(instance):
356 """gnt-instance reinstall"""
357 if instance.disk_template == constants.DT_DISKLESS:
358 print qa_utils.FormatInfo("Test not supported for diskless instances")
361 AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
363 # Test with non-existant OS definition
364 AssertCommand(["gnt-instance", "reinstall", "-f",
365 "--os-type=NonExistantOsForQa",
370 def _ReadSsconfInstanceList():
371 """Reads ssconf_instance_list from the master node.
374 master = qa_config.GetMasterNode()
376 ssconf_path = utils.PathJoin(pathutils.DATA_DIR,
377 "ssconf_%s" % constants.SS_INSTANCE_LIST)
379 cmd = ["cat", qa_utils.MakeNodePath(master, ssconf_path)]
381 return qa_utils.GetCommandOutput(master.primary,
382 utils.ShellQuoteArgs(cmd)).splitlines()
385 def _CheckSsconfInstanceList(instance):
386 """Checks if a certain instance is in the ssconf instance list.
388 @type instance: string
389 @param instance: Instance name
392 AssertIn(qa_utils.ResolveInstanceName(instance),
393 _ReadSsconfInstanceList())
396 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
397 def TestInstanceRenameAndBack(rename_source, rename_target):
398 """gnt-instance rename
400 This must leave the instance with the original name, not the target
404 _CheckSsconfInstanceList(rename_source)
406 # first do a rename to a different actual name, expecting it to fail
407 qa_utils.AddToEtcHosts(["meeeeh-not-exists", rename_target])
409 AssertCommand(["gnt-instance", "rename", rename_source, rename_target],
411 _CheckSsconfInstanceList(rename_source)
413 qa_utils.RemoveFromEtcHosts(["meeeeh-not-exists", rename_target])
415 info = _GetInstanceInfo(rename_source)
417 # Check instance volume tags correctly updated. Note that this check is lvm
418 # specific, so we skip it for non-lvm-based instances.
419 # FIXME: This will need updating when instances will be able to have
420 # different disks living on storage pools with etherogeneous storage types.
421 # FIXME: This check should be put inside the disk/storage class themselves,
422 # rather than explicitly called here.
423 if info["storage-type"] == constants.ST_LVM_VG:
424 # In the lvm world we can check for tags on the logical volume
425 tags_cmd = ("lvs -o tags --noheadings %s | grep " %
426 (" ".join(info["volumes"]), ))
428 # Other storage types don't have tags, so we use an always failing command,
429 # to make sure it never gets executed
432 # and now rename instance to rename_target...
433 AssertCommand(["gnt-instance", "rename", rename_source, rename_target])
434 _CheckSsconfInstanceList(rename_target)
435 qa_utils.RunInstanceCheck(rename_source, False)
436 qa_utils.RunInstanceCheck(rename_target, False)
438 # NOTE: tags might not be the exactly as the instance name, due to
439 # charset restrictions; hence the test might be flaky
440 if (rename_source != rename_target and
441 info["storage-type"] == constants.ST_LVM_VG):
442 for node in info["nodes"]:
443 AssertCommand(tags_cmd + rename_source, node=node, fail=True)
444 AssertCommand(tags_cmd + rename_target, node=node, fail=False)
447 AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
448 _CheckSsconfInstanceList(rename_source)
449 qa_utils.RunInstanceCheck(rename_target, False)
451 if (rename_source != rename_target and
452 info["storage-type"] == constants.ST_LVM_VG):
453 for node in info["nodes"]:
454 AssertCommand(tags_cmd + rename_source, node=node, fail=False)
455 AssertCommand(tags_cmd + rename_target, node=node, fail=True)
458 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
459 def TestInstanceFailover(instance):
460 """gnt-instance failover"""
461 if not IsFailoverSupported(instance):
462 print qa_utils.FormatInfo("Instance doesn't support failover, skipping"
466 cmd = ["gnt-instance", "failover", "--force", instance.name]
470 qa_utils.RunInstanceCheck(instance, True)
476 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
477 def TestInstanceMigrate(instance, toggle_always_failover=True):
478 """gnt-instance migrate"""
479 if not IsMigrationSupported(instance):
480 print qa_utils.FormatInfo("Instance doesn't support migration, skipping"
484 cmd = ["gnt-instance", "migrate", "--force", instance.name]
485 af_par = constants.BE_ALWAYS_FAILOVER
486 af_field = "be/" + constants.BE_ALWAYS_FAILOVER
487 af_init_val = _GetBoolInstanceField(instance.name, af_field)
491 # TODO: Verify the choice between failover and migration
492 qa_utils.RunInstanceCheck(instance, True)
494 # ... and back (possibly with always_failover toggled)
495 if toggle_always_failover:
496 AssertCommand(["gnt-instance", "modify", "-B",
497 ("%s=%s" % (af_par, not af_init_val)),
500 # TODO: Verify the choice between failover and migration
501 qa_utils.RunInstanceCheck(instance, True)
502 if toggle_always_failover:
503 AssertCommand(["gnt-instance", "modify", "-B",
504 ("%s=%s" % (af_par, af_init_val)), instance.name])
506 # TODO: Split into multiple tests
507 AssertCommand(["gnt-instance", "shutdown", instance.name])
508 qa_utils.RunInstanceCheck(instance, False)
509 AssertCommand(cmd, fail=True)
510 AssertCommand(["gnt-instance", "migrate", "--force", "--allow-failover",
512 AssertCommand(["gnt-instance", "start", instance.name])
514 # @InstanceCheck enforces the check that the instance is running
515 qa_utils.RunInstanceCheck(instance, True)
517 AssertCommand(["gnt-instance", "modify", "-B",
519 (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)),
523 qa_utils.RunInstanceCheck(instance, True)
524 # TODO: Verify that a failover has been done instead of a migration
526 # TODO: Verify whether the default value is restored here (not hardcoded)
527 AssertCommand(["gnt-instance", "modify", "-B",
529 (constants.BE_ALWAYS_FAILOVER, constants.VALUE_FALSE)),
533 qa_utils.RunInstanceCheck(instance, True)
536 def TestInstanceInfo(instance):
537 """gnt-instance info"""
538 AssertCommand(["gnt-instance", "info", instance.name])
541 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
542 def TestInstanceModify(instance):
543 """gnt-instance modify"""
544 default_hv = qa_config.GetDefaultHypervisor()
546 # Assume /sbin/init exists on all systems
547 test_kernel = "/sbin/init"
548 test_initrd = test_kernel
550 orig_maxmem = qa_config.get(constants.BE_MAXMEM)
551 orig_minmem = qa_config.get(constants.BE_MINMEM)
552 #orig_bridge = qa_config.get("bridge", "xen-br0")
555 ["-B", "%s=128" % constants.BE_MINMEM],
556 ["-B", "%s=128" % constants.BE_MAXMEM],
557 ["-B", "%s=%s,%s=%s" % (constants.BE_MINMEM, orig_minmem,
558 constants.BE_MAXMEM, orig_maxmem)],
559 ["-B", "%s=2" % constants.BE_VCPUS],
560 ["-B", "%s=1" % constants.BE_VCPUS],
561 ["-B", "%s=%s" % (constants.BE_VCPUS, constants.VALUE_DEFAULT)],
562 ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)],
563 ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_DEFAULT)],
565 ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, test_kernel)],
566 ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, constants.VALUE_DEFAULT)],
569 #["--bridge", "xen-br1"],
570 #["--bridge", orig_bridge],
573 if default_hv == constants.HT_XEN_PVM:
575 ["-H", "%s=%s" % (constants.HV_INITRD_PATH, test_initrd)],
576 ["-H", "no_%s" % (constants.HV_INITRD_PATH, )],
577 ["-H", "%s=%s" % (constants.HV_INITRD_PATH, constants.VALUE_DEFAULT)],
579 elif default_hv == constants.HT_XEN_HVM:
581 ["-H", "%s=acn" % constants.HV_BOOT_ORDER],
582 ["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
586 AssertCommand(["gnt-instance", "modify"] + alist + [instance.name])
589 AssertCommand(["gnt-instance", "modify", instance.name], fail=True)
591 # Marking offline while instance is running must fail...
592 AssertCommand(["gnt-instance", "modify", "--offline", instance.name],
595 # ...while making it online is ok, and should work
596 AssertCommand(["gnt-instance", "modify", "--online", instance.name])
599 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
600 def TestInstanceModifyPrimaryAndBack(instance, currentnode, othernode):
601 """gnt-instance modify --new-primary
603 This will leave the instance on its original primary node, not other node.
606 if instance.disk_template != constants.DT_FILE:
607 print qa_utils.FormatInfo("Test only supported for the file disk template")
610 cluster_name = qa_config.get("name")
613 current = currentnode.primary
614 other = othernode.primary
616 # FIXME: the qa doesn't have a customizable file storage dir parameter. As
617 # such for now we use the default.
618 filestorage = pathutils.DEFAULT_FILE_STORAGE_DIR
619 disk = os.path.join(filestorage, name)
621 AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name],
623 AssertCommand(["gnt-instance", "shutdown", name])
624 AssertCommand(["scp", "-oGlobalKnownHostsFile=%s" %
625 pathutils.SSH_KNOWN_HOSTS_FILE,
626 "-oCheckHostIp=no", "-oStrictHostKeyChecking=yes",
627 "-oHashKnownHosts=no", "-oHostKeyAlias=%s" % cluster_name,
628 "-r", disk, "%s:%s" % (other, filestorage)])
629 AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name])
630 AssertCommand(["gnt-instance", "startup", name])
633 AssertCommand(["gnt-instance", "shutdown", name])
634 AssertCommand(["rm", "-rf", disk], node=other)
635 AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % current, name])
636 AssertCommand(["gnt-instance", "startup", name])
639 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
640 def TestInstanceStoppedModify(instance):
641 """gnt-instance modify (stopped instance)"""
644 # Instance was not marked offline; try marking it online once more
645 AssertCommand(["gnt-instance", "modify", "--online", name])
647 # Mark instance as offline
648 AssertCommand(["gnt-instance", "modify", "--offline", name])
650 # When the instance is offline shutdown should only work with --force,
651 # while start should never work
652 AssertCommand(["gnt-instance", "shutdown", name], fail=True)
653 AssertCommand(["gnt-instance", "shutdown", "--force", name])
654 AssertCommand(["gnt-instance", "start", name], fail=True)
655 AssertCommand(["gnt-instance", "start", "--force", name], fail=True)
657 # Also do offline to offline
658 AssertCommand(["gnt-instance", "modify", "--offline", name])
661 AssertCommand(["gnt-instance", "modify", "--online", name])
664 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
665 def TestInstanceConvertDiskToPlain(instance, inodes):
666 """gnt-instance modify -t"""
669 template = instance.disk_template
670 if template != constants.DT_DRBD8:
671 print qa_utils.FormatInfo("Unsupported template %s, skipping conversion"
675 assert len(inodes) == 2
676 AssertCommand(["gnt-instance", "modify", "-t", constants.DT_PLAIN, name])
677 AssertCommand(["gnt-instance", "modify", "-t", constants.DT_DRBD8,
678 "-n", inodes[1].primary, name])
681 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
682 def TestInstanceGrowDisk(instance):
683 """gnt-instance grow-disk"""
684 if qa_config.GetExclusiveStorage():
685 print qa_utils.FormatInfo("Test not supported with exclusive_storage")
688 if instance.disk_template == constants.DT_DISKLESS:
689 print qa_utils.FormatInfo("Test not supported for diskless instances")
693 all_size = qa_config.get("disk")
694 all_grow = qa_config.get("disk-growth")
697 # missing disk sizes but instance grow disk has been enabled,
698 # let's set fixed/nomimal growth
699 all_grow = ["128M" for _ in all_size]
701 for idx, (size, grow) in enumerate(zip(all_size, all_grow)):
702 # succeed in grow by amount
703 AssertCommand(["gnt-instance", "grow-disk", name, str(idx), grow])
704 # fail in grow to the old size
705 AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
707 # succeed to grow to old size + 2 * growth
708 int_size = utils.ParseUnit(size)
709 int_grow = utils.ParseUnit(grow)
710 AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
711 str(int_size + 2 * int_grow)])
714 def TestInstanceList():
715 """gnt-instance list"""
716 qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
719 def TestInstanceListFields():
720 """gnt-instance list-fields"""
721 qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
724 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
725 def TestInstanceConsole(instance):
726 """gnt-instance console"""
727 AssertCommand(["gnt-instance", "console", "--show-cmd", instance.name])
730 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
731 def TestReplaceDisks(instance, curr_nodes, other_nodes):
732 """gnt-instance replace-disks"""
734 cmd = ["gnt-instance", "replace-disks"]
736 cmd.append(instance.name)
739 if not IsDiskReplacingSupported(instance):
740 print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
744 # Currently all supported templates have one primary and one secondary node
745 assert len(curr_nodes) == 2
746 snode = curr_nodes[1]
747 assert len(other_nodes) == 1
748 othernode = other_nodes[0]
750 options = qa_config.get("options", {})
751 use_ialloc = options.get("use-iallocators", True)
755 # A placeholder; the actual command choice depends on use_ialloc
757 # Restore the original secondary
758 ["--new-secondary=%s" % snode.primary],
762 data = ["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT]
764 data = ["--new-secondary=%s" % othernode.primary]
765 AssertCommand(buildcmd(data))
767 AssertCommand(buildcmd(["-a"]))
768 AssertCommand(["gnt-instance", "stop", instance.name])
769 AssertCommand(buildcmd(["-a"]), fail=True)
770 AssertCommand(["gnt-instance", "activate-disks", instance.name])
771 AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
773 AssertCommand(buildcmd(["-a"]))
774 AssertCommand(["gnt-instance", "start", instance.name])
777 def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
779 """Execute gnt-instance recreate-disks and check the result
781 @param cmdargs: Arguments (instance name excluded)
782 @param instance: Instance to operate on
783 @param fail: True if the command is expected to fail
784 @param check: If True and fail is False, check that the disks work
785 @prama destroy: If True, destroy the old disks first
789 _DestroyInstanceVolumes(instance)
790 AssertCommand((["gnt-instance", "recreate-disks"] + cmdargs +
791 [instance.name]), fail)
792 if not fail and check:
793 # Quick check that the disks are there
794 AssertCommand(["gnt-instance", "activate-disks", instance.name])
795 AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
797 AssertCommand(["gnt-instance", "deactivate-disks", instance.name])
800 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
801 def TestRecreateDisks(instance, inodes, othernodes):
802 """gnt-instance recreate-disks
804 @param instance: Instance to work on
805 @param inodes: List of the current nodes of the instance
806 @param othernodes: list/tuple of nodes where to temporarily recreate disks
809 options = qa_config.get("options", {})
810 use_ialloc = options.get("use-iallocators", True)
811 other_seq = ":".join([n.primary for n in othernodes])
812 orig_seq = ":".join([n.primary for n in inodes])
813 # These fail because the instance is running
814 _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
816 _AssertRecreateDisks(["-I", "hail"], instance, fail=True, destroy=False)
818 _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
819 AssertCommand(["gnt-instance", "stop", instance.name])
820 # Disks exist: this should fail
821 _AssertRecreateDisks([], instance, fail=True, destroy=False)
822 # Recreate disks in place
823 _AssertRecreateDisks([], instance)
826 _AssertRecreateDisks(["-I", "hail"], instance)
827 # Move disks somewhere else
828 _AssertRecreateDisks(["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT],
831 _AssertRecreateDisks(["-n", other_seq], instance)
833 _AssertRecreateDisks(["-n", orig_seq], instance, check=False)
834 # This and InstanceCheck decoration check that the disks are working
835 AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
836 AssertCommand(["gnt-instance", "start", instance.name])
839 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
840 def TestInstanceExport(instance, node):
841 """gnt-backup export -n ..."""
843 AssertCommand(["gnt-backup", "export", "-n", node.primary, name])
844 return qa_utils.ResolveInstanceName(name)
847 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
848 def TestInstanceExportWithRemove(instance, node):
849 """gnt-backup export --remove-instance"""
850 AssertCommand(["gnt-backup", "export", "-n", node.primary,
851 "--remove-instance", instance.name])
854 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
855 def TestInstanceExportNoTarget(instance):
856 """gnt-backup export (without target node, should fail)"""
857 AssertCommand(["gnt-backup", "export", instance.name], fail=True)
860 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
861 def TestInstanceImport(newinst, node, expnode, name):
862 """gnt-backup import"""
863 templ = constants.DT_PLAIN
864 cmd = (["gnt-backup", "import",
865 "--disk-template=%s" % templ,
867 "--src-node=%s" % expnode.primary,
868 "--src-dir=%s/%s" % (pathutils.EXPORT_DIR, name),
869 "--node=%s" % node.primary] +
870 _GetGenericAddParameters(newinst, templ,
871 force_mac=constants.VALUE_GENERATE))
872 cmd.append(newinst.name)
874 newinst.SetDiskTemplate(templ)
877 def TestBackupList(expnode):
878 """gnt-backup list"""
879 AssertCommand(["gnt-backup", "list", "--node=%s" % expnode.primary])
881 qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
882 namefield=None, test_unknown=False)
885 def TestBackupListFields():
886 """gnt-backup list-fields"""
887 qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
890 def TestRemoveInstanceOfflineNode(instance, snode, set_offline, set_online):
891 """gtn-instance remove with an off-line node
893 @param instance: instance
894 @param snode: secondary node, to be set offline
895 @param set_offline: function to call to set the node off-line
896 @param set_online: function to call to set the node on-line
899 info = _GetInstanceInfo(instance.name)
902 TestInstanceRemove(instance)
905 # Clean up the disks on the offline node
906 for minor in info["drbd-minors"][snode.primary]:
907 AssertCommand(["drbdsetup", str(minor), "down"], node=snode)
908 AssertCommand(["lvremove", "-f"] + info["volumes"], node=snode)