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 AssertIn, AssertCommand, AssertEqual
39 from qa_utils import InstanceCheck, INST_DOWN, INST_UP, FIRST_ARG, RETURN_VALUE
42 def _GetDiskStatePath(disk):
43 return "/sys/block/%s/device/state" % disk
46 def _GetGenericAddParameters(inst, force_mac=None):
48 params.append("%s=%s,%s=%s" % (constants.BE_MINMEM,
49 qa_config.get(constants.BE_MINMEM),
51 qa_config.get(constants.BE_MAXMEM)))
52 for idx, size in enumerate(qa_config.get("disk")):
53 params.extend(["--disk", "%s:size=%s" % (idx, size)])
55 # Set static MAC address if configured
59 nic0_mac = inst.GetNicMacAddr(0, None)
62 params.extend(["--net", "0:mac=%s" % nic0_mac])
67 def _DiskTest(node, disk_template):
68 instance = qa_config.AcquireInstance()
70 cmd = (["gnt-instance", "add",
71 "--os-type=%s" % qa_config.get("os"),
72 "--disk-template=%s" % disk_template,
74 _GetGenericAddParameters(instance))
75 cmd.append(instance.name)
79 _CheckSsconfInstanceList(instance.name)
80 instance.SetDiskTemplate(disk_template)
88 def _GetInstanceInfo(instance):
89 """Return information about the actual state of an instance.
91 @type instance: string
92 @param instance: the instance name
93 @return: a dictionary with the following keys:
94 - "nodes": instance nodes, a list of strings
95 - "volumes": instance volume IDs, a list of strings
96 - "drbd-minors": DRBD minors used by the instance, a dictionary where
97 keys are nodes, and values are lists of integers (or an empty
98 dictionary for non-DRBD instances)
101 master = qa_config.GetMasterNode()
102 infocmd = utils.ShellQuoteArgs(["gnt-instance", "info", instance])
103 info_out = qa_utils.GetCommandOutput(master.primary, infocmd)
104 re_node = re.compile(r"^\s+-\s+(?:primary|secondaries):\s+(\S.+)$")
105 node_elem = r"([^,()]+)(?:\s+\([^)]+\))?"
106 # re_nodelist matches a list of nodes returned by gnt-instance info, e.g.:
108 # node2.fqdn,node3.fqdn
109 # node4.fqdn (group mygroup, group UUID 01234567-abcd-0123-4567-0123456789ab)
110 # FIXME This works with no more than 2 secondaries
111 re_nodelist = re.compile(node_elem + "(?:," + node_elem + ")?$")
112 re_vol = re.compile(r"^\s+logical_id:\s+(\S+)$")
113 re_drbdnode = re.compile(r"^\s+node[AB]:\s+([^\s,]+),\s+minor=([0-9]+)$")
117 for line in info_out.splitlines():
118 m = re_node.match(line)
121 m2 = re_nodelist.match(nodestr)
123 nodes.extend(filter(None, m2.groups()))
125 nodes.append(nodestr)
126 m = re_vol.match(line)
128 vols.append(m.group(1))
129 m = re_drbdnode.match(line)
132 minor = int(m.group(2))
133 if drbd_min.get(node) is not None:
134 drbd_min[node].append(minor)
136 drbd_min[node] = [minor]
139 return {"nodes": nodes, "volumes": vols, "drbd-minors": drbd_min}
142 def _DestroyInstanceVolumes(instance):
143 """Remove all the LVM volumes of an instance.
145 This is used to simulate HW errors (dead nodes, broken disks...); the
146 configuration of the instance is not affected.
147 @type instance: dictionary
148 @param instance: the instance
151 info = _GetInstanceInfo(instance.name)
152 vols = info["volumes"]
153 for node in info["nodes"]:
154 AssertCommand(["lvremove", "-f"] + vols, node=node)
157 def _GetBoolInstanceField(instance, field):
158 """Get the Boolean value of a field of an instance.
160 @type instance: string
161 @param instance: Instance name
163 @param field: Name of the field
166 master = qa_config.GetMasterNode()
167 infocmd = utils.ShellQuoteArgs(["gnt-instance", "list", "--no-headers",
168 "-o", field, instance])
169 info_out = qa_utils.GetCommandOutput(master.primary, infocmd).strip()
172 elif info_out == "N":
175 raise qa_error.Error("Field %s of instance %s has a non-Boolean value:"
176 " %s" % (field, instance, info_out))
179 def IsFailoverSupported(instance):
180 return instance.disk_template in constants.DTS_MIRRORED
183 def IsMigrationSupported(instance):
184 return instance.disk_template in constants.DTS_MIRRORED
187 def IsDiskReplacingSupported(instance):
188 return instance.disk_template == constants.DT_DRBD8
191 @InstanceCheck(None, INST_UP, RETURN_VALUE)
192 def TestInstanceAddWithPlainDisk(nodes):
193 """gnt-instance add -t plain"""
194 assert len(nodes) == 1
195 return _DiskTest(nodes[0].primary, constants.DT_PLAIN)
198 @InstanceCheck(None, INST_UP, RETURN_VALUE)
199 def TestInstanceAddWithDrbdDisk(nodes):
200 """gnt-instance add -t drbd"""
201 assert len(nodes) == 2
202 return _DiskTest(":".join(map(operator.attrgetter("primary"), nodes)),
206 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
207 def TestInstanceRemove(instance):
208 """gnt-instance remove"""
209 AssertCommand(["gnt-instance", "remove", "-f", instance.name])
212 @InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
213 def TestInstanceStartup(instance):
214 """gnt-instance startup"""
215 AssertCommand(["gnt-instance", "startup", instance.name])
218 @InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
219 def TestInstanceShutdown(instance):
220 """gnt-instance shutdown"""
221 AssertCommand(["gnt-instance", "shutdown", instance.name])
224 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
225 def TestInstanceReboot(instance):
226 """gnt-instance reboot"""
227 options = qa_config.get("options", {})
228 reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
230 for rtype in reboot_types:
231 AssertCommand(["gnt-instance", "reboot", "--type=%s" % rtype, name])
233 AssertCommand(["gnt-instance", "shutdown", name])
234 qa_utils.RunInstanceCheck(instance, False)
235 AssertCommand(["gnt-instance", "reboot", name])
237 master = qa_config.GetMasterNode()
238 cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", name]
239 result_output = qa_utils.GetCommandOutput(master.primary,
240 utils.ShellQuoteArgs(cmd))
241 AssertEqual(result_output.strip(), constants.INSTST_RUNNING)
244 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
245 def TestInstanceReinstall(instance):
246 """gnt-instance reinstall"""
247 AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
249 # Test with non-existant OS definition
250 AssertCommand(["gnt-instance", "reinstall", "-f",
251 "--os-type=NonExistantOsForQa",
256 def _ReadSsconfInstanceList():
257 """Reads ssconf_instance_list from the master node.
260 master = qa_config.GetMasterNode()
262 cmd = ["cat", utils.PathJoin(pathutils.DATA_DIR,
263 "ssconf_%s" % constants.SS_INSTANCE_LIST)]
265 return qa_utils.GetCommandOutput(master.primary,
266 utils.ShellQuoteArgs(cmd)).splitlines()
269 def _CheckSsconfInstanceList(instance):
270 """Checks if a certain instance is in the ssconf instance list.
272 @type instance: string
273 @param instance: Instance name
276 AssertIn(qa_utils.ResolveInstanceName(instance),
277 _ReadSsconfInstanceList())
280 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
281 def TestInstanceRenameAndBack(rename_source, rename_target):
282 """gnt-instance rename
284 This must leave the instance with the original name, not the target
288 _CheckSsconfInstanceList(rename_source)
290 # first do a rename to a different actual name, expecting it to fail
291 qa_utils.AddToEtcHosts(["meeeeh-not-exists", rename_target])
293 AssertCommand(["gnt-instance", "rename", rename_source, rename_target],
295 _CheckSsconfInstanceList(rename_source)
297 qa_utils.RemoveFromEtcHosts(["meeeeh-not-exists", rename_target])
299 # Check instance volume tags correctly updated
300 # FIXME: this is LVM specific!
301 info = _GetInstanceInfo(rename_source)
302 tags_cmd = ("lvs -o tags --noheadings %s | grep " %
303 (" ".join(info["volumes"]), ))
305 # and now rename instance to rename_target...
306 AssertCommand(["gnt-instance", "rename", rename_source, rename_target])
307 _CheckSsconfInstanceList(rename_target)
308 qa_utils.RunInstanceCheck(rename_source, False)
309 qa_utils.RunInstanceCheck(rename_target, False)
311 # NOTE: tags might not be the exactly as the instance name, due to
312 # charset restrictions; hence the test might be flaky
313 if rename_source != rename_target:
314 for node in info["nodes"]:
315 AssertCommand(tags_cmd + rename_source, node=node, fail=True)
316 AssertCommand(tags_cmd + rename_target, node=node, fail=False)
319 AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
320 _CheckSsconfInstanceList(rename_source)
321 qa_utils.RunInstanceCheck(rename_target, False)
323 if rename_source != rename_target:
324 for node in info["nodes"]:
325 AssertCommand(tags_cmd + rename_source, node=node, fail=False)
326 AssertCommand(tags_cmd + rename_target, node=node, fail=True)
329 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
330 def TestInstanceFailover(instance):
331 """gnt-instance failover"""
332 if not IsFailoverSupported(instance):
333 print qa_utils.FormatInfo("Instance doesn't support failover, skipping"
337 cmd = ["gnt-instance", "failover", "--force", instance.name]
341 qa_utils.RunInstanceCheck(instance, True)
347 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
348 def TestInstanceMigrate(instance, toggle_always_failover=True):
349 """gnt-instance migrate"""
350 if not IsMigrationSupported(instance):
351 print qa_utils.FormatInfo("Instance doesn't support migration, skipping"
355 cmd = ["gnt-instance", "migrate", "--force", instance.name]
356 af_par = constants.BE_ALWAYS_FAILOVER
357 af_field = "be/" + constants.BE_ALWAYS_FAILOVER
358 af_init_val = _GetBoolInstanceField(instance.name, af_field)
362 # TODO: Verify the choice between failover and migration
363 qa_utils.RunInstanceCheck(instance, True)
365 # ... and back (possibly with always_failover toggled)
366 if toggle_always_failover:
367 AssertCommand(["gnt-instance", "modify", "-B",
368 ("%s=%s" % (af_par, not af_init_val)),
371 # TODO: Verify the choice between failover and migration
372 qa_utils.RunInstanceCheck(instance, True)
373 if toggle_always_failover:
374 AssertCommand(["gnt-instance", "modify", "-B",
375 ("%s=%s" % (af_par, af_init_val)), instance.name])
377 # TODO: Split into multiple tests
378 AssertCommand(["gnt-instance", "shutdown", instance.name])
379 qa_utils.RunInstanceCheck(instance, False)
380 AssertCommand(cmd, fail=True)
381 AssertCommand(["gnt-instance", "migrate", "--force", "--allow-failover",
383 AssertCommand(["gnt-instance", "start", instance.name])
385 # @InstanceCheck enforces the check that the instance is running
386 qa_utils.RunInstanceCheck(instance, True)
388 AssertCommand(["gnt-instance", "modify", "-B",
390 (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)),
394 qa_utils.RunInstanceCheck(instance, True)
395 # TODO: Verify that a failover has been done instead of a migration
397 # TODO: Verify whether the default value is restored here (not hardcoded)
398 AssertCommand(["gnt-instance", "modify", "-B",
400 (constants.BE_ALWAYS_FAILOVER, constants.VALUE_FALSE)),
404 qa_utils.RunInstanceCheck(instance, True)
407 def TestInstanceInfo(instance):
408 """gnt-instance info"""
409 AssertCommand(["gnt-instance", "info", instance.name])
412 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
413 def TestInstanceModify(instance):
414 """gnt-instance modify"""
415 default_hv = qa_config.GetDefaultHypervisor()
417 # Assume /sbin/init exists on all systems
418 test_kernel = "/sbin/init"
419 test_initrd = test_kernel
421 orig_maxmem = qa_config.get(constants.BE_MAXMEM)
422 orig_minmem = qa_config.get(constants.BE_MINMEM)
423 #orig_bridge = qa_config.get("bridge", "xen-br0")
426 ["-B", "%s=128" % constants.BE_MINMEM],
427 ["-B", "%s=128" % constants.BE_MAXMEM],
428 ["-B", "%s=%s,%s=%s" % (constants.BE_MINMEM, orig_minmem,
429 constants.BE_MAXMEM, orig_maxmem)],
430 ["-B", "%s=2" % constants.BE_VCPUS],
431 ["-B", "%s=1" % constants.BE_VCPUS],
432 ["-B", "%s=%s" % (constants.BE_VCPUS, constants.VALUE_DEFAULT)],
433 ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)],
434 ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_DEFAULT)],
436 ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, test_kernel)],
437 ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, constants.VALUE_DEFAULT)],
440 #["--bridge", "xen-br1"],
441 #["--bridge", orig_bridge],
444 if default_hv == constants.HT_XEN_PVM:
446 ["-H", "%s=%s" % (constants.HV_INITRD_PATH, test_initrd)],
447 ["-H", "no_%s" % (constants.HV_INITRD_PATH, )],
448 ["-H", "%s=%s" % (constants.HV_INITRD_PATH, constants.VALUE_DEFAULT)],
450 elif default_hv == constants.HT_XEN_HVM:
452 ["-H", "%s=acn" % constants.HV_BOOT_ORDER],
453 ["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
457 AssertCommand(["gnt-instance", "modify"] + alist + [instance.name])
460 AssertCommand(["gnt-instance", "modify", instance.name], fail=True)
462 # Marking offline while instance is running must fail...
463 AssertCommand(["gnt-instance", "modify", "--offline", instance.name],
466 # ...while making it online is ok, and should work
467 AssertCommand(["gnt-instance", "modify", "--online", instance.name])
470 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
471 def TestInstanceStoppedModify(instance):
472 """gnt-instance modify (stopped instance)"""
475 # Instance was not marked offline; try marking it online once more
476 AssertCommand(["gnt-instance", "modify", "--online", name])
478 # Mark instance as offline
479 AssertCommand(["gnt-instance", "modify", "--offline", name])
481 # When the instance is offline shutdown should only work with --force,
482 # while start should never work
483 AssertCommand(["gnt-instance", "shutdown", name], fail=True)
484 AssertCommand(["gnt-instance", "shutdown", "--force", name])
485 AssertCommand(["gnt-instance", "start", name], fail=True)
486 AssertCommand(["gnt-instance", "start", "--force", name], fail=True)
488 # Also do offline to offline
489 AssertCommand(["gnt-instance", "modify", "--offline", name])
492 AssertCommand(["gnt-instance", "modify", "--online", name])
495 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
496 def TestInstanceConvertDiskToPlain(instance, inodes):
497 """gnt-instance modify -t"""
500 template = instance.disk_template
501 if template != constants.DT_DRBD8:
502 print qa_utils.FormatInfo("Unsupported template %s, skipping conversion"
506 assert len(inodes) == 2
507 AssertCommand(["gnt-instance", "modify", "-t", constants.DT_PLAIN, name])
508 AssertCommand(["gnt-instance", "modify", "-t", constants.DT_DRBD8,
509 "-n", inodes[1].primary, name])
512 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
513 def TestInstanceGrowDisk(instance):
514 """gnt-instance grow-disk"""
515 if qa_config.GetExclusiveStorage():
516 print qa_utils.FormatInfo("Test not supported with exclusive_storage")
519 all_size = qa_config.get("disk")
520 all_grow = qa_config.get("disk-growth")
522 # missing disk sizes but instance grow disk has been enabled,
523 # let's set fixed/nomimal growth
524 all_grow = ["128M" for _ in all_size]
525 for idx, (size, grow) in enumerate(zip(all_size, all_grow)):
526 # succeed in grow by amount
527 AssertCommand(["gnt-instance", "grow-disk", name, str(idx), grow])
528 # fail in grow to the old size
529 AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
531 # succeed to grow to old size + 2 * growth
532 int_size = utils.ParseUnit(size)
533 int_grow = utils.ParseUnit(grow)
534 AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
535 str(int_size + 2 * int_grow)])
538 def TestInstanceList():
539 """gnt-instance list"""
540 qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
543 def TestInstanceListFields():
544 """gnt-instance list-fields"""
545 qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
548 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
549 def TestInstanceConsole(instance):
550 """gnt-instance console"""
551 AssertCommand(["gnt-instance", "console", "--show-cmd", instance.name])
554 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
555 def TestReplaceDisks(instance, curr_nodes, other_nodes):
556 """gnt-instance replace-disks"""
558 cmd = ["gnt-instance", "replace-disks"]
560 cmd.append(instance.name)
563 if not IsDiskReplacingSupported(instance):
564 print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
568 # Currently all supported templates have one primary and one secondary node
569 assert len(curr_nodes) == 2
570 snode = curr_nodes[1]
571 assert len(other_nodes) == 1
572 othernode = other_nodes[0]
574 options = qa_config.get("options", {})
575 use_ialloc = options.get("use-iallocators", True)
579 # A placeholder; the actual command choice depends on use_ialloc
581 # Restore the original secondary
582 ["--new-secondary=%s" % snode.primary],
586 data = ["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT]
588 data = ["--new-secondary=%s" % othernode.primary]
589 AssertCommand(buildcmd(data))
591 AssertCommand(buildcmd(["-a"]))
592 AssertCommand(["gnt-instance", "stop", instance.name])
593 AssertCommand(buildcmd(["-a"]), fail=True)
594 AssertCommand(["gnt-instance", "activate-disks", instance.name])
595 AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
597 AssertCommand(buildcmd(["-a"]))
598 AssertCommand(["gnt-instance", "start", instance.name])
601 def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
603 """Execute gnt-instance recreate-disks and check the result
605 @param cmdargs: Arguments (instance name excluded)
606 @param instance: Instance to operate on
607 @param fail: True if the command is expected to fail
608 @param check: If True and fail is False, check that the disks work
609 @prama destroy: If True, destroy the old disks first
613 _DestroyInstanceVolumes(instance)
614 AssertCommand((["gnt-instance", "recreate-disks"] + cmdargs +
615 [instance.name]), fail)
616 if not fail and check:
617 # Quick check that the disks are there
618 AssertCommand(["gnt-instance", "activate-disks", instance.name])
619 AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
621 AssertCommand(["gnt-instance", "deactivate-disks", instance.name])
624 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
625 def TestRecreateDisks(instance, inodes, othernodes):
626 """gnt-instance recreate-disks
628 @param instance: Instance to work on
629 @param inodes: List of the current nodes of the instance
630 @param othernodes: list/tuple of nodes where to temporarily recreate disks
633 options = qa_config.get("options", {})
634 use_ialloc = options.get("use-iallocators", True)
635 other_seq = ":".join([n.primary for n in othernodes])
636 orig_seq = ":".join([n.primary for n in inodes])
637 # These fail because the instance is running
638 _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
640 _AssertRecreateDisks(["-I", "hail"], instance, fail=True, destroy=False)
642 _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
643 AssertCommand(["gnt-instance", "stop", instance.name])
644 # Disks exist: this should fail
645 _AssertRecreateDisks([], instance, fail=True, destroy=False)
646 # Recreate disks in place
647 _AssertRecreateDisks([], instance)
650 _AssertRecreateDisks(["-I", "hail"], instance)
651 # Move disks somewhere else
652 _AssertRecreateDisks(["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT],
655 _AssertRecreateDisks(["-n", other_seq], instance)
657 _AssertRecreateDisks(["-n", orig_seq], instance, check=False)
658 # This and InstanceCheck decoration check that the disks are working
659 AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
660 AssertCommand(["gnt-instance", "start", instance.name])
663 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
664 def TestInstanceExport(instance, node):
665 """gnt-backup export -n ..."""
667 AssertCommand(["gnt-backup", "export", "-n", node.primary, name])
668 return qa_utils.ResolveInstanceName(name)
671 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
672 def TestInstanceExportWithRemove(instance, node):
673 """gnt-backup export --remove-instance"""
674 AssertCommand(["gnt-backup", "export", "-n", node.primary,
675 "--remove-instance", instance.name])
678 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
679 def TestInstanceExportNoTarget(instance):
680 """gnt-backup export (without target node, should fail)"""
681 AssertCommand(["gnt-backup", "export", instance.name], fail=True)
684 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
685 def TestInstanceImport(newinst, node, expnode, name):
686 """gnt-backup import"""
687 templ = constants.DT_PLAIN
688 cmd = (["gnt-backup", "import",
689 "--disk-template=%s" % templ,
691 "--src-node=%s" % expnode.primary,
692 "--src-dir=%s/%s" % (pathutils.EXPORT_DIR, name),
693 "--node=%s" % node.primary] +
694 _GetGenericAddParameters(newinst, force_mac=constants.VALUE_GENERATE))
695 cmd.append(newinst.name)
697 newinst.SetDiskTemplate(templ)
700 def TestBackupList(expnode):
701 """gnt-backup list"""
702 AssertCommand(["gnt-backup", "list", "--node=%s" % expnode.primary])
704 qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
705 namefield=None, test_unknown=False)
708 def TestBackupListFields():
709 """gnt-backup list-fields"""
710 qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
713 def TestRemoveInstanceOfflineNode(instance, snode, set_offline, set_online):
714 """gtn-instance remove with an off-line node
716 @param instance: instance
717 @param snode: secondary node, to be set offline
718 @param set_offline: function to call to set the node off-line
719 @param set_online: function to call to set the node on-line
722 info = _GetInstanceInfo(instance.name)
725 TestInstanceRemove(instance)
728 # Clean up the disks on the offline node
729 for minor in info["drbd-minors"][snode.primary]:
730 AssertCommand(["drbdsetup", str(minor), "down"], node=snode)
731 AssertCommand(["lvremove", "-f"] + info["volumes"], node=snode)