Update the monitoring agent design document
[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 while instance is running must fail...
426   AssertCommand(["gnt-instance", "modify", "--offline", instance["name"]],
427                  fail=True)
428
429   # ...while making it online is ok, and should work
430   AssertCommand(["gnt-instance", "modify", "--online", instance["name"]])
431
432
433 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
434 def TestInstanceStoppedModify(instance):
435   """gnt-instance modify (stopped instance)"""
436   name = instance["name"]
437
438   # Instance was not marked offline; try marking it online once more
439   AssertCommand(["gnt-instance", "modify", "--online", name])
440
441   # Mark instance as offline
442   AssertCommand(["gnt-instance", "modify", "--offline", name])
443
444   # When the instance is offline shutdown should only work with --force,
445   # while start should never work
446   AssertCommand(["gnt-instance", "shutdown", name], fail=True)
447   AssertCommand(["gnt-instance", "shutdown", "--force", name])
448   AssertCommand(["gnt-instance", "start", name], fail=True)
449   AssertCommand(["gnt-instance", "start", "--force", name], fail=True)
450
451   # Also do offline to offline
452   AssertCommand(["gnt-instance", "modify", "--offline", name])
453
454   # And online again
455   AssertCommand(["gnt-instance", "modify", "--online", name])
456
457
458 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
459 def TestInstanceConvertDisk(instance, snode):
460   """gnt-instance modify -t"""
461   name = instance["name"]
462   AssertCommand(["gnt-instance", "modify", "-t", "plain", name])
463   AssertCommand(["gnt-instance", "modify", "-t", "drbd",
464                  "-n", snode["primary"], name])
465
466
467 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
468 def TestInstanceGrowDisk(instance):
469   """gnt-instance grow-disk"""
470   name = instance["name"]
471   all_size = qa_config.get("disk")
472   all_grow = qa_config.get("disk-growth")
473   if not all_grow:
474     # missing disk sizes but instance grow disk has been enabled,
475     # let's set fixed/nomimal growth
476     all_grow = ["128M" for _ in all_size]
477   for idx, (size, grow) in enumerate(zip(all_size, all_grow)):
478     # succeed in grow by amount
479     AssertCommand(["gnt-instance", "grow-disk", name, str(idx), grow])
480     # fail in grow to the old size
481     AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
482                    size], fail=True)
483     # succeed to grow to old size + 2 * growth
484     int_size = utils.ParseUnit(size)
485     int_grow = utils.ParseUnit(grow)
486     AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
487                    str(int_size + 2 * int_grow)])
488
489
490 def TestInstanceList():
491   """gnt-instance list"""
492   qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
493
494
495 def TestInstanceListFields():
496   """gnt-instance list-fields"""
497   qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
498
499
500 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
501 def TestInstanceConsole(instance):
502   """gnt-instance console"""
503   AssertCommand(["gnt-instance", "console", "--show-cmd", instance["name"]])
504
505
506 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
507 def TestReplaceDisks(instance, pnode, snode, othernode):
508   """gnt-instance replace-disks"""
509   # pylint: disable=W0613
510   # due to unused pnode arg
511   # FIXME: should be removed from the function completely
512   def buildcmd(args):
513     cmd = ["gnt-instance", "replace-disks"]
514     cmd.extend(args)
515     cmd.append(instance["name"])
516     return cmd
517
518   options = qa_config.get("options", {})
519   use_ialloc = options.get("use-iallocators", True)
520   for data in [
521     ["-p"],
522     ["-s"],
523     # A placeholder; the actual command choice depends on use_ialloc
524     None,
525     # Restore the original secondary
526     ["--new-secondary=%s" % snode["primary"]],
527     ]:
528     if data is None:
529       if use_ialloc:
530         data = ["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT]
531       else:
532         data = ["--new-secondary=%s" % othernode["primary"]]
533     AssertCommand(buildcmd(data))
534
535   AssertCommand(buildcmd(["-a"]))
536   AssertCommand(["gnt-instance", "stop", instance["name"]])
537   AssertCommand(buildcmd(["-a"]), fail=True)
538   AssertCommand(["gnt-instance", "activate-disks", instance["name"]])
539   AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
540                  instance["name"]])
541   AssertCommand(buildcmd(["-a"]))
542   AssertCommand(["gnt-instance", "start", instance["name"]])
543
544
545 def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
546                          destroy=True):
547   """Execute gnt-instance recreate-disks and check the result
548
549   @param cmdargs: Arguments (instance name excluded)
550   @param instance: Instance to operate on
551   @param fail: True if the command is expected to fail
552   @param check: If True and fail is False, check that the disks work
553   @prama destroy: If True, destroy the old disks first
554
555   """
556   if destroy:
557     _DestroyInstanceVolumes(instance)
558   AssertCommand((["gnt-instance", "recreate-disks"] + cmdargs +
559                  [instance["name"]]), fail)
560   if not fail and check:
561     # Quick check that the disks are there
562     AssertCommand(["gnt-instance", "activate-disks", instance["name"]])
563     AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
564                    instance["name"]])
565     AssertCommand(["gnt-instance", "deactivate-disks", instance["name"]])
566
567
568 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
569 def TestRecreateDisks(instance, pnode, snode, othernodes):
570   """gnt-instance recreate-disks
571
572   @param instance: Instance to work on
573   @param pnode: Primary node
574   @param snode: Secondary node, or None for sigle-homed instances
575   @param othernodes: list/tuple of nodes where to temporarily recreate disks
576
577   """
578   options = qa_config.get("options", {})
579   use_ialloc = options.get("use-iallocators", True)
580   other_seq = ":".join([n["primary"] for n in othernodes])
581   orig_seq = pnode["primary"]
582   if snode:
583     orig_seq = orig_seq + ":" + snode["primary"]
584   # These fail because the instance is running
585   _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
586   if use_ialloc:
587     _AssertRecreateDisks(["-I", "hail"], instance, fail=True, destroy=False)
588   else:
589     _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
590   AssertCommand(["gnt-instance", "stop", instance["name"]])
591   # Disks exist: this should fail
592   _AssertRecreateDisks([], instance, fail=True, destroy=False)
593   # Recreate disks in place
594   _AssertRecreateDisks([], instance)
595   # Move disks away
596   if use_ialloc:
597     _AssertRecreateDisks(["-I", "hail"], instance)
598     # Move disks somewhere else
599     _AssertRecreateDisks(["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT],
600                          instance)
601   else:
602     _AssertRecreateDisks(["-n", other_seq], instance)
603   # Move disks back
604   _AssertRecreateDisks(["-n", orig_seq], instance, check=False)
605   # This and InstanceCheck decoration check that the disks are working
606   AssertCommand(["gnt-instance", "reinstall", "-f", instance["name"]])
607   AssertCommand(["gnt-instance", "start", instance["name"]])
608
609
610 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
611 def TestInstanceExport(instance, node):
612   """gnt-backup export -n ..."""
613   name = instance["name"]
614   AssertCommand(["gnt-backup", "export", "-n", node["primary"], name])
615   return qa_utils.ResolveInstanceName(name)
616
617
618 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
619 def TestInstanceExportWithRemove(instance, node):
620   """gnt-backup export --remove-instance"""
621   AssertCommand(["gnt-backup", "export", "-n", node["primary"],
622                  "--remove-instance", instance["name"]])
623
624
625 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
626 def TestInstanceExportNoTarget(instance):
627   """gnt-backup export (without target node, should fail)"""
628   AssertCommand(["gnt-backup", "export", instance["name"]], fail=True)
629
630
631 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
632 def TestInstanceImport(newinst, node, expnode, name):
633   """gnt-backup import"""
634   cmd = (["gnt-backup", "import",
635           "--disk-template=plain",
636           "--no-ip-check",
637           "--src-node=%s" % expnode["primary"],
638           "--src-dir=%s/%s" % (pathutils.EXPORT_DIR, name),
639           "--node=%s" % node["primary"]] +
640          _GetGenericAddParameters(newinst, force_mac=constants.VALUE_GENERATE))
641   cmd.append(newinst["name"])
642   AssertCommand(cmd)
643
644
645 def TestBackupList(expnode):
646   """gnt-backup list"""
647   AssertCommand(["gnt-backup", "list", "--node=%s" % expnode["primary"]])
648
649   qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
650                             namefield=None, test_unknown=False)
651
652
653 def TestBackupListFields():
654   """gnt-backup list-fields"""
655   qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
656
657
658 def _TestInstanceDiskFailure(instance, node, node2, onmaster):
659   """Testing disk failure."""
660   master = qa_config.GetMasterNode()
661   sq = utils.ShellQuoteArgs
662
663   instance_full = qa_utils.ResolveInstanceName(instance["name"])
664   node_full = qa_utils.ResolveNodeName(node)
665   node2_full = qa_utils.ResolveNodeName(node2)
666
667   print qa_utils.FormatInfo("Getting physical disk names")
668   cmd = ["gnt-node", "volumes", "--separator=|", "--no-headers",
669          "--output=node,phys,instance",
670          node["primary"], node2["primary"]]
671   output = qa_utils.GetCommandOutput(master["primary"], sq(cmd))
672
673   # Get physical disk names
674   re_disk = re.compile(r"^/dev/([a-z]+)\d+$")
675   node2disk = {}
676   for line in output.splitlines():
677     (node_name, phys, inst) = line.split("|")
678     if inst == instance_full:
679       if node_name not in node2disk:
680         node2disk[node_name] = []
681
682       m = re_disk.match(phys)
683       if not m:
684         raise qa_error.Error("Unknown disk name format: %s" % phys)
685
686       name = m.group(1)
687       if name not in node2disk[node_name]:
688         node2disk[node_name].append(name)
689
690   if [node2_full, node_full][int(onmaster)] not in node2disk:
691     raise qa_error.Error("Couldn't find physical disks used on"
692                          " %s node" % ["secondary", "master"][int(onmaster)])
693
694   print qa_utils.FormatInfo("Checking whether nodes have ability to stop"
695                             " disks")
696   for node_name, disks in node2disk.iteritems():
697     cmds = []
698     for disk in disks:
699       cmds.append(sq(["test", "-f", _GetDiskStatePath(disk)]))
700     AssertCommand(" && ".join(cmds), node=node_name)
701
702   print qa_utils.FormatInfo("Getting device paths")
703   cmd = ["gnt-instance", "activate-disks", instance["name"]]
704   output = qa_utils.GetCommandOutput(master["primary"], sq(cmd))
705   devpath = []
706   for line in output.splitlines():
707     (_, _, tmpdevpath) = line.split(":")
708     devpath.append(tmpdevpath)
709   print devpath
710
711   print qa_utils.FormatInfo("Getting drbd device paths")
712   cmd = ["gnt-instance", "info", instance["name"]]
713   output = qa_utils.GetCommandOutput(master["primary"], sq(cmd))
714   pattern = (r"\s+-\s+sd[a-z]+,\s+type:\s+drbd8?,\s+.*$"
715              r"\s+primary:\s+(/dev/drbd\d+)\s+")
716   drbddevs = re.findall(pattern, output, re.M)
717   print drbddevs
718
719   halted_disks = []
720   try:
721     print qa_utils.FormatInfo("Deactivating disks")
722     cmds = []
723     for name in node2disk[[node2_full, node_full][int(onmaster)]]:
724       halted_disks.append(name)
725       cmds.append(sq(["echo", "offline"]) + " >%s" % _GetDiskStatePath(name))
726     AssertCommand(" && ".join(cmds), node=[node2, node][int(onmaster)])
727
728     print qa_utils.FormatInfo("Write to disks and give some time to notice"
729                               " the problem")
730     cmds = []
731     for disk in devpath:
732       cmds.append(sq(["dd", "count=1", "bs=512", "conv=notrunc",
733                       "if=%s" % disk, "of=%s" % disk]))
734     for _ in (0, 1, 2):
735       AssertCommand(" && ".join(cmds), node=node)
736       time.sleep(3)
737
738     print qa_utils.FormatInfo("Debugging info")
739     for name in drbddevs:
740       AssertCommand(["drbdsetup", name, "show"], node=node)
741
742     AssertCommand(["gnt-instance", "info", instance["name"]])
743
744   finally:
745     print qa_utils.FormatInfo("Activating disks again")
746     cmds = []
747     for name in halted_disks:
748       cmds.append(sq(["echo", "running"]) + " >%s" % _GetDiskStatePath(name))
749     AssertCommand("; ".join(cmds), node=[node2, node][int(onmaster)])
750
751   if onmaster:
752     for name in drbddevs:
753       AssertCommand(["drbdsetup", name, "detach"], node=node)
754   else:
755     for name in drbddevs:
756       AssertCommand(["drbdsetup", name, "disconnect"], node=node2)
757
758   # TODO
759   #AssertCommand(["vgs"], [node2, node][int(onmaster)])
760
761   print qa_utils.FormatInfo("Making sure disks are up again")
762   AssertCommand(["gnt-instance", "replace-disks", instance["name"]])
763
764   print qa_utils.FormatInfo("Restarting instance")
765   AssertCommand(["gnt-instance", "shutdown", instance["name"]])
766   AssertCommand(["gnt-instance", "startup", instance["name"]])
767
768   AssertCommand(["gnt-cluster", "verify"])
769
770
771 def TestInstanceMasterDiskFailure(instance, node, node2):
772   """Testing disk failure on master node."""
773   # pylint: disable=W0613
774   # due to unused args
775   print qa_utils.FormatError("Disk failure on primary node cannot be"
776                              " tested due to potential crashes.")
777   # The following can cause crashes, thus it's disabled until fixed
778   #return _TestInstanceDiskFailure(instance, node, node2, True)
779
780
781 def TestInstanceSecondaryDiskFailure(instance, node, node2):
782   """Testing disk failure on secondary node."""
783   return _TestInstanceDiskFailure(instance, node, node2, False)