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