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 TestInstanceAddWithPlainDisk(nodes, fail=False):
251 """gnt-instance add -t plain"""
252 if constants.DT_PLAIN in qa_config.GetEnabledDiskTemplates():
253 instance = CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_PLAIN,
256 qa_utils.RunInstanceCheck(instance, True)
260 @InstanceCheck(None, INST_UP, RETURN_VALUE)
261 def TestInstanceAddWithDrbdDisk(nodes):
262 """gnt-instance add -t drbd"""
263 if constants.DT_DRBD8 in qa_config.GetEnabledDiskTemplates():
264 return CreateInstanceDrbd8(nodes)
267 @InstanceCheck(None, INST_UP, RETURN_VALUE)
268 def TestInstanceAddFile(nodes):
269 """gnt-instance add -t file"""
270 assert len(nodes) == 1
271 if constants.DT_FILE in qa_config.GetEnabledDiskTemplates():
272 return CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_FILE)
275 @InstanceCheck(None, INST_UP, RETURN_VALUE)
276 def TestInstanceAddDiskless(nodes):
277 """gnt-instance add -t diskless"""
278 assert len(nodes) == 1
279 if constants.DT_FILE in qa_config.GetEnabledDiskTemplates():
280 return CreateInstanceByDiskTemplateOneNode(nodes, constants.DT_DISKLESS)
283 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
284 def TestInstanceRemove(instance):
285 """gnt-instance remove"""
286 AssertCommand(["gnt-instance", "remove", "-f", instance.name])
289 @InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
290 def TestInstanceStartup(instance):
291 """gnt-instance startup"""
292 AssertCommand(["gnt-instance", "startup", instance.name])
295 @InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
296 def TestInstanceShutdown(instance):
297 """gnt-instance shutdown"""
298 AssertCommand(["gnt-instance", "shutdown", instance.name])
301 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
302 def TestInstanceReboot(instance):
303 """gnt-instance reboot"""
304 options = qa_config.get("options", {})
305 reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
307 for rtype in reboot_types:
308 AssertCommand(["gnt-instance", "reboot", "--type=%s" % rtype, name])
310 AssertCommand(["gnt-instance", "shutdown", name])
311 qa_utils.RunInstanceCheck(instance, False)
312 AssertCommand(["gnt-instance", "reboot", name])
314 master = qa_config.GetMasterNode()
315 cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", name]
316 result_output = qa_utils.GetCommandOutput(master.primary,
317 utils.ShellQuoteArgs(cmd))
318 AssertEqual(result_output.strip(), constants.INSTST_RUNNING)
321 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
322 def TestInstanceReinstall(instance):
323 """gnt-instance reinstall"""
324 if instance.disk_template == constants.DT_DISKLESS:
325 print qa_utils.FormatInfo("Test not supported for diskless instances")
328 AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
330 # Test with non-existant OS definition
331 AssertCommand(["gnt-instance", "reinstall", "-f",
332 "--os-type=NonExistantOsForQa",
337 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
338 def TestInstanceRenameAndBack(rename_source, rename_target):
339 """gnt-instance rename
341 This must leave the instance with the original name, not the target
345 CheckSsconfInstanceList(rename_source)
347 # first do a rename to a different actual name, expecting it to fail
348 qa_utils.AddToEtcHosts(["meeeeh-not-exists", rename_target])
350 AssertCommand(["gnt-instance", "rename", rename_source, rename_target],
352 CheckSsconfInstanceList(rename_source)
354 qa_utils.RemoveFromEtcHosts(["meeeeh-not-exists", rename_target])
356 info = _GetInstanceInfo(rename_source)
358 # Check instance volume tags correctly updated. Note that this check is lvm
359 # specific, so we skip it for non-lvm-based instances.
360 # FIXME: This will need updating when instances will be able to have
361 # different disks living on storage pools with etherogeneous storage types.
362 # FIXME: This check should be put inside the disk/storage class themselves,
363 # rather than explicitly called here.
364 if info["storage-type"] == constants.ST_LVM_VG:
365 # In the lvm world we can check for tags on the logical volume
366 tags_cmd = ("lvs -o tags --noheadings %s | grep " %
367 (" ".join(info["volumes"]), ))
369 # Other storage types don't have tags, so we use an always failing command,
370 # to make sure it never gets executed
373 # and now rename instance to rename_target...
374 AssertCommand(["gnt-instance", "rename", rename_source, rename_target])
375 CheckSsconfInstanceList(rename_target)
376 qa_utils.RunInstanceCheck(rename_source, False)
377 qa_utils.RunInstanceCheck(rename_target, False)
379 # NOTE: tags might not be the exactly as the instance name, due to
380 # charset restrictions; hence the test might be flaky
381 if (rename_source != rename_target and
382 info["storage-type"] == constants.ST_LVM_VG):
383 for node in info["nodes"]:
384 AssertCommand(tags_cmd + rename_source, node=node, fail=True)
385 AssertCommand(tags_cmd + rename_target, node=node, fail=False)
388 AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
389 CheckSsconfInstanceList(rename_source)
390 qa_utils.RunInstanceCheck(rename_target, False)
392 if (rename_source != rename_target and
393 info["storage-type"] == constants.ST_LVM_VG):
394 for node in info["nodes"]:
395 AssertCommand(tags_cmd + rename_source, node=node, fail=False)
396 AssertCommand(tags_cmd + rename_target, node=node, fail=True)
399 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
400 def TestInstanceFailover(instance):
401 """gnt-instance failover"""
402 if not IsFailoverSupported(instance):
403 print qa_utils.FormatInfo("Instance doesn't support failover, skipping"
407 cmd = ["gnt-instance", "failover", "--force", instance.name]
411 qa_utils.RunInstanceCheck(instance, True)
417 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
418 def TestInstanceMigrate(instance, toggle_always_failover=True):
419 """gnt-instance migrate"""
420 if not IsMigrationSupported(instance):
421 print qa_utils.FormatInfo("Instance doesn't support migration, skipping"
425 cmd = ["gnt-instance", "migrate", "--force", instance.name]
426 af_par = constants.BE_ALWAYS_FAILOVER
427 af_field = "be/" + constants.BE_ALWAYS_FAILOVER
428 af_init_val = _GetBoolInstanceField(instance.name, af_field)
432 # TODO: Verify the choice between failover and migration
433 qa_utils.RunInstanceCheck(instance, True)
435 # ... and back (possibly with always_failover toggled)
436 if toggle_always_failover:
437 AssertCommand(["gnt-instance", "modify", "-B",
438 ("%s=%s" % (af_par, not af_init_val)),
441 # TODO: Verify the choice between failover and migration
442 qa_utils.RunInstanceCheck(instance, True)
443 if toggle_always_failover:
444 AssertCommand(["gnt-instance", "modify", "-B",
445 ("%s=%s" % (af_par, af_init_val)), instance.name])
447 # TODO: Split into multiple tests
448 AssertCommand(["gnt-instance", "shutdown", instance.name])
449 qa_utils.RunInstanceCheck(instance, False)
450 AssertCommand(cmd, fail=True)
451 AssertCommand(["gnt-instance", "migrate", "--force", "--allow-failover",
453 AssertCommand(["gnt-instance", "start", instance.name])
455 # @InstanceCheck enforces the check that the instance is running
456 qa_utils.RunInstanceCheck(instance, True)
458 AssertCommand(["gnt-instance", "modify", "-B",
460 (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)),
464 qa_utils.RunInstanceCheck(instance, True)
465 # TODO: Verify that a failover has been done instead of a migration
467 # TODO: Verify whether the default value is restored here (not hardcoded)
468 AssertCommand(["gnt-instance", "modify", "-B",
470 (constants.BE_ALWAYS_FAILOVER, constants.VALUE_FALSE)),
474 qa_utils.RunInstanceCheck(instance, True)
477 def TestInstanceInfo(instance):
478 """gnt-instance info"""
479 AssertCommand(["gnt-instance", "info", instance.name])
482 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
483 def TestInstanceModify(instance):
484 """gnt-instance modify"""
485 default_hv = qa_config.GetDefaultHypervisor()
487 # Assume /sbin/init exists on all systems
488 test_kernel = "/sbin/init"
489 test_initrd = test_kernel
491 orig_maxmem = qa_config.get(constants.BE_MAXMEM)
492 orig_minmem = qa_config.get(constants.BE_MINMEM)
493 #orig_bridge = qa_config.get("bridge", "xen-br0")
496 ["-B", "%s=128" % constants.BE_MINMEM],
497 ["-B", "%s=128" % constants.BE_MAXMEM],
498 ["-B", "%s=%s,%s=%s" % (constants.BE_MINMEM, orig_minmem,
499 constants.BE_MAXMEM, orig_maxmem)],
500 ["-B", "%s=2" % constants.BE_VCPUS],
501 ["-B", "%s=1" % constants.BE_VCPUS],
502 ["-B", "%s=%s" % (constants.BE_VCPUS, constants.VALUE_DEFAULT)],
503 ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)],
504 ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_DEFAULT)],
506 ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, test_kernel)],
507 ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, constants.VALUE_DEFAULT)],
510 #["--bridge", "xen-br1"],
511 #["--bridge", orig_bridge],
514 if default_hv == constants.HT_XEN_PVM:
516 ["-H", "%s=%s" % (constants.HV_INITRD_PATH, test_initrd)],
517 ["-H", "no_%s" % (constants.HV_INITRD_PATH, )],
518 ["-H", "%s=%s" % (constants.HV_INITRD_PATH, constants.VALUE_DEFAULT)],
520 elif default_hv == constants.HT_XEN_HVM:
522 ["-H", "%s=acn" % constants.HV_BOOT_ORDER],
523 ["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
527 AssertCommand(["gnt-instance", "modify"] + alist + [instance.name])
530 AssertCommand(["gnt-instance", "modify", instance.name], fail=True)
532 # Marking offline while instance is running must fail...
533 AssertCommand(["gnt-instance", "modify", "--offline", instance.name],
536 # ...while making it online is ok, and should work
537 AssertCommand(["gnt-instance", "modify", "--online", instance.name])
540 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
541 def TestInstanceModifyPrimaryAndBack(instance, currentnode, othernode):
542 """gnt-instance modify --new-primary
544 This will leave the instance on its original primary node, not other node.
547 if instance.disk_template != constants.DT_FILE:
548 print qa_utils.FormatInfo("Test only supported for the file disk template")
551 cluster_name = qa_config.get("name")
554 current = currentnode.primary
555 other = othernode.primary
557 # FIXME: the qa doesn't have a customizable file storage dir parameter. As
558 # such for now we use the default.
559 filestorage = pathutils.DEFAULT_FILE_STORAGE_DIR
560 disk = os.path.join(filestorage, name)
562 AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name],
564 AssertCommand(["gnt-instance", "shutdown", name])
565 AssertCommand(["scp", "-oGlobalKnownHostsFile=%s" %
566 pathutils.SSH_KNOWN_HOSTS_FILE,
567 "-oCheckHostIp=no", "-oStrictHostKeyChecking=yes",
568 "-oHashKnownHosts=no", "-oHostKeyAlias=%s" % cluster_name,
569 "-r", disk, "%s:%s" % (other, filestorage)], node=current)
570 AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name])
571 AssertCommand(["gnt-instance", "startup", name])
574 AssertCommand(["gnt-instance", "shutdown", name])
575 AssertCommand(["rm", "-rf", disk], node=other)
576 AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % current, name])
577 AssertCommand(["gnt-instance", "startup", name])
580 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
581 def TestInstanceStoppedModify(instance):
582 """gnt-instance modify (stopped instance)"""
585 # Instance was not marked offline; try marking it online once more
586 AssertCommand(["gnt-instance", "modify", "--online", name])
588 # Mark instance as offline
589 AssertCommand(["gnt-instance", "modify", "--offline", name])
591 # When the instance is offline shutdown should only work with --force,
592 # while start should never work
593 AssertCommand(["gnt-instance", "shutdown", name], fail=True)
594 AssertCommand(["gnt-instance", "shutdown", "--force", name])
595 AssertCommand(["gnt-instance", "start", name], fail=True)
596 AssertCommand(["gnt-instance", "start", "--force", name], fail=True)
598 # Also do offline to offline
599 AssertCommand(["gnt-instance", "modify", "--offline", name])
602 AssertCommand(["gnt-instance", "modify", "--online", name])
605 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
606 def TestInstanceConvertDiskToPlain(instance, inodes):
607 """gnt-instance modify -t"""
610 template = instance.disk_template
611 if template != constants.DT_DRBD8:
612 print qa_utils.FormatInfo("Unsupported template %s, skipping conversion"
616 assert len(inodes) == 2
617 AssertCommand(["gnt-instance", "modify", "-t", constants.DT_PLAIN, name])
618 AssertCommand(["gnt-instance", "modify", "-t", constants.DT_DRBD8,
619 "-n", inodes[1].primary, name])
622 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
623 def TestInstanceGrowDisk(instance):
624 """gnt-instance grow-disk"""
625 if qa_config.GetExclusiveStorage():
626 print qa_utils.FormatInfo("Test not supported with exclusive_storage")
629 if instance.disk_template == constants.DT_DISKLESS:
630 print qa_utils.FormatInfo("Test not supported for diskless instances")
634 disks = qa_config.GetDiskOptions()
635 all_size = [d.get("size") for d in disks]
636 all_grow = [d.get("growth") for d in disks]
639 # missing disk sizes but instance grow disk has been enabled,
640 # let's set fixed/nomimal growth
641 all_grow = ["128M" for _ in all_size]
643 for idx, (size, grow) in enumerate(zip(all_size, all_grow)):
644 # succeed in grow by amount
645 AssertCommand(["gnt-instance", "grow-disk", name, str(idx), grow])
646 # fail in grow to the old size
647 AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
649 # succeed to grow to old size + 2 * growth
650 int_size = utils.ParseUnit(size)
651 int_grow = utils.ParseUnit(grow)
652 AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
653 str(int_size + 2 * int_grow)])
656 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
657 def TestInstanceDeviceNames(instance):
658 if instance.disk_template == constants.DT_DISKLESS:
659 print qa_utils.FormatInfo("Test not supported for diskless instances")
663 for dev_type in ["disk", "net"]:
664 if dev_type == "disk":
665 options = ",size=512M"
668 # succeed in adding a device named 'test_device'
669 AssertCommand(["gnt-instance", "modify",
670 "--%s=-1:add,name=test_device%s" % (dev_type, options),
672 # succeed in removing the 'test_device'
673 AssertCommand(["gnt-instance", "modify",
674 "--%s=test_device:remove" % dev_type,
676 # fail to add two devices with the same name
677 AssertCommand(["gnt-instance", "modify",
678 "--%s=-1:add,name=test_device%s" % (dev_type, options),
679 "--%s=-1:add,name=test_device%s" % (dev_type, options),
681 # fail to add a device with invalid name
682 AssertCommand(["gnt-instance", "modify",
683 "--%s=-1:add,name=2%s" % (dev_type, options),
686 disks = qa_config.GetDiskOptions()
687 disk_names = [d.get("name") for d in disks]
688 for idx, disk_name in enumerate(disk_names):
689 # Refer to disk by idx
690 AssertCommand(["gnt-instance", "modify",
691 "--disk=%s:modify,name=renamed" % idx,
693 # Refer to by name and rename to original name
694 AssertCommand(["gnt-instance", "modify",
695 "--disk=renamed:modify,name=%s" % disk_name,
698 # fail in renaming to disks to the same name
699 AssertCommand(["gnt-instance", "modify",
700 "--disk=0:modify,name=same_name",
701 "--disk=1:modify,name=same_name",
705 def TestInstanceList():
706 """gnt-instance list"""
707 qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
710 def TestInstanceListFields():
711 """gnt-instance list-fields"""
712 qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
715 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
716 def TestInstanceConsole(instance):
717 """gnt-instance console"""
718 AssertCommand(["gnt-instance", "console", "--show-cmd", instance.name])
721 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
722 def TestReplaceDisks(instance, curr_nodes, other_nodes):
723 """gnt-instance replace-disks"""
725 cmd = ["gnt-instance", "replace-disks"]
727 cmd.append(instance.name)
730 if not IsDiskReplacingSupported(instance):
731 print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
735 # Currently all supported templates have one primary and one secondary node
736 assert len(curr_nodes) == 2
737 snode = curr_nodes[1]
738 assert len(other_nodes) == 1
739 othernode = other_nodes[0]
741 options = qa_config.get("options", {})
742 use_ialloc = options.get("use-iallocators", True)
746 # A placeholder; the actual command choice depends on use_ialloc
748 # Restore the original secondary
749 ["--new-secondary=%s" % snode.primary],
753 data = ["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT]
755 data = ["--new-secondary=%s" % othernode.primary]
756 AssertCommand(buildcmd(data))
758 AssertCommand(buildcmd(["-a"]))
759 AssertCommand(["gnt-instance", "stop", instance.name])
760 AssertCommand(buildcmd(["-a"]), fail=True)
761 AssertCommand(["gnt-instance", "activate-disks", instance.name])
762 AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
764 AssertCommand(buildcmd(["-a"]))
765 AssertCommand(["gnt-instance", "start", instance.name])
768 def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
770 """Execute gnt-instance recreate-disks and check the result
772 @param cmdargs: Arguments (instance name excluded)
773 @param instance: Instance to operate on
774 @param fail: True if the command is expected to fail
775 @param check: If True and fail is False, check that the disks work
776 @prama destroy: If True, destroy the old disks first
780 _DestroyInstanceDisks(instance)
781 AssertCommand((["gnt-instance", "recreate-disks"] + cmdargs +
782 [instance.name]), fail)
783 if not fail and check:
784 # Quick check that the disks are there
785 AssertCommand(["gnt-instance", "activate-disks", instance.name])
786 AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
788 AssertCommand(["gnt-instance", "deactivate-disks", instance.name])
791 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
792 def TestRecreateDisks(instance, inodes, othernodes):
793 """gnt-instance recreate-disks
795 @param instance: Instance to work on
796 @param inodes: List of the current nodes of the instance
797 @param othernodes: list/tuple of nodes where to temporarily recreate disks
800 options = qa_config.get("options", {})
801 use_ialloc = options.get("use-iallocators", True)
802 other_seq = ":".join([n.primary for n in othernodes])
803 orig_seq = ":".join([n.primary for n in inodes])
804 # These fail because the instance is running
805 _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
807 _AssertRecreateDisks(["-I", "hail"], instance, fail=True, destroy=False)
809 _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
810 AssertCommand(["gnt-instance", "stop", instance.name])
811 # Disks exist: this should fail
812 _AssertRecreateDisks([], instance, fail=True, destroy=False)
813 # Recreate disks in place
814 _AssertRecreateDisks([], instance)
817 _AssertRecreateDisks(["-I", "hail"], instance)
818 # Move disks somewhere else
819 _AssertRecreateDisks(["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT],
822 _AssertRecreateDisks(["-n", other_seq], instance)
824 _AssertRecreateDisks(["-n", orig_seq], instance, check=False)
825 # This and InstanceCheck decoration check that the disks are working
826 AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
827 AssertCommand(["gnt-instance", "start", instance.name])
830 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
831 def TestInstanceExport(instance, node):
832 """gnt-backup export -n ..."""
834 AssertCommand(["gnt-backup", "export", "-n", node.primary, name])
835 return qa_utils.ResolveInstanceName(name)
838 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
839 def TestInstanceExportWithRemove(instance, node):
840 """gnt-backup export --remove-instance"""
841 AssertCommand(["gnt-backup", "export", "-n", node.primary,
842 "--remove-instance", instance.name])
845 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
846 def TestInstanceExportNoTarget(instance):
847 """gnt-backup export (without target node, should fail)"""
848 AssertCommand(["gnt-backup", "export", instance.name], fail=True)
851 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
852 def TestInstanceImport(newinst, node, expnode, name):
853 """gnt-backup import"""
854 templ = constants.DT_PLAIN
855 cmd = (["gnt-backup", "import",
856 "--disk-template=%s" % templ,
858 "--src-node=%s" % expnode.primary,
859 "--src-dir=%s/%s" % (pathutils.EXPORT_DIR, name),
860 "--node=%s" % node.primary] +
861 GetGenericAddParameters(newinst, templ,
862 force_mac=constants.VALUE_GENERATE))
863 cmd.append(newinst.name)
865 newinst.SetDiskTemplate(templ)
868 def TestBackupList(expnode):
869 """gnt-backup list"""
870 AssertCommand(["gnt-backup", "list", "--node=%s" % expnode.primary])
872 qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
873 namefield=None, test_unknown=False)
876 def TestBackupListFields():
877 """gnt-backup list-fields"""
878 qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
881 def TestRemoveInstanceOfflineNode(instance, snode, set_offline, set_online):
882 """gnt-instance remove with an off-line node
884 @param instance: instance
885 @param snode: secondary node, to be set offline
886 @param set_offline: function to call to set the node off-line
887 @param set_online: function to call to set the node on-line
890 info = _GetInstanceInfo(instance.name)
893 TestInstanceRemove(instance)
897 # Clean up the disks on the offline node, if necessary
898 if instance.disk_template not in constants.DTS_EXT_MIRROR:
899 # FIXME: abstract the cleanup inside the disks
900 if info["storage-type"] == constants.ST_LVM_VG:
901 for minor in info["drbd-minors"][snode.primary]:
902 # DRBD 8.3 syntax comes first, then DRBD 8.4 syntax. The 8.4 syntax
903 # relies on the fact that we always create a resources for each minor,
904 # and that this resources is always named resource{minor}.
905 # As 'drbdsetup 0 down' does return success (even though that's invalid
906 # syntax), we always have to perform both commands and ignore the
908 drbd_shutdown_cmd = \
909 "(drbdsetup %d down && drbdsetup down resource%d) || /bin/true" % \
911 AssertCommand(drbd_shutdown_cmd, node=snode)
912 AssertCommand(["lvremove", "-f"] + info["volumes"], node=snode)
913 elif info["storage-type"] == constants.ST_FILE:
914 filestorage = pathutils.DEFAULT_FILE_STORAGE_DIR
915 disk = os.path.join(filestorage, instance.name)
916 AssertCommand(["rm", "-rf", disk], node=snode)
919 def TestInstanceCreationRestrictedByDiskTemplates():
920 """Test adding instances for disbled disk templates."""
921 enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
922 nodes = qa_config.AcquireManyNodes(2)
924 # Setup the cluster with the enabled_disk_templates
926 ["gnt-cluster", "modify",
927 "--enabled-disk-template=%s" %
928 ",".join(enabled_disk_templates)],
931 # Test instance creation for enabled disk templates
932 for disk_template in enabled_disk_templates:
933 instance = CreateInstanceByDiskTemplate(nodes, disk_template, False)
934 TestInstanceRemove(instance)
936 # Test that instance creation fails for disabled disk templates
937 disabled_disk_templates = list(constants.DISK_TEMPLATES
938 - set(enabled_disk_templates))
939 for disk_template in disabled_disk_templates:
940 instance = CreateInstanceByDiskTemplate(nodes, disk_template, True)
942 # Test instance creation for after disabling enabled disk templates
943 if (len(enabled_disk_templates) > 1):
944 # Partition the disk templates, enable them separately and check if the
945 # disabled ones cannot be used by instances.
946 middle = len(enabled_disk_templates) / 2
947 templates1 = enabled_disk_templates[:middle]
948 templates2 = enabled_disk_templates[middle:]
950 for (enabled, disabled) in [(templates1, templates2),
951 (templates2, templates1)]:
952 AssertCommand(["gnt-cluster", "modify",
953 "--enabled-disk-template=%s" %
956 for disk_template in disabled:
957 CreateInstanceByDiskTemplate(nodes, disk_template, True)
958 elif (len(enabled_disk_templates) == 1):
959 # If only one disk template is enabled in the QA config, we have to enable
960 # some of the disabled disk templates in order to test if the disabling the
961 # only enabled disk template prohibits creating instances of that template.
962 AssertCommand(["gnt-cluster", "modify",
963 "--enabled-disk-template=%s" %
964 ",".join(disabled_disk_templates)],
966 CreateInstanceByDiskTemplate(nodes, enabled_disk_templates[0], True)
968 raise qa_error.Error("Please enable at least one disk template"
969 " in your QA setup.")
971 # Restore initially enabled disk templates
972 AssertCommand(["gnt-cluster", "modify",
973 "--enabled-disk-template=%s" %
974 ",".join(enabled_disk_templates)],