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():
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)])
56 def _DiskTest(node, disk_template):
57 instance = qa_config.AcquireInstance()
59 cmd = (["gnt-instance", "add",
60 "--os-type=%s" % qa_config.get("os"),
61 "--disk-template=%s" % disk_template,
63 _GetGenericAddParameters())
64 cmd.append(instance["name"])
68 _CheckSsconfInstanceList(instance["name"])
72 qa_config.ReleaseInstance(instance)
76 @InstanceCheck(None, INST_UP, RETURN_VALUE)
77 def TestInstanceAddWithPlainDisk(node):
78 """gnt-instance add -t plain"""
79 return _DiskTest(node["primary"], "plain")
82 @InstanceCheck(None, INST_UP, RETURN_VALUE)
83 def TestInstanceAddWithDrbdDisk(node, node2):
84 """gnt-instance add -t drbd"""
85 return _DiskTest("%s:%s" % (node["primary"], node2["primary"]),
89 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
90 def TestInstanceRemove(instance):
91 """gnt-instance remove"""
92 AssertCommand(["gnt-instance", "remove", "-f", instance["name"]])
94 qa_config.ReleaseInstance(instance)
97 @InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
98 def TestInstanceStartup(instance):
99 """gnt-instance startup"""
100 AssertCommand(["gnt-instance", "startup", instance["name"]])
103 @InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
104 def TestInstanceShutdown(instance):
105 """gnt-instance shutdown"""
106 AssertCommand(["gnt-instance", "shutdown", instance["name"]])
109 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
110 def TestInstanceReboot(instance):
111 """gnt-instance reboot"""
112 options = qa_config.get("options", {})
113 reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
114 name = instance["name"]
115 for rtype in reboot_types:
116 AssertCommand(["gnt-instance", "reboot", "--type=%s" % rtype, name])
118 AssertCommand(["gnt-instance", "shutdown", name])
119 qa_utils.RunInstanceCheck(instance, False)
120 AssertCommand(["gnt-instance", "reboot", name])
122 master = qa_config.GetMasterNode()
123 cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", name]
124 result_output = qa_utils.GetCommandOutput(master["primary"],
125 utils.ShellQuoteArgs(cmd))
126 AssertEqual(result_output.strip(), constants.INSTST_RUNNING)
129 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
130 def TestInstanceReinstall(instance):
131 """gnt-instance reinstall"""
132 AssertCommand(["gnt-instance", "reinstall", "-f", instance["name"]])
135 def _ReadSsconfInstanceList():
136 """Reads ssconf_instance_list from the master node.
139 master = qa_config.GetMasterNode()
141 cmd = ["cat", utils.PathJoin(constants.DATA_DIR,
142 "ssconf_%s" % constants.SS_INSTANCE_LIST)]
144 return qa_utils.GetCommandOutput(master["primary"],
145 utils.ShellQuoteArgs(cmd)).splitlines()
148 def _CheckSsconfInstanceList(instance):
149 """Checks if a certain instance is in the ssconf instance list.
151 @type instance: string
152 @param instance: Instance name
155 AssertIn(qa_utils.ResolveInstanceName(instance),
156 _ReadSsconfInstanceList())
159 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
160 def TestInstanceRenameAndBack(rename_source, rename_target):
161 """gnt-instance rename
163 This must leave the instance with the original name, not the target
167 _CheckSsconfInstanceList(rename_source)
169 # first do a rename to a different actual name, expecting it to fail
170 qa_utils.AddToEtcHosts(["meeeeh-not-exists", rename_target])
172 AssertCommand(["gnt-instance", "rename", rename_source, rename_target],
174 _CheckSsconfInstanceList(rename_source)
176 qa_utils.RemoveFromEtcHosts(["meeeeh-not-exists", rename_target])
178 # and now rename instance to rename_target...
179 AssertCommand(["gnt-instance", "rename", rename_source, rename_target])
180 _CheckSsconfInstanceList(rename_target)
181 qa_utils.RunInstanceCheck(rename_source, False)
182 qa_utils.RunInstanceCheck(rename_target, False)
185 AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
186 _CheckSsconfInstanceList(rename_source)
187 qa_utils.RunInstanceCheck(rename_target, False)
190 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
191 def TestInstanceFailover(instance):
192 """gnt-instance failover"""
193 cmd = ["gnt-instance", "failover", "--force", instance["name"]]
197 qa_utils.RunInstanceCheck(instance, True)
203 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
204 def TestInstanceMigrate(instance):
205 """gnt-instance migrate"""
206 cmd = ["gnt-instance", "migrate", "--force", instance["name"]]
210 qa_utils.RunInstanceCheck(instance, True)
215 # TODO: Split into multiple tests
216 AssertCommand(["gnt-instance", "shutdown", instance["name"]])
217 qa_utils.RunInstanceCheck(instance, False)
218 AssertCommand(cmd, fail=True)
219 AssertCommand(["gnt-instance", "migrate", "--force", "--allow-failover",
221 AssertCommand(["gnt-instance", "start", instance["name"]])
223 qa_utils.RunInstanceCheck(instance, True)
225 AssertCommand(["gnt-instance", "modify", "-B",
227 (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)),
230 AssertCommand(cmd, fail=True)
231 qa_utils.RunInstanceCheck(instance, True)
232 AssertCommand(["gnt-instance", "migrate", "--force", "--allow-failover",
235 # TODO: Verify whether the default value is restored here (not hardcoded)
236 AssertCommand(["gnt-instance", "modify", "-B",
238 (constants.BE_ALWAYS_FAILOVER, constants.VALUE_FALSE)),
242 qa_utils.RunInstanceCheck(instance, True)
245 def TestInstanceInfo(instance):
246 """gnt-instance info"""
247 AssertCommand(["gnt-instance", "info", instance["name"]])
250 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
251 def TestInstanceModify(instance):
252 """gnt-instance modify"""
253 # Assume /sbin/init exists on all systems
254 test_kernel = "/sbin/init"
255 test_initrd = test_kernel
257 orig_maxmem = qa_config.get(constants.BE_MAXMEM)
258 orig_minmem = qa_config.get(constants.BE_MINMEM)
259 #orig_bridge = qa_config.get("bridge", "xen-br0")
261 ["-B", "%s=128" % constants.BE_MINMEM],
262 ["-B", "%s=128" % constants.BE_MAXMEM],
263 ["-B", "%s=%s,%s=%s" % (constants.BE_MINMEM, orig_minmem,
264 constants.BE_MAXMEM, orig_maxmem)],
265 ["-B", "%s=2" % constants.BE_VCPUS],
266 ["-B", "%s=1" % constants.BE_VCPUS],
267 ["-B", "%s=%s" % (constants.BE_VCPUS, constants.VALUE_DEFAULT)],
268 ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)],
269 ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_DEFAULT)],
271 ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, test_kernel)],
272 ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, constants.VALUE_DEFAULT)],
273 ["-H", "%s=%s" % (constants.HV_INITRD_PATH, test_initrd)],
274 ["-H", "no_%s" % (constants.HV_INITRD_PATH, )],
275 ["-H", "%s=%s" % (constants.HV_INITRD_PATH, constants.VALUE_DEFAULT)],
278 #["--bridge", "xen-br1"],
279 #["--bridge", orig_bridge],
281 # TODO: Do these tests only with xen-hvm
282 #["-H", "%s=acn" % constants.HV_BOOT_ORDER],
283 #["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
286 AssertCommand(["gnt-instance", "modify"] + alist + [instance["name"]])
289 AssertCommand(["gnt-instance", "modify", instance["name"]], fail=True)
291 # Marking offline/online while instance is running must fail
292 for arg in ["--online", "--offline"]:
293 AssertCommand(["gnt-instance", "modify", arg, instance["name"]], fail=True)
296 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
297 def TestInstanceStoppedModify(instance):
298 """gnt-instance modify (stopped instance)"""
299 name = instance["name"]
301 # Instance was not marked offline; try marking it online once more
302 AssertCommand(["gnt-instance", "modify", "--online", name])
304 # Mark instance as offline
305 AssertCommand(["gnt-instance", "modify", "--offline", name])
308 AssertCommand(["gnt-instance", "modify", "--online", name])
311 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
312 def TestInstanceConvertDisk(instance, snode):
313 """gnt-instance modify -t"""
314 name = instance["name"]
315 AssertCommand(["gnt-instance", "modify", "-t", "plain", name])
316 AssertCommand(["gnt-instance", "modify", "-t", "drbd",
317 "-n", snode["primary"], name])
320 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
321 def TestInstanceGrowDisk(instance):
322 """gnt-instance grow-disk"""
323 name = instance["name"]
324 all_size = qa_config.get("disk")
325 all_grow = qa_config.get("disk-growth")
327 # missing disk sizes but instance grow disk has been enabled,
328 # let's set fixed/nomimal growth
329 all_grow = ["128M" for _ in all_size]
330 for idx, (size, grow) in enumerate(zip(all_size, all_grow)):
331 # succeed in grow by amount
332 AssertCommand(["gnt-instance", "grow-disk", name, str(idx), grow])
333 # fail in grow to the old size
334 AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
336 # succeed to grow to old size + 2 * growth
337 int_size = utils.ParseUnit(size)
338 int_grow = utils.ParseUnit(grow)
339 AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
340 str(int_size + 2 * int_grow)])
343 def TestInstanceList():
344 """gnt-instance list"""
345 qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
348 def TestInstanceListFields():
349 """gnt-instance list-fields"""
350 qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
353 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
354 def TestInstanceConsole(instance):
355 """gnt-instance console"""
356 AssertCommand(["gnt-instance", "console", "--show-cmd", instance["name"]])
359 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
360 def TestReplaceDisks(instance, pnode, snode, othernode):
361 """gnt-instance replace-disks"""
362 # pylint: disable=W0613
363 # due to unused pnode arg
364 # FIXME: should be removed from the function completely
366 cmd = ["gnt-instance", "replace-disks"]
368 cmd.append(instance["name"])
374 ["--new-secondary=%s" % othernode["primary"]],
376 ["--new-secondary=%s" % snode["primary"]],
378 AssertCommand(buildcmd(data))
380 AssertCommand(buildcmd(["-a"]))
381 AssertCommand(["gnt-instance", "stop", instance["name"]])
382 AssertCommand(buildcmd(["-a"]), fail=True)
383 AssertCommand(["gnt-instance", "activate-disks", instance["name"]])
384 AssertCommand(buildcmd(["-a"]))
385 AssertCommand(["gnt-instance", "start", instance["name"]])
388 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
389 def TestInstanceExport(instance, node):
390 """gnt-backup export -n ..."""
391 name = instance["name"]
392 AssertCommand(["gnt-backup", "export", "-n", node["primary"], name])
393 return qa_utils.ResolveInstanceName(name)
396 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
397 def TestInstanceExportWithRemove(instance, node):
398 """gnt-backup export --remove-instance"""
399 AssertCommand(["gnt-backup", "export", "-n", node["primary"],
400 "--remove-instance", instance["name"]])
403 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
404 def TestInstanceExportNoTarget(instance):
405 """gnt-backup export (without target node, should fail)"""
406 AssertCommand(["gnt-backup", "export", instance["name"]], fail=True)
409 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
410 def TestInstanceImport(newinst, node, expnode, name):
411 """gnt-backup import"""
412 cmd = (["gnt-backup", "import",
413 "--disk-template=plain",
415 "--net", "0:mac=generate",
416 "--src-node=%s" % expnode["primary"],
417 "--src-dir=%s/%s" % (constants.EXPORT_DIR, name),
418 "--node=%s" % node["primary"]] +
419 _GetGenericAddParameters())
420 cmd.append(newinst["name"])
424 def TestBackupList(expnode):
425 """gnt-backup list"""
426 AssertCommand(["gnt-backup", "list", "--node=%s" % expnode["primary"]])
428 qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
429 namefield=None, test_unknown=False)
432 def TestBackupListFields():
433 """gnt-backup list-fields"""
434 qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
437 def _TestInstanceDiskFailure(instance, node, node2, onmaster):
438 """Testing disk failure."""
439 master = qa_config.GetMasterNode()
440 sq = utils.ShellQuoteArgs
442 instance_full = qa_utils.ResolveInstanceName(instance["name"])
443 node_full = qa_utils.ResolveNodeName(node)
444 node2_full = qa_utils.ResolveNodeName(node2)
446 print qa_utils.FormatInfo("Getting physical disk names")
447 cmd = ["gnt-node", "volumes", "--separator=|", "--no-headers",
448 "--output=node,phys,instance",
449 node["primary"], node2["primary"]]
450 output = qa_utils.GetCommandOutput(master["primary"], sq(cmd))
452 # Get physical disk names
453 re_disk = re.compile(r"^/dev/([a-z]+)\d+$")
455 for line in output.splitlines():
456 (node_name, phys, inst) = line.split("|")
457 if inst == instance_full:
458 if node_name not in node2disk:
459 node2disk[node_name] = []
461 m = re_disk.match(phys)
463 raise qa_error.Error("Unknown disk name format: %s" % phys)
466 if name not in node2disk[node_name]:
467 node2disk[node_name].append(name)
469 if [node2_full, node_full][int(onmaster)] not in node2disk:
470 raise qa_error.Error("Couldn't find physical disks used on"
471 " %s node" % ["secondary", "master"][int(onmaster)])
473 print qa_utils.FormatInfo("Checking whether nodes have ability to stop"
475 for node_name, disks in node2disk.iteritems():
478 cmds.append(sq(["test", "-f", _GetDiskStatePath(disk)]))
479 AssertCommand(" && ".join(cmds), node=node_name)
481 print qa_utils.FormatInfo("Getting device paths")
482 cmd = ["gnt-instance", "activate-disks", instance["name"]]
483 output = qa_utils.GetCommandOutput(master["primary"], sq(cmd))
485 for line in output.splitlines():
486 (_, _, tmpdevpath) = line.split(":")
487 devpath.append(tmpdevpath)
490 print qa_utils.FormatInfo("Getting drbd device paths")
491 cmd = ["gnt-instance", "info", instance["name"]]
492 output = qa_utils.GetCommandOutput(master["primary"], sq(cmd))
493 pattern = (r"\s+-\s+sd[a-z]+,\s+type:\s+drbd8?,\s+.*$"
494 r"\s+primary:\s+(/dev/drbd\d+)\s+")
495 drbddevs = re.findall(pattern, output, re.M)
500 print qa_utils.FormatInfo("Deactivating disks")
502 for name in node2disk[[node2_full, node_full][int(onmaster)]]:
503 halted_disks.append(name)
504 cmds.append(sq(["echo", "offline"]) + " >%s" % _GetDiskStatePath(name))
505 AssertCommand(" && ".join(cmds), node=[node2, node][int(onmaster)])
507 print qa_utils.FormatInfo("Write to disks and give some time to notice"
508 " to notice the problem")
511 cmds.append(sq(["dd", "count=1", "bs=512", "conv=notrunc",
512 "if=%s" % disk, "of=%s" % disk]))
514 AssertCommand(" && ".join(cmds), node=node)
517 print qa_utils.FormatInfo("Debugging info")
518 for name in drbddevs:
519 AssertCommand(["drbdsetup", name, "show"], node=node)
521 AssertCommand(["gnt-instance", "info", instance["name"]])
524 print qa_utils.FormatInfo("Activating disks again")
526 for name in halted_disks:
527 cmds.append(sq(["echo", "running"]) + " >%s" % _GetDiskStatePath(name))
528 AssertCommand("; ".join(cmds), node=[node2, node][int(onmaster)])
531 for name in drbddevs:
532 AssertCommand(["drbdsetup", name, "detach"], node=node)
534 for name in drbddevs:
535 AssertCommand(["drbdsetup", name, "disconnect"], node=node2)
538 #AssertCommand(["vgs"], [node2, node][int(onmaster)])
540 print qa_utils.FormatInfo("Making sure disks are up again")
541 AssertCommand(["gnt-instance", "replace-disks", instance["name"]])
543 print qa_utils.FormatInfo("Restarting instance")
544 AssertCommand(["gnt-instance", "shutdown", instance["name"]])
545 AssertCommand(["gnt-instance", "startup", instance["name"]])
547 AssertCommand(["gnt-cluster", "verify"])
550 def TestInstanceMasterDiskFailure(instance, node, node2):
551 """Testing disk failure on master node."""
552 # pylint: disable=W0613
554 print qa_utils.FormatError("Disk failure on primary node cannot be"
555 " tested due to potential crashes.")
556 # The following can cause crashes, thus it's disabled until fixed
557 #return _TestInstanceDiskFailure(instance, node, node2, True)
560 def TestInstanceSecondaryDiskFailure(instance, node, node2):
561 """Testing disk failure on secondary node."""
562 return _TestInstanceDiskFailure(instance, node, node2, False)