4 # Copyright (C) 2007, 2011, 2012 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
37 from qa_utils import AssertIn, AssertCommand, AssertEqual
38 from qa_utils import InstanceCheck, INST_DOWN, INST_UP, FIRST_ARG, RETURN_VALUE
41 def _GetDiskStatePath(disk):
42 return "/sys/block/%s/device/state" % disk
45 def _GetGenericAddParameters(inst, force_mac=None):
47 params.append("%s=%s,%s=%s" % (constants.BE_MINMEM,
48 qa_config.get(constants.BE_MINMEM),
50 qa_config.get(constants.BE_MAXMEM)))
51 for idx, size in enumerate(qa_config.get("disk")):
52 params.extend(["--disk", "%s:size=%s" % (idx, size)])
54 # Set static MAC address if configured
58 nic0_mac = qa_config.GetInstanceNicMac(inst)
60 params.extend(["--net", "0:mac=%s" % nic0_mac])
65 def _DiskTest(node, disk_template):
66 instance = qa_config.AcquireInstance()
68 cmd = (["gnt-instance", "add",
69 "--os-type=%s" % qa_config.get("os"),
70 "--disk-template=%s" % disk_template,
72 _GetGenericAddParameters(instance))
73 cmd.append(instance["name"])
77 _CheckSsconfInstanceList(instance["name"])
81 qa_config.ReleaseInstance(instance)
85 @InstanceCheck(None, INST_UP, RETURN_VALUE)
86 def TestInstanceAddWithPlainDisk(node):
87 """gnt-instance add -t plain"""
88 return _DiskTest(node["primary"], "plain")
91 @InstanceCheck(None, INST_UP, RETURN_VALUE)
92 def TestInstanceAddWithDrbdDisk(node, node2):
93 """gnt-instance add -t drbd"""
94 return _DiskTest("%s:%s" % (node["primary"], node2["primary"]),
98 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
99 def TestInstanceRemove(instance):
100 """gnt-instance remove"""
101 AssertCommand(["gnt-instance", "remove", "-f", instance["name"]])
103 qa_config.ReleaseInstance(instance)
106 @InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
107 def TestInstanceStartup(instance):
108 """gnt-instance startup"""
109 AssertCommand(["gnt-instance", "startup", instance["name"]])
112 @InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
113 def TestInstanceShutdown(instance):
114 """gnt-instance shutdown"""
115 AssertCommand(["gnt-instance", "shutdown", instance["name"]])
118 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
119 def TestInstanceReboot(instance):
120 """gnt-instance reboot"""
121 options = qa_config.get("options", {})
122 reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
123 name = instance["name"]
124 for rtype in reboot_types:
125 AssertCommand(["gnt-instance", "reboot", "--type=%s" % rtype, name])
127 AssertCommand(["gnt-instance", "shutdown", name])
128 qa_utils.RunInstanceCheck(instance, False)
129 AssertCommand(["gnt-instance", "reboot", name])
131 master = qa_config.GetMasterNode()
132 cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", name]
133 result_output = qa_utils.GetCommandOutput(master["primary"],
134 utils.ShellQuoteArgs(cmd))
135 AssertEqual(result_output.strip(), constants.INSTST_RUNNING)
138 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
139 def TestInstanceReinstall(instance):
140 """gnt-instance reinstall"""
141 AssertCommand(["gnt-instance", "reinstall", "-f", instance["name"]])
143 # Test with non-existant OS definition
144 AssertCommand(["gnt-instance", "reinstall", "-f",
145 "--os-type=NonExistantOsForQa",
150 def _ReadSsconfInstanceList():
151 """Reads ssconf_instance_list from the master node.
154 master = qa_config.GetMasterNode()
156 cmd = ["cat", utils.PathJoin(constants.DATA_DIR,
157 "ssconf_%s" % constants.SS_INSTANCE_LIST)]
159 return qa_utils.GetCommandOutput(master["primary"],
160 utils.ShellQuoteArgs(cmd)).splitlines()
163 def _CheckSsconfInstanceList(instance):
164 """Checks if a certain instance is in the ssconf instance list.
166 @type instance: string
167 @param instance: Instance name
170 AssertIn(qa_utils.ResolveInstanceName(instance),
171 _ReadSsconfInstanceList())
174 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
175 def TestInstanceRenameAndBack(rename_source, rename_target):
176 """gnt-instance rename
178 This must leave the instance with the original name, not the target
182 _CheckSsconfInstanceList(rename_source)
184 # first do a rename to a different actual name, expecting it to fail
185 qa_utils.AddToEtcHosts(["meeeeh-not-exists", rename_target])
187 AssertCommand(["gnt-instance", "rename", rename_source, rename_target],
189 _CheckSsconfInstanceList(rename_source)
191 qa_utils.RemoveFromEtcHosts(["meeeeh-not-exists", rename_target])
193 # and now rename instance to rename_target...
194 AssertCommand(["gnt-instance", "rename", rename_source, rename_target])
195 _CheckSsconfInstanceList(rename_target)
196 qa_utils.RunInstanceCheck(rename_source, False)
197 qa_utils.RunInstanceCheck(rename_target, False)
200 AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
201 _CheckSsconfInstanceList(rename_source)
202 qa_utils.RunInstanceCheck(rename_target, False)
205 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
206 def TestInstanceFailover(instance):
207 """gnt-instance failover"""
208 cmd = ["gnt-instance", "failover", "--force", instance["name"]]
212 qa_utils.RunInstanceCheck(instance, True)
218 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
219 def TestInstanceMigrate(instance):
220 """gnt-instance migrate"""
221 cmd = ["gnt-instance", "migrate", "--force", instance["name"]]
225 qa_utils.RunInstanceCheck(instance, True)
230 # TODO: Split into multiple tests
231 AssertCommand(["gnt-instance", "shutdown", instance["name"]])
232 qa_utils.RunInstanceCheck(instance, False)
233 AssertCommand(cmd, fail=True)
234 AssertCommand(["gnt-instance", "migrate", "--force", "--allow-failover",
236 AssertCommand(["gnt-instance", "start", instance["name"]])
238 qa_utils.RunInstanceCheck(instance, True)
240 AssertCommand(["gnt-instance", "modify", "-B",
242 (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)),
246 qa_utils.RunInstanceCheck(instance, True)
247 # TODO: Verify that a failover has been done instead of a migration
249 # TODO: Verify whether the default value is restored here (not hardcoded)
250 AssertCommand(["gnt-instance", "modify", "-B",
252 (constants.BE_ALWAYS_FAILOVER, constants.VALUE_FALSE)),
256 qa_utils.RunInstanceCheck(instance, True)
259 def TestInstanceInfo(instance):
260 """gnt-instance info"""
261 AssertCommand(["gnt-instance", "info", instance["name"]])
264 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
265 def TestInstanceModify(instance):
266 """gnt-instance modify"""
267 default_hv = qa_config.GetDefaultHypervisor()
269 # Assume /sbin/init exists on all systems
270 test_kernel = "/sbin/init"
271 test_initrd = test_kernel
273 orig_maxmem = qa_config.get(constants.BE_MAXMEM)
274 orig_minmem = qa_config.get(constants.BE_MINMEM)
275 #orig_bridge = qa_config.get("bridge", "xen-br0")
278 ["-B", "%s=128" % constants.BE_MINMEM],
279 ["-B", "%s=128" % constants.BE_MAXMEM],
280 ["-B", "%s=%s,%s=%s" % (constants.BE_MINMEM, orig_minmem,
281 constants.BE_MAXMEM, orig_maxmem)],
282 ["-B", "%s=2" % constants.BE_VCPUS],
283 ["-B", "%s=1" % constants.BE_VCPUS],
284 ["-B", "%s=%s" % (constants.BE_VCPUS, constants.VALUE_DEFAULT)],
285 ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)],
286 ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_DEFAULT)],
288 ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, test_kernel)],
289 ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, constants.VALUE_DEFAULT)],
292 #["--bridge", "xen-br1"],
293 #["--bridge", orig_bridge],
296 if default_hv == constants.HT_XEN_PVM:
298 ["-H", "%s=%s" % (constants.HV_INITRD_PATH, test_initrd)],
299 ["-H", "no_%s" % (constants.HV_INITRD_PATH, )],
300 ["-H", "%s=%s" % (constants.HV_INITRD_PATH, constants.VALUE_DEFAULT)],
302 elif default_hv == constants.HT_XEN_HVM:
304 ["-H", "%s=acn" % constants.HV_BOOT_ORDER],
305 ["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
309 AssertCommand(["gnt-instance", "modify"] + alist + [instance["name"]])
312 AssertCommand(["gnt-instance", "modify", instance["name"]], fail=True)
314 # Marking offline/online while instance is running must fail
315 for arg in ["--online", "--offline"]:
316 AssertCommand(["gnt-instance", "modify", arg, instance["name"]], fail=True)
319 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
320 def TestInstanceStoppedModify(instance):
321 """gnt-instance modify (stopped instance)"""
322 name = instance["name"]
324 # Instance was not marked offline; try marking it online once more
325 AssertCommand(["gnt-instance", "modify", "--online", name])
327 # Mark instance as offline
328 AssertCommand(["gnt-instance", "modify", "--offline", name])
331 AssertCommand(["gnt-instance", "modify", "--online", name])
334 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
335 def TestInstanceConvertDisk(instance, snode):
336 """gnt-instance modify -t"""
337 name = instance["name"]
338 AssertCommand(["gnt-instance", "modify", "-t", "plain", name])
339 AssertCommand(["gnt-instance", "modify", "-t", "drbd",
340 "-n", snode["primary"], name])
343 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
344 def TestInstanceGrowDisk(instance):
345 """gnt-instance grow-disk"""
346 name = instance["name"]
347 all_size = qa_config.get("disk")
348 all_grow = qa_config.get("disk-growth")
350 # missing disk sizes but instance grow disk has been enabled,
351 # let's set fixed/nomimal growth
352 all_grow = ["128M" for _ in all_size]
353 for idx, (size, grow) in enumerate(zip(all_size, all_grow)):
354 # succeed in grow by amount
355 AssertCommand(["gnt-instance", "grow-disk", name, str(idx), grow])
356 # fail in grow to the old size
357 AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
359 # succeed to grow to old size + 2 * growth
360 int_size = utils.ParseUnit(size)
361 int_grow = utils.ParseUnit(grow)
362 AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
363 str(int_size + 2 * int_grow)])
366 def TestInstanceList():
367 """gnt-instance list"""
368 qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
371 def TestInstanceListFields():
372 """gnt-instance list-fields"""
373 qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
376 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
377 def TestInstanceConsole(instance):
378 """gnt-instance console"""
379 AssertCommand(["gnt-instance", "console", "--show-cmd", instance["name"]])
382 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
383 def TestReplaceDisks(instance, pnode, snode, othernode):
384 """gnt-instance replace-disks"""
385 # pylint: disable=W0613
386 # due to unused pnode arg
387 # FIXME: should be removed from the function completely
389 cmd = ["gnt-instance", "replace-disks"]
391 cmd.append(instance["name"])
397 ["--new-secondary=%s" % othernode["primary"]],
399 ["--new-secondary=%s" % snode["primary"]],
401 AssertCommand(buildcmd(data))
403 AssertCommand(buildcmd(["-a"]))
404 AssertCommand(["gnt-instance", "stop", instance["name"]])
405 AssertCommand(buildcmd(["-a"]), fail=True)
406 AssertCommand(["gnt-instance", "activate-disks", instance["name"]])
407 AssertCommand(buildcmd(["-a"]))
408 AssertCommand(["gnt-instance", "start", instance["name"]])
411 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
412 def TestInstanceExport(instance, node):
413 """gnt-backup export -n ..."""
414 name = instance["name"]
415 AssertCommand(["gnt-backup", "export", "-n", node["primary"], name])
416 return qa_utils.ResolveInstanceName(name)
419 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
420 def TestInstanceExportWithRemove(instance, node):
421 """gnt-backup export --remove-instance"""
422 AssertCommand(["gnt-backup", "export", "-n", node["primary"],
423 "--remove-instance", instance["name"]])
426 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
427 def TestInstanceExportNoTarget(instance):
428 """gnt-backup export (without target node, should fail)"""
429 AssertCommand(["gnt-backup", "export", instance["name"]], fail=True)
432 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
433 def TestInstanceImport(newinst, node, expnode, name):
434 """gnt-backup import"""
435 cmd = (["gnt-backup", "import",
436 "--disk-template=plain",
438 "--src-node=%s" % expnode["primary"],
439 "--src-dir=%s/%s" % (constants.EXPORT_DIR, name),
440 "--node=%s" % node["primary"]] +
441 _GetGenericAddParameters(newinst, force_mac=constants.VALUE_GENERATE))
442 cmd.append(newinst["name"])
446 def TestBackupList(expnode):
447 """gnt-backup list"""
448 AssertCommand(["gnt-backup", "list", "--node=%s" % expnode["primary"]])
450 qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
451 namefield=None, test_unknown=False)
454 def TestBackupListFields():
455 """gnt-backup list-fields"""
456 qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
459 def _TestInstanceDiskFailure(instance, node, node2, onmaster):
460 """Testing disk failure."""
461 master = qa_config.GetMasterNode()
462 sq = utils.ShellQuoteArgs
464 instance_full = qa_utils.ResolveInstanceName(instance["name"])
465 node_full = qa_utils.ResolveNodeName(node)
466 node2_full = qa_utils.ResolveNodeName(node2)
468 print qa_utils.FormatInfo("Getting physical disk names")
469 cmd = ["gnt-node", "volumes", "--separator=|", "--no-headers",
470 "--output=node,phys,instance",
471 node["primary"], node2["primary"]]
472 output = qa_utils.GetCommandOutput(master["primary"], sq(cmd))
474 # Get physical disk names
475 re_disk = re.compile(r"^/dev/([a-z]+)\d+$")
477 for line in output.splitlines():
478 (node_name, phys, inst) = line.split("|")
479 if inst == instance_full:
480 if node_name not in node2disk:
481 node2disk[node_name] = []
483 m = re_disk.match(phys)
485 raise qa_error.Error("Unknown disk name format: %s" % phys)
488 if name not in node2disk[node_name]:
489 node2disk[node_name].append(name)
491 if [node2_full, node_full][int(onmaster)] not in node2disk:
492 raise qa_error.Error("Couldn't find physical disks used on"
493 " %s node" % ["secondary", "master"][int(onmaster)])
495 print qa_utils.FormatInfo("Checking whether nodes have ability to stop"
497 for node_name, disks in node2disk.iteritems():
500 cmds.append(sq(["test", "-f", _GetDiskStatePath(disk)]))
501 AssertCommand(" && ".join(cmds), node=node_name)
503 print qa_utils.FormatInfo("Getting device paths")
504 cmd = ["gnt-instance", "activate-disks", instance["name"]]
505 output = qa_utils.GetCommandOutput(master["primary"], sq(cmd))
507 for line in output.splitlines():
508 (_, _, tmpdevpath) = line.split(":")
509 devpath.append(tmpdevpath)
512 print qa_utils.FormatInfo("Getting drbd device paths")
513 cmd = ["gnt-instance", "info", instance["name"]]
514 output = qa_utils.GetCommandOutput(master["primary"], sq(cmd))
515 pattern = (r"\s+-\s+sd[a-z]+,\s+type:\s+drbd8?,\s+.*$"
516 r"\s+primary:\s+(/dev/drbd\d+)\s+")
517 drbddevs = re.findall(pattern, output, re.M)
522 print qa_utils.FormatInfo("Deactivating disks")
524 for name in node2disk[[node2_full, node_full][int(onmaster)]]:
525 halted_disks.append(name)
526 cmds.append(sq(["echo", "offline"]) + " >%s" % _GetDiskStatePath(name))
527 AssertCommand(" && ".join(cmds), node=[node2, node][int(onmaster)])
529 print qa_utils.FormatInfo("Write to disks and give some time to notice"
533 cmds.append(sq(["dd", "count=1", "bs=512", "conv=notrunc",
534 "if=%s" % disk, "of=%s" % disk]))
536 AssertCommand(" && ".join(cmds), node=node)
539 print qa_utils.FormatInfo("Debugging info")
540 for name in drbddevs:
541 AssertCommand(["drbdsetup", name, "show"], node=node)
543 AssertCommand(["gnt-instance", "info", instance["name"]])
546 print qa_utils.FormatInfo("Activating disks again")
548 for name in halted_disks:
549 cmds.append(sq(["echo", "running"]) + " >%s" % _GetDiskStatePath(name))
550 AssertCommand("; ".join(cmds), node=[node2, node][int(onmaster)])
553 for name in drbddevs:
554 AssertCommand(["drbdsetup", name, "detach"], node=node)
556 for name in drbddevs:
557 AssertCommand(["drbdsetup", name, "disconnect"], node=node2)
560 #AssertCommand(["vgs"], [node2, node][int(onmaster)])
562 print qa_utils.FormatInfo("Making sure disks are up again")
563 AssertCommand(["gnt-instance", "replace-disks", instance["name"]])
565 print qa_utils.FormatInfo("Restarting instance")
566 AssertCommand(["gnt-instance", "shutdown", instance["name"]])
567 AssertCommand(["gnt-instance", "startup", instance["name"]])
569 AssertCommand(["gnt-cluster", "verify"])
572 def TestInstanceMasterDiskFailure(instance, node, node2):
573 """Testing disk failure on master node."""
574 # pylint: disable=W0613
576 print qa_utils.FormatError("Disk failure on primary node cannot be"
577 " tested due to potential crashes.")
578 # The following can cause crashes, thus it's disabled until fixed
579 #return _TestInstanceDiskFailure(instance, node, node2, True)
582 def TestInstanceSecondaryDiskFailure(instance, node, node2):
583 """Testing disk failure on secondary node."""
584 return _TestInstanceDiskFailure(instance, node, node2, False)