qa_utils: Support virtual cluster for backup files
[ganeti-local] / qa / qa_instance.py
1 #
2 #
3
4 # Copyright (C) 2007, 2011, 2012, 2013 Google Inc.
5 #
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.
10 #
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.
15 #
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
19 # 02110-1301, USA.
20
21
22 """Instance related QA tests.
23
24 """
25
26 import operator
27 import re
28
29 from ganeti import utils
30 from ganeti import constants
31 from ganeti import query
32 from ganeti import pathutils
33
34 import qa_config
35 import qa_utils
36 import qa_error
37
38 from qa_utils import AssertIn, AssertCommand, AssertEqual
39 from qa_utils import InstanceCheck, INST_DOWN, INST_UP, FIRST_ARG, RETURN_VALUE
40
41
42 def _GetDiskStatePath(disk):
43   return "/sys/block/%s/device/state" % disk
44
45
46 def _GetGenericAddParameters(inst, force_mac=None):
47   params = ["-B"]
48   params.append("%s=%s,%s=%s" % (constants.BE_MINMEM,
49                                  qa_config.get(constants.BE_MINMEM),
50                                  constants.BE_MAXMEM,
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)])
54
55   # Set static MAC address if configured
56   if force_mac:
57     nic0_mac = force_mac
58   else:
59     nic0_mac = inst.GetNicMacAddr(0, None)
60
61   if nic0_mac:
62     params.extend(["--net", "0:mac=%s" % nic0_mac])
63
64   return params
65
66
67 def _DiskTest(node, disk_template):
68   instance = qa_config.AcquireInstance()
69   try:
70     cmd = (["gnt-instance", "add",
71             "--os-type=%s" % qa_config.get("os"),
72             "--disk-template=%s" % disk_template,
73             "--node=%s" % node] +
74            _GetGenericAddParameters(instance))
75     cmd.append(instance.name)
76
77     AssertCommand(cmd)
78
79     _CheckSsconfInstanceList(instance.name)
80     instance.SetDiskTemplate(disk_template)
81
82     return instance
83   except:
84     instance.Release()
85     raise
86
87
88 def _GetInstanceInfo(instance):
89   """Return information about the actual state of an instance.
90
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)
99
100   """
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.:
107   #  node1.fqdn
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]+)$")
114   nodes = []
115   vols = []
116   drbd_min = {}
117   for line in info_out.splitlines():
118     m = re_node.match(line)
119     if m:
120       nodestr = m.group(1)
121       m2 = re_nodelist.match(nodestr)
122       if m2:
123         nodes.extend(filter(None, m2.groups()))
124       else:
125         nodes.append(nodestr)
126     m = re_vol.match(line)
127     if m:
128       vols.append(m.group(1))
129     m = re_drbdnode.match(line)
130     if m:
131       node = m.group(1)
132       minor = int(m.group(2))
133       if drbd_min.get(node) is not None:
134         drbd_min[node].append(minor)
135       else:
136         drbd_min[node] = [minor]
137   assert vols
138   assert nodes
139   return {"nodes": nodes, "volumes": vols, "drbd-minors": drbd_min}
140
141
142 def _DestroyInstanceVolumes(instance):
143   """Remove all the LVM volumes of an instance.
144
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
149
150   """
151   info = _GetInstanceInfo(instance.name)
152   vols = info["volumes"]
153   for node in info["nodes"]:
154     AssertCommand(["lvremove", "-f"] + vols, node=node)
155
156
157 def _GetBoolInstanceField(instance, field):
158   """Get the Boolean value of a field of an instance.
159
160   @type instance: string
161   @param instance: Instance name
162   @type field: string
163   @param field: Name of the field
164
165   """
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()
170   if info_out == "Y":
171     return True
172   elif info_out == "N":
173     return False
174   else:
175     raise qa_error.Error("Field %s of instance %s has a non-Boolean value:"
176                          " %s" % (field, instance, info_out))
177
178
179 def IsFailoverSupported(instance):
180   return instance.disk_template in constants.DTS_MIRRORED
181
182
183 def IsMigrationSupported(instance):
184   return instance.disk_template in constants.DTS_MIRRORED
185
186
187 def IsDiskReplacingSupported(instance):
188   return instance.disk_template == constants.DT_DRBD8
189
190
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)
196
197
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)),
203                    constants.DT_DRBD8)
204
205
206 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
207 def TestInstanceRemove(instance):
208   """gnt-instance remove"""
209   AssertCommand(["gnt-instance", "remove", "-f", instance.name])
210
211
212 @InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
213 def TestInstanceStartup(instance):
214   """gnt-instance startup"""
215   AssertCommand(["gnt-instance", "startup", instance.name])
216
217
218 @InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
219 def TestInstanceShutdown(instance):
220   """gnt-instance shutdown"""
221   AssertCommand(["gnt-instance", "shutdown", instance.name])
222
223
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)
229   name = instance.name
230   for rtype in reboot_types:
231     AssertCommand(["gnt-instance", "reboot", "--type=%s" % rtype, name])
232
233   AssertCommand(["gnt-instance", "shutdown", name])
234   qa_utils.RunInstanceCheck(instance, False)
235   AssertCommand(["gnt-instance", "reboot", name])
236
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)
242
243
244 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
245 def TestInstanceReinstall(instance):
246   """gnt-instance reinstall"""
247   AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
248
249   # Test with non-existant OS definition
250   AssertCommand(["gnt-instance", "reinstall", "-f",
251                  "--os-type=NonExistantOsForQa",
252                  instance.name],
253                 fail=True)
254
255
256 def _ReadSsconfInstanceList():
257   """Reads ssconf_instance_list from the master node.
258
259   """
260   master = qa_config.GetMasterNode()
261
262   cmd = ["cat", utils.PathJoin(pathutils.DATA_DIR,
263                                "ssconf_%s" % constants.SS_INSTANCE_LIST)]
264
265   return qa_utils.GetCommandOutput(master.primary,
266                                    utils.ShellQuoteArgs(cmd)).splitlines()
267
268
269 def _CheckSsconfInstanceList(instance):
270   """Checks if a certain instance is in the ssconf instance list.
271
272   @type instance: string
273   @param instance: Instance name
274
275   """
276   AssertIn(qa_utils.ResolveInstanceName(instance),
277            _ReadSsconfInstanceList())
278
279
280 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
281 def TestInstanceRenameAndBack(rename_source, rename_target):
282   """gnt-instance rename
283
284   This must leave the instance with the original name, not the target
285   name.
286
287   """
288   _CheckSsconfInstanceList(rename_source)
289
290   # first do a rename to a different actual name, expecting it to fail
291   qa_utils.AddToEtcHosts(["meeeeh-not-exists", rename_target])
292   try:
293     AssertCommand(["gnt-instance", "rename", rename_source, rename_target],
294                   fail=True)
295     _CheckSsconfInstanceList(rename_source)
296   finally:
297     qa_utils.RemoveFromEtcHosts(["meeeeh-not-exists", rename_target])
298
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"]), ))
304
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)
310
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)
317
318   # and back
319   AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
320   _CheckSsconfInstanceList(rename_source)
321   qa_utils.RunInstanceCheck(rename_target, False)
322
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)
327
328
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"
334                               " test")
335     return
336
337   cmd = ["gnt-instance", "failover", "--force", instance.name]
338
339   # failover ...
340   AssertCommand(cmd)
341   qa_utils.RunInstanceCheck(instance, True)
342
343   # ... and back
344   AssertCommand(cmd)
345
346
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"
352                               " test")
353     return
354
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)
359
360   # migrate ...
361   AssertCommand(cmd)
362   # TODO: Verify the choice between failover and migration
363   qa_utils.RunInstanceCheck(instance, True)
364
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)),
369                    instance.name])
370   AssertCommand(cmd)
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])
376
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",
382                  instance.name])
383   AssertCommand(["gnt-instance", "start", instance.name])
384   AssertCommand(cmd)
385   # @InstanceCheck enforces the check that the instance is running
386   qa_utils.RunInstanceCheck(instance, True)
387
388   AssertCommand(["gnt-instance", "modify", "-B",
389                  ("%s=%s" %
390                   (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)),
391                  instance.name])
392
393   AssertCommand(cmd)
394   qa_utils.RunInstanceCheck(instance, True)
395   # TODO: Verify that a failover has been done instead of a migration
396
397   # TODO: Verify whether the default value is restored here (not hardcoded)
398   AssertCommand(["gnt-instance", "modify", "-B",
399                  ("%s=%s" %
400                   (constants.BE_ALWAYS_FAILOVER, constants.VALUE_FALSE)),
401                  instance.name])
402
403   AssertCommand(cmd)
404   qa_utils.RunInstanceCheck(instance, True)
405
406
407 def TestInstanceInfo(instance):
408   """gnt-instance info"""
409   AssertCommand(["gnt-instance", "info", instance.name])
410
411
412 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
413 def TestInstanceModify(instance):
414   """gnt-instance modify"""
415   default_hv = qa_config.GetDefaultHypervisor()
416
417   # Assume /sbin/init exists on all systems
418   test_kernel = "/sbin/init"
419   test_initrd = test_kernel
420
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")
424
425   args = [
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)],
435
436     ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, test_kernel)],
437     ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, constants.VALUE_DEFAULT)],
438
439     # TODO: bridge tests
440     #["--bridge", "xen-br1"],
441     #["--bridge", orig_bridge],
442     ]
443
444   if default_hv == constants.HT_XEN_PVM:
445     args.extend([
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)],
449       ])
450   elif default_hv == constants.HT_XEN_HVM:
451     args.extend([
452       ["-H", "%s=acn" % constants.HV_BOOT_ORDER],
453       ["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
454       ])
455
456   for alist in args:
457     AssertCommand(["gnt-instance", "modify"] + alist + [instance.name])
458
459   # check no-modify
460   AssertCommand(["gnt-instance", "modify", instance.name], fail=True)
461
462   # Marking offline while instance is running must fail...
463   AssertCommand(["gnt-instance", "modify", "--offline", instance.name],
464                  fail=True)
465
466   # ...while making it online is ok, and should work
467   AssertCommand(["gnt-instance", "modify", "--online", instance.name])
468
469
470 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
471 def TestInstanceStoppedModify(instance):
472   """gnt-instance modify (stopped instance)"""
473   name = instance.name
474
475   # Instance was not marked offline; try marking it online once more
476   AssertCommand(["gnt-instance", "modify", "--online", name])
477
478   # Mark instance as offline
479   AssertCommand(["gnt-instance", "modify", "--offline", name])
480
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)
487
488   # Also do offline to offline
489   AssertCommand(["gnt-instance", "modify", "--offline", name])
490
491   # And online again
492   AssertCommand(["gnt-instance", "modify", "--online", name])
493
494
495 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
496 def TestInstanceConvertDiskToPlain(instance, inodes):
497   """gnt-instance modify -t"""
498   name = instance.name
499
500   template = instance.disk_template
501   if template != constants.DT_DRBD8:
502     print qa_utils.FormatInfo("Unsupported template %s, skipping conversion"
503                               " test" % template)
504     return
505
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])
510
511
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")
517     return
518   name = instance.name
519   all_size = qa_config.get("disk")
520   all_grow = qa_config.get("disk-growth")
521   if not all_grow:
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),
530                    size], fail=True)
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)])
536
537
538 def TestInstanceList():
539   """gnt-instance list"""
540   qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
541
542
543 def TestInstanceListFields():
544   """gnt-instance list-fields"""
545   qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
546
547
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])
552
553
554 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
555 def TestReplaceDisks(instance, curr_nodes, other_nodes):
556   """gnt-instance replace-disks"""
557   def buildcmd(args):
558     cmd = ["gnt-instance", "replace-disks"]
559     cmd.extend(args)
560     cmd.append(instance.name)
561     return cmd
562
563   if not IsDiskReplacingSupported(instance):
564     print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
565                               " skipping test")
566     return
567
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]
573
574   options = qa_config.get("options", {})
575   use_ialloc = options.get("use-iallocators", True)
576   for data in [
577     ["-p"],
578     ["-s"],
579     # A placeholder; the actual command choice depends on use_ialloc
580     None,
581     # Restore the original secondary
582     ["--new-secondary=%s" % snode.primary],
583     ]:
584     if data is None:
585       if use_ialloc:
586         data = ["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT]
587       else:
588         data = ["--new-secondary=%s" % othernode.primary]
589     AssertCommand(buildcmd(data))
590
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",
596                  instance.name])
597   AssertCommand(buildcmd(["-a"]))
598   AssertCommand(["gnt-instance", "start", instance.name])
599
600
601 def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
602                          destroy=True):
603   """Execute gnt-instance recreate-disks and check the result
604
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
610
611   """
612   if destroy:
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",
620                    instance.name])
621     AssertCommand(["gnt-instance", "deactivate-disks", instance.name])
622
623
624 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
625 def TestRecreateDisks(instance, inodes, othernodes):
626   """gnt-instance recreate-disks
627
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
631
632   """
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)
639   if use_ialloc:
640     _AssertRecreateDisks(["-I", "hail"], instance, fail=True, destroy=False)
641   else:
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)
648   # Move disks away
649   if use_ialloc:
650     _AssertRecreateDisks(["-I", "hail"], instance)
651     # Move disks somewhere else
652     _AssertRecreateDisks(["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT],
653                          instance)
654   else:
655     _AssertRecreateDisks(["-n", other_seq], instance)
656   # Move disks back
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])
661
662
663 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
664 def TestInstanceExport(instance, node):
665   """gnt-backup export -n ..."""
666   name = instance.name
667   AssertCommand(["gnt-backup", "export", "-n", node.primary, name])
668   return qa_utils.ResolveInstanceName(name)
669
670
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])
676
677
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)
682
683
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,
690           "--no-ip-check",
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)
696   AssertCommand(cmd)
697   newinst.SetDiskTemplate(templ)
698
699
700 def TestBackupList(expnode):
701   """gnt-backup list"""
702   AssertCommand(["gnt-backup", "list", "--node=%s" % expnode.primary])
703
704   qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
705                             namefield=None, test_unknown=False)
706
707
708 def TestBackupListFields():
709   """gnt-backup list-fields"""
710   qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
711
712
713 def TestRemoveInstanceOfflineNode(instance, snode, set_offline, set_online):
714   """gtn-instance remove with an off-line node
715
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
720
721   """
722   info = _GetInstanceInfo(instance.name)
723   set_offline(snode)
724   try:
725     TestInstanceRemove(instance)
726   finally:
727     set_online(snode)
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)