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 def _DestroyInstanceVolumes(instance):
86 """Remove all the LVM volumes of an instance.
88 This is used to simulate HW errors (dead nodes, broken disks...); the
89 configuration of the instance is not affected.
92 master = qa_config.GetMasterNode()
93 infocmd = utils.ShellQuoteArgs(["gnt-instance", "info", instance["name"]])
94 info_out = qa_utils.GetCommandOutput(master["primary"], infocmd)
95 re_node = re.compile(r"^\s+-\s+(?:primary|secondaries):\s+(\S.+)$")
96 node_elem = r"([^,()]+)(?:\s+\([^)]+\))?"
97 # re_nodelist matches a list of nodes returned by gnt-instance info, e.g.:
99 # node2.fqdn,node3.fqdn
100 # node4.fqdn (group mygroup, group UUID 01234567-abcd-0123-4567-0123456789ab)
101 # FIXME This works with no more than 2 secondaries
102 re_nodelist = re.compile(node_elem + "(?:," + node_elem + ")?$")
103 re_vol = re.compile(r"^\s+logical_id:\s+(\S+)$")
106 for line in info_out.splitlines():
107 m = re_node.match(line)
110 m2 = re_nodelist.match(nodestr)
112 nodes.extend(filter(None, m2.groups()))
114 nodes.append(nodestr)
115 m = re_vol.match(line)
117 vols.append(m.group(1))
121 AssertCommand(["lvremove", "-f"] + vols, node=node)
124 @InstanceCheck(None, INST_UP, RETURN_VALUE)
125 def TestInstanceAddWithPlainDisk(node):
126 """gnt-instance add -t plain"""
127 return _DiskTest(node["primary"], "plain")
130 @InstanceCheck(None, INST_UP, RETURN_VALUE)
131 def TestInstanceAddWithDrbdDisk(node, node2):
132 """gnt-instance add -t drbd"""
133 return _DiskTest("%s:%s" % (node["primary"], node2["primary"]),
137 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
138 def TestInstanceRemove(instance):
139 """gnt-instance remove"""
140 AssertCommand(["gnt-instance", "remove", "-f", instance["name"]])
142 qa_config.ReleaseInstance(instance)
145 @InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
146 def TestInstanceStartup(instance):
147 """gnt-instance startup"""
148 AssertCommand(["gnt-instance", "startup", instance["name"]])
151 @InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
152 def TestInstanceShutdown(instance):
153 """gnt-instance shutdown"""
154 AssertCommand(["gnt-instance", "shutdown", instance["name"]])
157 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
158 def TestInstanceReboot(instance):
159 """gnt-instance reboot"""
160 options = qa_config.get("options", {})
161 reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
162 name = instance["name"]
163 for rtype in reboot_types:
164 AssertCommand(["gnt-instance", "reboot", "--type=%s" % rtype, name])
166 AssertCommand(["gnt-instance", "shutdown", name])
167 qa_utils.RunInstanceCheck(instance, False)
168 AssertCommand(["gnt-instance", "reboot", name])
170 master = qa_config.GetMasterNode()
171 cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", name]
172 result_output = qa_utils.GetCommandOutput(master["primary"],
173 utils.ShellQuoteArgs(cmd))
174 AssertEqual(result_output.strip(), constants.INSTST_RUNNING)
177 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
178 def TestInstanceReinstall(instance):
179 """gnt-instance reinstall"""
180 AssertCommand(["gnt-instance", "reinstall", "-f", instance["name"]])
183 def _ReadSsconfInstanceList():
184 """Reads ssconf_instance_list from the master node.
187 master = qa_config.GetMasterNode()
189 cmd = ["cat", utils.PathJoin(constants.DATA_DIR,
190 "ssconf_%s" % constants.SS_INSTANCE_LIST)]
192 return qa_utils.GetCommandOutput(master["primary"],
193 utils.ShellQuoteArgs(cmd)).splitlines()
196 def _CheckSsconfInstanceList(instance):
197 """Checks if a certain instance is in the ssconf instance list.
199 @type instance: string
200 @param instance: Instance name
203 AssertIn(qa_utils.ResolveInstanceName(instance),
204 _ReadSsconfInstanceList())
207 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
208 def TestInstanceRenameAndBack(rename_source, rename_target):
209 """gnt-instance rename
211 This must leave the instance with the original name, not the target
215 _CheckSsconfInstanceList(rename_source)
217 # first do a rename to a different actual name, expecting it to fail
218 qa_utils.AddToEtcHosts(["meeeeh-not-exists", rename_target])
220 AssertCommand(["gnt-instance", "rename", rename_source, rename_target],
222 _CheckSsconfInstanceList(rename_source)
224 qa_utils.RemoveFromEtcHosts(["meeeeh-not-exists", rename_target])
226 # and now rename instance to rename_target...
227 AssertCommand(["gnt-instance", "rename", rename_source, rename_target])
228 _CheckSsconfInstanceList(rename_target)
229 qa_utils.RunInstanceCheck(rename_source, False)
230 qa_utils.RunInstanceCheck(rename_target, False)
233 AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
234 _CheckSsconfInstanceList(rename_source)
235 qa_utils.RunInstanceCheck(rename_target, False)
238 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
239 def TestInstanceFailover(instance):
240 """gnt-instance failover"""
241 cmd = ["gnt-instance", "failover", "--force", instance["name"]]
245 qa_utils.RunInstanceCheck(instance, True)
251 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
252 def TestInstanceMigrate(instance):
253 """gnt-instance migrate"""
254 cmd = ["gnt-instance", "migrate", "--force", instance["name"]]
258 qa_utils.RunInstanceCheck(instance, True)
263 # TODO: Split into multiple tests
264 AssertCommand(["gnt-instance", "shutdown", instance["name"]])
265 qa_utils.RunInstanceCheck(instance, False)
266 AssertCommand(cmd, fail=True)
267 AssertCommand(["gnt-instance", "migrate", "--force", "--allow-failover",
269 AssertCommand(["gnt-instance", "start", instance["name"]])
271 qa_utils.RunInstanceCheck(instance, True)
273 AssertCommand(["gnt-instance", "modify", "-B",
275 (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)),
278 AssertCommand(cmd, fail=True)
279 qa_utils.RunInstanceCheck(instance, True)
280 AssertCommand(["gnt-instance", "migrate", "--force", "--allow-failover",
283 # TODO: Verify whether the default value is restored here (not hardcoded)
284 AssertCommand(["gnt-instance", "modify", "-B",
286 (constants.BE_ALWAYS_FAILOVER, constants.VALUE_FALSE)),
290 qa_utils.RunInstanceCheck(instance, True)
293 def TestInstanceInfo(instance):
294 """gnt-instance info"""
295 AssertCommand(["gnt-instance", "info", instance["name"]])
298 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
299 def TestInstanceModify(instance):
300 """gnt-instance modify"""
301 # Assume /sbin/init exists on all systems
302 test_kernel = "/sbin/init"
303 test_initrd = test_kernel
305 orig_maxmem = qa_config.get(constants.BE_MAXMEM)
306 orig_minmem = qa_config.get(constants.BE_MINMEM)
307 #orig_bridge = qa_config.get("bridge", "xen-br0")
309 ["-B", "%s=128" % constants.BE_MINMEM],
310 ["-B", "%s=128" % constants.BE_MAXMEM],
311 ["-B", "%s=%s,%s=%s" % (constants.BE_MINMEM, orig_minmem,
312 constants.BE_MAXMEM, orig_maxmem)],
313 ["-B", "%s=2" % constants.BE_VCPUS],
314 ["-B", "%s=1" % constants.BE_VCPUS],
315 ["-B", "%s=%s" % (constants.BE_VCPUS, constants.VALUE_DEFAULT)],
316 ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)],
317 ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_DEFAULT)],
319 ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, test_kernel)],
320 ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, constants.VALUE_DEFAULT)],
321 ["-H", "%s=%s" % (constants.HV_INITRD_PATH, test_initrd)],
322 ["-H", "no_%s" % (constants.HV_INITRD_PATH, )],
323 ["-H", "%s=%s" % (constants.HV_INITRD_PATH, constants.VALUE_DEFAULT)],
326 #["--bridge", "xen-br1"],
327 #["--bridge", orig_bridge],
329 # TODO: Do these tests only with xen-hvm
330 #["-H", "%s=acn" % constants.HV_BOOT_ORDER],
331 #["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
334 AssertCommand(["gnt-instance", "modify"] + alist + [instance["name"]])
337 AssertCommand(["gnt-instance", "modify", instance["name"]], fail=True)
339 # Marking offline/online while instance is running must fail
340 for arg in ["--online", "--offline"]:
341 AssertCommand(["gnt-instance", "modify", arg, instance["name"]], fail=True)
344 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
345 def TestInstanceStoppedModify(instance):
346 """gnt-instance modify (stopped instance)"""
347 name = instance["name"]
349 # Instance was not marked offline; try marking it online once more
350 AssertCommand(["gnt-instance", "modify", "--online", name])
352 # Mark instance as offline
353 AssertCommand(["gnt-instance", "modify", "--offline", name])
356 AssertCommand(["gnt-instance", "modify", "--online", name])
359 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
360 def TestInstanceConvertDisk(instance, snode):
361 """gnt-instance modify -t"""
362 name = instance["name"]
363 AssertCommand(["gnt-instance", "modify", "-t", "plain", name])
364 AssertCommand(["gnt-instance", "modify", "-t", "drbd",
365 "-n", snode["primary"], name])
368 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
369 def TestInstanceGrowDisk(instance):
370 """gnt-instance grow-disk"""
371 name = instance["name"]
372 all_size = qa_config.get("disk")
373 all_grow = qa_config.get("disk-growth")
375 # missing disk sizes but instance grow disk has been enabled,
376 # let's set fixed/nomimal growth
377 all_grow = ["128M" for _ in all_size]
378 for idx, (size, grow) in enumerate(zip(all_size, all_grow)):
379 # succeed in grow by amount
380 AssertCommand(["gnt-instance", "grow-disk", name, str(idx), grow])
381 # fail in grow to the old size
382 AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
384 # succeed to grow to old size + 2 * growth
385 int_size = utils.ParseUnit(size)
386 int_grow = utils.ParseUnit(grow)
387 AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
388 str(int_size + 2 * int_grow)])
391 def TestInstanceList():
392 """gnt-instance list"""
393 qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
396 def TestInstanceListFields():
397 """gnt-instance list-fields"""
398 qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
401 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
402 def TestInstanceConsole(instance):
403 """gnt-instance console"""
404 AssertCommand(["gnt-instance", "console", "--show-cmd", instance["name"]])
407 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
408 def TestReplaceDisks(instance, pnode, snode, othernode):
409 """gnt-instance replace-disks"""
410 # pylint: disable=W0613
411 # due to unused pnode arg
412 # FIXME: should be removed from the function completely
414 cmd = ["gnt-instance", "replace-disks"]
416 cmd.append(instance["name"])
422 ["--new-secondary=%s" % othernode["primary"]],
424 ["--new-secondary=%s" % snode["primary"]],
426 AssertCommand(buildcmd(data))
428 AssertCommand(buildcmd(["-a"]))
429 AssertCommand(["gnt-instance", "stop", instance["name"]])
430 AssertCommand(buildcmd(["-a"]), fail=True)
431 AssertCommand(["gnt-instance", "activate-disks", instance["name"]])
432 AssertCommand(buildcmd(["-a"]))
433 AssertCommand(["gnt-instance", "start", instance["name"]])
436 def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
438 """Execute gnt-instance recreate-disks and check the result
440 @param cmdargs: Arguments (instance name excluded)
441 @param instance: Instance to operate on
442 @param fail: True if the command is expected to fail
443 @param check: If True and fail is False, check that the disks work
444 @prama destroy: If True, destroy the old disks first
448 _DestroyInstanceVolumes(instance)
449 AssertCommand((["gnt-instance", "recreate-disks"] + cmdargs +
450 [instance["name"]]), fail)
451 if not fail and check:
452 # Quick check that the disks are there
453 AssertCommand(["gnt-instance", "activate-disks", instance["name"]])
454 AssertCommand(["gnt-instance", "deactivate-disks", instance["name"]])
456 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
457 def TestRecreateDisks(instance, pnode, snode, othernodes):
458 """gnt-instance recreate-disks
460 @param instance: Instance to work on
461 @param pnode: Primary node
462 @param snode: Secondary node, or None for sigle-homed instances
463 @param othernodes: list/tuple of nodes where to temporarily recreate disks
466 other_seq = ":".join([n["primary"] for n in othernodes])
467 orig_seq = pnode["primary"]
469 orig_seq = orig_seq + ":" + snode["primary"]
470 # This fails beacuse the instance is running
471 _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
472 AssertCommand(["gnt-instance", "stop", instance["name"]])
473 # Disks exist: this should fail
474 _AssertRecreateDisks([], instance, fail=True, destroy=False)
475 # Recreate disks in place
476 _AssertRecreateDisks([], instance)
478 _AssertRecreateDisks(["-n", other_seq], instance)
480 _AssertRecreateDisks(["-n", orig_seq], instance, check=False)
481 # This and InstanceCheck decoration check that the disks are working
482 AssertCommand(["gnt-instance", "reinstall", "-f", instance["name"]])
483 AssertCommand(["gnt-instance", "start", instance["name"]])
486 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
487 def TestInstanceExport(instance, node):
488 """gnt-backup export -n ..."""
489 name = instance["name"]
490 AssertCommand(["gnt-backup", "export", "-n", node["primary"], name])
491 return qa_utils.ResolveInstanceName(name)
494 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
495 def TestInstanceExportWithRemove(instance, node):
496 """gnt-backup export --remove-instance"""
497 AssertCommand(["gnt-backup", "export", "-n", node["primary"],
498 "--remove-instance", instance["name"]])
501 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
502 def TestInstanceExportNoTarget(instance):
503 """gnt-backup export (without target node, should fail)"""
504 AssertCommand(["gnt-backup", "export", instance["name"]], fail=True)
507 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
508 def TestInstanceImport(newinst, node, expnode, name):
509 """gnt-backup import"""
510 cmd = (["gnt-backup", "import",
511 "--disk-template=plain",
513 "--src-node=%s" % expnode["primary"],
514 "--src-dir=%s/%s" % (constants.EXPORT_DIR, name),
515 "--node=%s" % node["primary"]] +
516 _GetGenericAddParameters(newinst, force_mac=constants.VALUE_GENERATE))
517 cmd.append(newinst["name"])
521 def TestBackupList(expnode):
522 """gnt-backup list"""
523 AssertCommand(["gnt-backup", "list", "--node=%s" % expnode["primary"]])
525 qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
526 namefield=None, test_unknown=False)
529 def TestBackupListFields():
530 """gnt-backup list-fields"""
531 qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
534 def _TestInstanceDiskFailure(instance, node, node2, onmaster):
535 """Testing disk failure."""
536 master = qa_config.GetMasterNode()
537 sq = utils.ShellQuoteArgs
539 instance_full = qa_utils.ResolveInstanceName(instance["name"])
540 node_full = qa_utils.ResolveNodeName(node)
541 node2_full = qa_utils.ResolveNodeName(node2)
543 print qa_utils.FormatInfo("Getting physical disk names")
544 cmd = ["gnt-node", "volumes", "--separator=|", "--no-headers",
545 "--output=node,phys,instance",
546 node["primary"], node2["primary"]]
547 output = qa_utils.GetCommandOutput(master["primary"], sq(cmd))
549 # Get physical disk names
550 re_disk = re.compile(r"^/dev/([a-z]+)\d+$")
552 for line in output.splitlines():
553 (node_name, phys, inst) = line.split("|")
554 if inst == instance_full:
555 if node_name not in node2disk:
556 node2disk[node_name] = []
558 m = re_disk.match(phys)
560 raise qa_error.Error("Unknown disk name format: %s" % phys)
563 if name not in node2disk[node_name]:
564 node2disk[node_name].append(name)
566 if [node2_full, node_full][int(onmaster)] not in node2disk:
567 raise qa_error.Error("Couldn't find physical disks used on"
568 " %s node" % ["secondary", "master"][int(onmaster)])
570 print qa_utils.FormatInfo("Checking whether nodes have ability to stop"
572 for node_name, disks in node2disk.iteritems():
575 cmds.append(sq(["test", "-f", _GetDiskStatePath(disk)]))
576 AssertCommand(" && ".join(cmds), node=node_name)
578 print qa_utils.FormatInfo("Getting device paths")
579 cmd = ["gnt-instance", "activate-disks", instance["name"]]
580 output = qa_utils.GetCommandOutput(master["primary"], sq(cmd))
582 for line in output.splitlines():
583 (_, _, tmpdevpath) = line.split(":")
584 devpath.append(tmpdevpath)
587 print qa_utils.FormatInfo("Getting drbd device paths")
588 cmd = ["gnt-instance", "info", instance["name"]]
589 output = qa_utils.GetCommandOutput(master["primary"], sq(cmd))
590 pattern = (r"\s+-\s+sd[a-z]+,\s+type:\s+drbd8?,\s+.*$"
591 r"\s+primary:\s+(/dev/drbd\d+)\s+")
592 drbddevs = re.findall(pattern, output, re.M)
597 print qa_utils.FormatInfo("Deactivating disks")
599 for name in node2disk[[node2_full, node_full][int(onmaster)]]:
600 halted_disks.append(name)
601 cmds.append(sq(["echo", "offline"]) + " >%s" % _GetDiskStatePath(name))
602 AssertCommand(" && ".join(cmds), node=[node2, node][int(onmaster)])
604 print qa_utils.FormatInfo("Write to disks and give some time to notice"
608 cmds.append(sq(["dd", "count=1", "bs=512", "conv=notrunc",
609 "if=%s" % disk, "of=%s" % disk]))
611 AssertCommand(" && ".join(cmds), node=node)
614 print qa_utils.FormatInfo("Debugging info")
615 for name in drbddevs:
616 AssertCommand(["drbdsetup", name, "show"], node=node)
618 AssertCommand(["gnt-instance", "info", instance["name"]])
621 print qa_utils.FormatInfo("Activating disks again")
623 for name in halted_disks:
624 cmds.append(sq(["echo", "running"]) + " >%s" % _GetDiskStatePath(name))
625 AssertCommand("; ".join(cmds), node=[node2, node][int(onmaster)])
628 for name in drbddevs:
629 AssertCommand(["drbdsetup", name, "detach"], node=node)
631 for name in drbddevs:
632 AssertCommand(["drbdsetup", name, "disconnect"], node=node2)
635 #AssertCommand(["vgs"], [node2, node][int(onmaster)])
637 print qa_utils.FormatInfo("Making sure disks are up again")
638 AssertCommand(["gnt-instance", "replace-disks", instance["name"]])
640 print qa_utils.FormatInfo("Restarting instance")
641 AssertCommand(["gnt-instance", "shutdown", instance["name"]])
642 AssertCommand(["gnt-instance", "startup", instance["name"]])
644 AssertCommand(["gnt-cluster", "verify"])
647 def TestInstanceMasterDiskFailure(instance, node, node2):
648 """Testing disk failure on master node."""
649 # pylint: disable=W0613
651 print qa_utils.FormatError("Disk failure on primary node cannot be"
652 " tested due to potential crashes.")
653 # The following can cause crashes, thus it's disabled until fixed
654 #return _TestInstanceDiskFailure(instance, node, node2, True)
657 def TestInstanceSecondaryDiskFailure(instance, node, node2):
658 """Testing disk failure on secondary node."""
659 return _TestInstanceDiskFailure(instance, node, node2, False)