QA: Use lists of nodes as argument to instance tests
[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 = qa_config.GetInstanceNicMac(inst)
60   if nic0_mac:
61     params.extend(["--net", "0:mac=%s" % nic0_mac])
62
63   return params
64
65
66 def _DiskTest(node, disk_template):
67   instance = qa_config.AcquireInstance()
68   try:
69     cmd = (["gnt-instance", "add",
70             "--os-type=%s" % qa_config.get("os"),
71             "--disk-template=%s" % disk_template,
72             "--node=%s" % node] +
73            _GetGenericAddParameters(instance))
74     cmd.append(instance["name"])
75
76     AssertCommand(cmd)
77
78     _CheckSsconfInstanceList(instance["name"])
79     qa_config.SetInstanceTemplate(instance, disk_template)
80
81     return instance
82   except:
83     qa_config.ReleaseInstance(instance)
84     raise
85
86
87 def _GetInstanceInfo(instance):
88   """Return information about the actual state of an instance.
89
90   @type instance: string
91   @param instance: the instance name
92   @return: a dictionary with two keys:
93       - "nodes": instance nodes, a list of strings
94       - "volumes": instance volume IDs, a list of strings
95
96   """
97   master = qa_config.GetMasterNode()
98   infocmd = utils.ShellQuoteArgs(["gnt-instance", "info", instance])
99   info_out = qa_utils.GetCommandOutput(master["primary"], infocmd)
100   re_node = re.compile(r"^\s+-\s+(?:primary|secondaries):\s+(\S.+)$")
101   node_elem = r"([^,()]+)(?:\s+\([^)]+\))?"
102   # re_nodelist matches a list of nodes returned by gnt-instance info, e.g.:
103   #  node1.fqdn
104   #  node2.fqdn,node3.fqdn
105   #  node4.fqdn (group mygroup, group UUID 01234567-abcd-0123-4567-0123456789ab)
106   # FIXME This works with no more than 2 secondaries
107   re_nodelist = re.compile(node_elem + "(?:," + node_elem + ")?$")
108   re_vol = re.compile(r"^\s+logical_id:\s+(\S+)$")
109   nodes = []
110   vols = []
111   for line in info_out.splitlines():
112     m = re_node.match(line)
113     if m:
114       nodestr = m.group(1)
115       m2 = re_nodelist.match(nodestr)
116       if m2:
117         nodes.extend(filter(None, m2.groups()))
118       else:
119         nodes.append(nodestr)
120     m = re_vol.match(line)
121     if m:
122       vols.append(m.group(1))
123   assert vols
124   assert nodes
125   return {"nodes": nodes, "volumes": vols}
126
127
128 def _DestroyInstanceVolumes(instance):
129   """Remove all the LVM volumes of an instance.
130
131   This is used to simulate HW errors (dead nodes, broken disks...); the
132   configuration of the instance is not affected.
133   @type instance: dictionary
134   @param instance: the instance
135
136   """
137   info = _GetInstanceInfo(instance["name"])
138   vols = info["volumes"]
139   for node in info["nodes"]:
140     AssertCommand(["lvremove", "-f"] + vols, node=node)
141
142
143 def _GetBoolInstanceField(instance, field):
144   """Get the Boolean value of a field of an instance.
145
146   @type instance: string
147   @param instance: Instance name
148   @type field: string
149   @param field: Name of the field
150
151   """
152   master = qa_config.GetMasterNode()
153   infocmd = utils.ShellQuoteArgs(["gnt-instance", "list", "--no-headers",
154                                   "-o", field, instance])
155   info_out = qa_utils.GetCommandOutput(master["primary"], infocmd).strip()
156   if info_out == "Y":
157     return True
158   elif info_out == "N":
159     return False
160   else:
161     raise qa_error.Error("Field %s of instance %s has a non-Boolean value:"
162                          " %s" % (field, instance, info_out))
163
164
165 def IsFailoverSupported(instance):
166   templ = qa_config.GetInstanceTemplate(instance)
167   return templ in constants.DTS_MIRRORED
168
169
170 def IsMigrationSupported(instance):
171   templ = qa_config.GetInstanceTemplate(instance)
172   return templ in constants.DTS_MIRRORED
173
174
175 def IsDiskReplacingSupported(instance):
176   templ = qa_config.GetInstanceTemplate(instance)
177   return templ == constants.DT_DRBD8
178
179
180 @InstanceCheck(None, INST_UP, RETURN_VALUE)
181 def TestInstanceAddWithPlainDisk(nodes):
182   """gnt-instance add -t plain"""
183   assert len(nodes) == 1
184   return _DiskTest(nodes[0]["primary"], "plain")
185
186
187 @InstanceCheck(None, INST_UP, RETURN_VALUE)
188 def TestInstanceAddWithDrbdDisk(nodes):
189   """gnt-instance add -t drbd"""
190   assert len(nodes) == 2
191   return _DiskTest(":".join(map(operator.itemgetter("primary"), nodes)),
192                    "drbd")
193
194
195 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
196 def TestInstanceRemove(instance):
197   """gnt-instance remove"""
198   AssertCommand(["gnt-instance", "remove", "-f", instance["name"]])
199
200   qa_config.ReleaseInstance(instance)
201
202
203 @InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
204 def TestInstanceStartup(instance):
205   """gnt-instance startup"""
206   AssertCommand(["gnt-instance", "startup", instance["name"]])
207
208
209 @InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
210 def TestInstanceShutdown(instance):
211   """gnt-instance shutdown"""
212   AssertCommand(["gnt-instance", "shutdown", instance["name"]])
213
214
215 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
216 def TestInstanceReboot(instance):
217   """gnt-instance reboot"""
218   options = qa_config.get("options", {})
219   reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
220   name = instance["name"]
221   for rtype in reboot_types:
222     AssertCommand(["gnt-instance", "reboot", "--type=%s" % rtype, name])
223
224   AssertCommand(["gnt-instance", "shutdown", name])
225   qa_utils.RunInstanceCheck(instance, False)
226   AssertCommand(["gnt-instance", "reboot", name])
227
228   master = qa_config.GetMasterNode()
229   cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", name]
230   result_output = qa_utils.GetCommandOutput(master["primary"],
231                                             utils.ShellQuoteArgs(cmd))
232   AssertEqual(result_output.strip(), constants.INSTST_RUNNING)
233
234
235 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
236 def TestInstanceReinstall(instance):
237   """gnt-instance reinstall"""
238   AssertCommand(["gnt-instance", "reinstall", "-f", instance["name"]])
239
240   # Test with non-existant OS definition
241   AssertCommand(["gnt-instance", "reinstall", "-f",
242                  "--os-type=NonExistantOsForQa",
243                  instance["name"]],
244                 fail=True)
245
246
247 def _ReadSsconfInstanceList():
248   """Reads ssconf_instance_list from the master node.
249
250   """
251   master = qa_config.GetMasterNode()
252
253   cmd = ["cat", utils.PathJoin(pathutils.DATA_DIR,
254                                "ssconf_%s" % constants.SS_INSTANCE_LIST)]
255
256   return qa_utils.GetCommandOutput(master["primary"],
257                                    utils.ShellQuoteArgs(cmd)).splitlines()
258
259
260 def _CheckSsconfInstanceList(instance):
261   """Checks if a certain instance is in the ssconf instance list.
262
263   @type instance: string
264   @param instance: Instance name
265
266   """
267   AssertIn(qa_utils.ResolveInstanceName(instance),
268            _ReadSsconfInstanceList())
269
270
271 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
272 def TestInstanceRenameAndBack(rename_source, rename_target):
273   """gnt-instance rename
274
275   This must leave the instance with the original name, not the target
276   name.
277
278   """
279   _CheckSsconfInstanceList(rename_source)
280
281   # first do a rename to a different actual name, expecting it to fail
282   qa_utils.AddToEtcHosts(["meeeeh-not-exists", rename_target])
283   try:
284     AssertCommand(["gnt-instance", "rename", rename_source, rename_target],
285                   fail=True)
286     _CheckSsconfInstanceList(rename_source)
287   finally:
288     qa_utils.RemoveFromEtcHosts(["meeeeh-not-exists", rename_target])
289
290   # Check instance volume tags correctly updated
291   # FIXME: this is LVM specific!
292   info = _GetInstanceInfo(rename_source)
293   tags_cmd = ("lvs -o tags --noheadings %s | grep " %
294               (" ".join(info["volumes"]), ))
295
296   # and now rename instance to rename_target...
297   AssertCommand(["gnt-instance", "rename", rename_source, rename_target])
298   _CheckSsconfInstanceList(rename_target)
299   qa_utils.RunInstanceCheck(rename_source, False)
300   qa_utils.RunInstanceCheck(rename_target, False)
301
302   # NOTE: tags might not be the exactly as the instance name, due to
303   # charset restrictions; hence the test might be flaky
304   if rename_source != rename_target:
305     for node in info["nodes"]:
306       AssertCommand(tags_cmd + rename_source, node=node, fail=True)
307       AssertCommand(tags_cmd + rename_target, node=node, fail=False)
308
309   # and back
310   AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
311   _CheckSsconfInstanceList(rename_source)
312   qa_utils.RunInstanceCheck(rename_target, False)
313
314   if rename_source != rename_target:
315     for node in info["nodes"]:
316       AssertCommand(tags_cmd + rename_source, node=node, fail=False)
317       AssertCommand(tags_cmd + rename_target, node=node, fail=True)
318
319
320 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
321 def TestInstanceFailover(instance):
322   """gnt-instance failover"""
323   if not IsFailoverSupported(instance):
324     print qa_utils.FormatInfo("Instance doesn't support failover, skipping"
325                               " test")
326     return
327
328   cmd = ["gnt-instance", "failover", "--force", instance["name"]]
329
330   # failover ...
331   AssertCommand(cmd)
332   qa_utils.RunInstanceCheck(instance, True)
333
334   # ... and back
335   AssertCommand(cmd)
336
337
338 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
339 def TestInstanceMigrate(instance, toggle_always_failover=True):
340   """gnt-instance migrate"""
341   if not IsMigrationSupported(instance):
342     print qa_utils.FormatInfo("Instance doesn't support migration, skipping"
343                               " test")
344     return
345
346   cmd = ["gnt-instance", "migrate", "--force", instance["name"]]
347   af_par = constants.BE_ALWAYS_FAILOVER
348   af_field = "be/" + constants.BE_ALWAYS_FAILOVER
349   af_init_val = _GetBoolInstanceField(instance["name"], af_field)
350
351   # migrate ...
352   AssertCommand(cmd)
353   # TODO: Verify the choice between failover and migration
354   qa_utils.RunInstanceCheck(instance, True)
355
356   # ... and back (possibly with always_failover toggled)
357   if toggle_always_failover:
358     AssertCommand(["gnt-instance", "modify", "-B",
359                    ("%s=%s" % (af_par, not af_init_val)),
360                    instance["name"]])
361   AssertCommand(cmd)
362   # TODO: Verify the choice between failover and migration
363   qa_utils.RunInstanceCheck(instance, True)
364   if toggle_always_failover:
365     AssertCommand(["gnt-instance", "modify", "-B",
366                    ("%s=%s" % (af_par, af_init_val)), instance["name"]])
367
368   # TODO: Split into multiple tests
369   AssertCommand(["gnt-instance", "shutdown", instance["name"]])
370   qa_utils.RunInstanceCheck(instance, False)
371   AssertCommand(cmd, fail=True)
372   AssertCommand(["gnt-instance", "migrate", "--force", "--allow-failover",
373                  instance["name"]])
374   AssertCommand(["gnt-instance", "start", instance["name"]])
375   AssertCommand(cmd)
376   # @InstanceCheck enforces the check that the instance is running
377   qa_utils.RunInstanceCheck(instance, True)
378
379   AssertCommand(["gnt-instance", "modify", "-B",
380                  ("%s=%s" %
381                   (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)),
382                  instance["name"]])
383
384   AssertCommand(cmd)
385   qa_utils.RunInstanceCheck(instance, True)
386   # TODO: Verify that a failover has been done instead of a migration
387
388   # TODO: Verify whether the default value is restored here (not hardcoded)
389   AssertCommand(["gnt-instance", "modify", "-B",
390                  ("%s=%s" %
391                   (constants.BE_ALWAYS_FAILOVER, constants.VALUE_FALSE)),
392                  instance["name"]])
393
394   AssertCommand(cmd)
395   qa_utils.RunInstanceCheck(instance, True)
396
397
398 def TestInstanceInfo(instance):
399   """gnt-instance info"""
400   AssertCommand(["gnt-instance", "info", instance["name"]])
401
402
403 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
404 def TestInstanceModify(instance):
405   """gnt-instance modify"""
406   default_hv = qa_config.GetDefaultHypervisor()
407
408   # Assume /sbin/init exists on all systems
409   test_kernel = "/sbin/init"
410   test_initrd = test_kernel
411
412   orig_maxmem = qa_config.get(constants.BE_MAXMEM)
413   orig_minmem = qa_config.get(constants.BE_MINMEM)
414   #orig_bridge = qa_config.get("bridge", "xen-br0")
415
416   args = [
417     ["-B", "%s=128" % constants.BE_MINMEM],
418     ["-B", "%s=128" % constants.BE_MAXMEM],
419     ["-B", "%s=%s,%s=%s" % (constants.BE_MINMEM, orig_minmem,
420                             constants.BE_MAXMEM, orig_maxmem)],
421     ["-B", "%s=2" % constants.BE_VCPUS],
422     ["-B", "%s=1" % constants.BE_VCPUS],
423     ["-B", "%s=%s" % (constants.BE_VCPUS, constants.VALUE_DEFAULT)],
424     ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)],
425     ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_DEFAULT)],
426
427     ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, test_kernel)],
428     ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, constants.VALUE_DEFAULT)],
429
430     # TODO: bridge tests
431     #["--bridge", "xen-br1"],
432     #["--bridge", orig_bridge],
433     ]
434
435   if default_hv == constants.HT_XEN_PVM:
436     args.extend([
437       ["-H", "%s=%s" % (constants.HV_INITRD_PATH, test_initrd)],
438       ["-H", "no_%s" % (constants.HV_INITRD_PATH, )],
439       ["-H", "%s=%s" % (constants.HV_INITRD_PATH, constants.VALUE_DEFAULT)],
440       ])
441   elif default_hv == constants.HT_XEN_HVM:
442     args.extend([
443       ["-H", "%s=acn" % constants.HV_BOOT_ORDER],
444       ["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
445       ])
446
447   for alist in args:
448     AssertCommand(["gnt-instance", "modify"] + alist + [instance["name"]])
449
450   # check no-modify
451   AssertCommand(["gnt-instance", "modify", instance["name"]], fail=True)
452
453   # Marking offline while instance is running must fail...
454   AssertCommand(["gnt-instance", "modify", "--offline", instance["name"]],
455                  fail=True)
456
457   # ...while making it online is ok, and should work
458   AssertCommand(["gnt-instance", "modify", "--online", instance["name"]])
459
460
461 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
462 def TestInstanceStoppedModify(instance):
463   """gnt-instance modify (stopped instance)"""
464   name = instance["name"]
465
466   # Instance was not marked offline; try marking it online once more
467   AssertCommand(["gnt-instance", "modify", "--online", name])
468
469   # Mark instance as offline
470   AssertCommand(["gnt-instance", "modify", "--offline", name])
471
472   # When the instance is offline shutdown should only work with --force,
473   # while start should never work
474   AssertCommand(["gnt-instance", "shutdown", name], fail=True)
475   AssertCommand(["gnt-instance", "shutdown", "--force", name])
476   AssertCommand(["gnt-instance", "start", name], fail=True)
477   AssertCommand(["gnt-instance", "start", "--force", name], fail=True)
478
479   # Also do offline to offline
480   AssertCommand(["gnt-instance", "modify", "--offline", name])
481
482   # And online again
483   AssertCommand(["gnt-instance", "modify", "--online", name])
484
485
486 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
487 def TestInstanceConvertDiskToPlain(instance, inodes):
488   """gnt-instance modify -t"""
489   name = instance["name"]
490   template = qa_config.GetInstanceTemplate(instance)
491   if template != "drbd":
492     print qa_utils.FormatInfo("Unsupported template %s, skipping conversion"
493                               " test" % template)
494     return
495   assert len(inodes) == 2
496   AssertCommand(["gnt-instance", "modify", "-t", "plain", name])
497   AssertCommand(["gnt-instance", "modify", "-t", "drbd",
498                  "-n", inodes[1]["primary"], name])
499
500
501 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
502 def TestInstanceGrowDisk(instance):
503   """gnt-instance grow-disk"""
504   if qa_config.GetExclusiveStorage():
505     print qa_utils.FormatInfo("Test not supported with exclusive_storage")
506     return
507   name = instance["name"]
508   all_size = qa_config.get("disk")
509   all_grow = qa_config.get("disk-growth")
510   if not all_grow:
511     # missing disk sizes but instance grow disk has been enabled,
512     # let's set fixed/nomimal growth
513     all_grow = ["128M" for _ in all_size]
514   for idx, (size, grow) in enumerate(zip(all_size, all_grow)):
515     # succeed in grow by amount
516     AssertCommand(["gnt-instance", "grow-disk", name, str(idx), grow])
517     # fail in grow to the old size
518     AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
519                    size], fail=True)
520     # succeed to grow to old size + 2 * growth
521     int_size = utils.ParseUnit(size)
522     int_grow = utils.ParseUnit(grow)
523     AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
524                    str(int_size + 2 * int_grow)])
525
526
527 def TestInstanceList():
528   """gnt-instance list"""
529   qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
530
531
532 def TestInstanceListFields():
533   """gnt-instance list-fields"""
534   qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
535
536
537 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
538 def TestInstanceConsole(instance):
539   """gnt-instance console"""
540   AssertCommand(["gnt-instance", "console", "--show-cmd", instance["name"]])
541
542
543 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
544 def TestReplaceDisks(instance, curr_nodes, other_nodes):
545   """gnt-instance replace-disks"""
546   def buildcmd(args):
547     cmd = ["gnt-instance", "replace-disks"]
548     cmd.extend(args)
549     cmd.append(instance["name"])
550     return cmd
551
552   if not IsDiskReplacingSupported(instance):
553     print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
554                               " skipping test")
555     return
556
557   # Currently all supported templates have one primary and one secondary node
558   assert len(curr_nodes) == 2
559   snode = curr_nodes[1]
560   assert len(other_nodes) == 1
561   othernode = other_nodes[0]
562
563   options = qa_config.get("options", {})
564   use_ialloc = options.get("use-iallocators", True)
565   for data in [
566     ["-p"],
567     ["-s"],
568     # A placeholder; the actual command choice depends on use_ialloc
569     None,
570     # Restore the original secondary
571     ["--new-secondary=%s" % snode["primary"]],
572     ]:
573     if data is None:
574       if use_ialloc:
575         data = ["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT]
576       else:
577         data = ["--new-secondary=%s" % othernode["primary"]]
578     AssertCommand(buildcmd(data))
579
580   AssertCommand(buildcmd(["-a"]))
581   AssertCommand(["gnt-instance", "stop", instance["name"]])
582   AssertCommand(buildcmd(["-a"]), fail=True)
583   AssertCommand(["gnt-instance", "activate-disks", instance["name"]])
584   AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
585                  instance["name"]])
586   AssertCommand(buildcmd(["-a"]))
587   AssertCommand(["gnt-instance", "start", instance["name"]])
588
589
590 def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
591                          destroy=True):
592   """Execute gnt-instance recreate-disks and check the result
593
594   @param cmdargs: Arguments (instance name excluded)
595   @param instance: Instance to operate on
596   @param fail: True if the command is expected to fail
597   @param check: If True and fail is False, check that the disks work
598   @prama destroy: If True, destroy the old disks first
599
600   """
601   if destroy:
602     _DestroyInstanceVolumes(instance)
603   AssertCommand((["gnt-instance", "recreate-disks"] + cmdargs +
604                  [instance["name"]]), fail)
605   if not fail and check:
606     # Quick check that the disks are there
607     AssertCommand(["gnt-instance", "activate-disks", instance["name"]])
608     AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
609                    instance["name"]])
610     AssertCommand(["gnt-instance", "deactivate-disks", instance["name"]])
611
612
613 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
614 def TestRecreateDisks(instance, inodes, othernodes):
615   """gnt-instance recreate-disks
616
617   @param instance: Instance to work on
618   @param inodes: List of the current nodes of the instance
619   @param othernodes: list/tuple of nodes where to temporarily recreate disks
620
621   """
622   options = qa_config.get("options", {})
623   use_ialloc = options.get("use-iallocators", True)
624   other_seq = ":".join([n["primary"] for n in othernodes])
625   orig_seq = ":".join([n["primary"] for n in inodes])
626   # These fail because the instance is running
627   _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
628   if use_ialloc:
629     _AssertRecreateDisks(["-I", "hail"], instance, fail=True, destroy=False)
630   else:
631     _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
632   AssertCommand(["gnt-instance", "stop", instance["name"]])
633   # Disks exist: this should fail
634   _AssertRecreateDisks([], instance, fail=True, destroy=False)
635   # Recreate disks in place
636   _AssertRecreateDisks([], instance)
637   # Move disks away
638   if use_ialloc:
639     _AssertRecreateDisks(["-I", "hail"], instance)
640     # Move disks somewhere else
641     _AssertRecreateDisks(["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT],
642                          instance)
643   else:
644     _AssertRecreateDisks(["-n", other_seq], instance)
645   # Move disks back
646   _AssertRecreateDisks(["-n", orig_seq], instance, check=False)
647   # This and InstanceCheck decoration check that the disks are working
648   AssertCommand(["gnt-instance", "reinstall", "-f", instance["name"]])
649   AssertCommand(["gnt-instance", "start", instance["name"]])
650
651
652 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
653 def TestInstanceExport(instance, node):
654   """gnt-backup export -n ..."""
655   name = instance["name"]
656   AssertCommand(["gnt-backup", "export", "-n", node["primary"], name])
657   return qa_utils.ResolveInstanceName(name)
658
659
660 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
661 def TestInstanceExportWithRemove(instance, node):
662   """gnt-backup export --remove-instance"""
663   AssertCommand(["gnt-backup", "export", "-n", node["primary"],
664                  "--remove-instance", instance["name"]])
665   qa_config.ReleaseInstance(instance)
666
667
668 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
669 def TestInstanceExportNoTarget(instance):
670   """gnt-backup export (without target node, should fail)"""
671   AssertCommand(["gnt-backup", "export", instance["name"]], fail=True)
672
673
674 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
675 def TestInstanceImport(newinst, node, expnode, name):
676   """gnt-backup import"""
677   templ = constants.DT_PLAIN
678   cmd = (["gnt-backup", "import",
679           "--disk-template=%s" % templ,
680           "--no-ip-check",
681           "--src-node=%s" % expnode["primary"],
682           "--src-dir=%s/%s" % (pathutils.EXPORT_DIR, name),
683           "--node=%s" % node["primary"]] +
684          _GetGenericAddParameters(newinst, force_mac=constants.VALUE_GENERATE))
685   cmd.append(newinst["name"])
686   AssertCommand(cmd)
687   qa_config.SetInstanceTemplate(newinst, templ)
688
689
690 def TestBackupList(expnode):
691   """gnt-backup list"""
692   AssertCommand(["gnt-backup", "list", "--node=%s" % expnode["primary"]])
693
694   qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
695                             namefield=None, test_unknown=False)
696
697
698 def TestBackupListFields():
699   """gnt-backup list-fields"""
700   qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())