gnt-instance reinstall: Don't always exit with success
[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
33 import qa_config
34 import qa_utils
35 import qa_error
36
37 from qa_utils import AssertIn, AssertCommand, AssertEqual
38 from qa_utils import InstanceCheck, INST_DOWN, INST_UP, FIRST_ARG, RETURN_VALUE
39
40
41 def _GetDiskStatePath(disk):
42   return "/sys/block/%s/device/state" % disk
43
44
45 def _GetGenericAddParameters(inst, force_mac=None):
46   params = ["-B"]
47   params.append("%s=%s,%s=%s" % (constants.BE_MINMEM,
48                                  qa_config.get(constants.BE_MINMEM),
49                                  constants.BE_MAXMEM,
50                                  qa_config.get(constants.BE_MAXMEM)))
51   for idx, size in enumerate(qa_config.get("disk")):
52     params.extend(["--disk", "%s:size=%s" % (idx, size)])
53
54   # Set static MAC address if configured
55   if force_mac:
56     nic0_mac = force_mac
57   else:
58     nic0_mac = qa_config.GetInstanceNicMac(inst)
59   if nic0_mac:
60     params.extend(["--net", "0:mac=%s" % nic0_mac])
61
62   return params
63
64
65 def _DiskTest(node, disk_template):
66   instance = qa_config.AcquireInstance()
67   try:
68     cmd = (["gnt-instance", "add",
69             "--os-type=%s" % qa_config.get("os"),
70             "--disk-template=%s" % disk_template,
71             "--node=%s" % node] +
72            _GetGenericAddParameters(instance))
73     cmd.append(instance["name"])
74
75     AssertCommand(cmd)
76
77     _CheckSsconfInstanceList(instance["name"])
78
79     return instance
80   except:
81     qa_config.ReleaseInstance(instance)
82     raise
83
84
85 @InstanceCheck(None, INST_UP, RETURN_VALUE)
86 def TestInstanceAddWithPlainDisk(node):
87   """gnt-instance add -t plain"""
88   return _DiskTest(node["primary"], "plain")
89
90
91 @InstanceCheck(None, INST_UP, RETURN_VALUE)
92 def TestInstanceAddWithDrbdDisk(node, node2):
93   """gnt-instance add -t drbd"""
94   return _DiskTest("%s:%s" % (node["primary"], node2["primary"]),
95                    "drbd")
96
97
98 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
99 def TestInstanceRemove(instance):
100   """gnt-instance remove"""
101   AssertCommand(["gnt-instance", "remove", "-f", instance["name"]])
102
103   qa_config.ReleaseInstance(instance)
104
105
106 @InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
107 def TestInstanceStartup(instance):
108   """gnt-instance startup"""
109   AssertCommand(["gnt-instance", "startup", instance["name"]])
110
111
112 @InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
113 def TestInstanceShutdown(instance):
114   """gnt-instance shutdown"""
115   AssertCommand(["gnt-instance", "shutdown", instance["name"]])
116
117
118 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
119 def TestInstanceReboot(instance):
120   """gnt-instance reboot"""
121   options = qa_config.get("options", {})
122   reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
123   name = instance["name"]
124   for rtype in reboot_types:
125     AssertCommand(["gnt-instance", "reboot", "--type=%s" % rtype, name])
126
127   AssertCommand(["gnt-instance", "shutdown", name])
128   qa_utils.RunInstanceCheck(instance, False)
129   AssertCommand(["gnt-instance", "reboot", name])
130
131   master = qa_config.GetMasterNode()
132   cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", name]
133   result_output = qa_utils.GetCommandOutput(master["primary"],
134                                             utils.ShellQuoteArgs(cmd))
135   AssertEqual(result_output.strip(), constants.INSTST_RUNNING)
136
137
138 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
139 def TestInstanceReinstall(instance):
140   """gnt-instance reinstall"""
141   AssertCommand(["gnt-instance", "reinstall", "-f", instance["name"]])
142
143   # Test with non-existant OS definition
144   AssertCommand(["gnt-instance", "reinstall", "-f",
145                  "--os-type=NonExistantOsForQa",
146                  instance["name"]],
147                 fail=True)
148
149
150 def _ReadSsconfInstanceList():
151   """Reads ssconf_instance_list from the master node.
152
153   """
154   master = qa_config.GetMasterNode()
155
156   cmd = ["cat", utils.PathJoin(constants.DATA_DIR,
157                                "ssconf_%s" % constants.SS_INSTANCE_LIST)]
158
159   return qa_utils.GetCommandOutput(master["primary"],
160                                    utils.ShellQuoteArgs(cmd)).splitlines()
161
162
163 def _CheckSsconfInstanceList(instance):
164   """Checks if a certain instance is in the ssconf instance list.
165
166   @type instance: string
167   @param instance: Instance name
168
169   """
170   AssertIn(qa_utils.ResolveInstanceName(instance),
171            _ReadSsconfInstanceList())
172
173
174 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
175 def TestInstanceRenameAndBack(rename_source, rename_target):
176   """gnt-instance rename
177
178   This must leave the instance with the original name, not the target
179   name.
180
181   """
182   _CheckSsconfInstanceList(rename_source)
183
184   # first do a rename to a different actual name, expecting it to fail
185   qa_utils.AddToEtcHosts(["meeeeh-not-exists", rename_target])
186   try:
187     AssertCommand(["gnt-instance", "rename", rename_source, rename_target],
188                   fail=True)
189     _CheckSsconfInstanceList(rename_source)
190   finally:
191     qa_utils.RemoveFromEtcHosts(["meeeeh-not-exists", rename_target])
192
193   # and now rename instance to rename_target...
194   AssertCommand(["gnt-instance", "rename", rename_source, rename_target])
195   _CheckSsconfInstanceList(rename_target)
196   qa_utils.RunInstanceCheck(rename_source, False)
197   qa_utils.RunInstanceCheck(rename_target, False)
198
199   # and back
200   AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
201   _CheckSsconfInstanceList(rename_source)
202   qa_utils.RunInstanceCheck(rename_target, False)
203
204
205 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
206 def TestInstanceFailover(instance):
207   """gnt-instance failover"""
208   cmd = ["gnt-instance", "failover", "--force", instance["name"]]
209
210   # failover ...
211   AssertCommand(cmd)
212   qa_utils.RunInstanceCheck(instance, True)
213
214   # ... and back
215   AssertCommand(cmd)
216
217
218 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
219 def TestInstanceMigrate(instance):
220   """gnt-instance migrate"""
221   cmd = ["gnt-instance", "migrate", "--force", instance["name"]]
222
223   # migrate ...
224   AssertCommand(cmd)
225   qa_utils.RunInstanceCheck(instance, True)
226
227   # ... and back
228   AssertCommand(cmd)
229
230   # TODO: Split into multiple tests
231   AssertCommand(["gnt-instance", "shutdown", instance["name"]])
232   qa_utils.RunInstanceCheck(instance, False)
233   AssertCommand(cmd, fail=True)
234   AssertCommand(["gnt-instance", "migrate", "--force", "--allow-failover",
235                  instance["name"]])
236   AssertCommand(["gnt-instance", "start", instance["name"]])
237   AssertCommand(cmd)
238   qa_utils.RunInstanceCheck(instance, True)
239
240   AssertCommand(["gnt-instance", "modify", "-B",
241                  ("%s=%s" %
242                   (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)),
243                  instance["name"]])
244
245   AssertCommand(cmd)
246   qa_utils.RunInstanceCheck(instance, True)
247   # TODO: Verify that a failover has been done instead of a migration
248
249   # TODO: Verify whether the default value is restored here (not hardcoded)
250   AssertCommand(["gnt-instance", "modify", "-B",
251                  ("%s=%s" %
252                   (constants.BE_ALWAYS_FAILOVER, constants.VALUE_FALSE)),
253                  instance["name"]])
254
255   AssertCommand(cmd)
256   qa_utils.RunInstanceCheck(instance, True)
257
258
259 def TestInstanceInfo(instance):
260   """gnt-instance info"""
261   AssertCommand(["gnt-instance", "info", instance["name"]])
262
263
264 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
265 def TestInstanceModify(instance):
266   """gnt-instance modify"""
267   default_hv = qa_config.GetDefaultHypervisor()
268
269   # Assume /sbin/init exists on all systems
270   test_kernel = "/sbin/init"
271   test_initrd = test_kernel
272
273   orig_maxmem = qa_config.get(constants.BE_MAXMEM)
274   orig_minmem = qa_config.get(constants.BE_MINMEM)
275   #orig_bridge = qa_config.get("bridge", "xen-br0")
276
277   args = [
278     ["-B", "%s=128" % constants.BE_MINMEM],
279     ["-B", "%s=128" % constants.BE_MAXMEM],
280     ["-B", "%s=%s,%s=%s" % (constants.BE_MINMEM, orig_minmem,
281                             constants.BE_MAXMEM, orig_maxmem)],
282     ["-B", "%s=2" % constants.BE_VCPUS],
283     ["-B", "%s=1" % constants.BE_VCPUS],
284     ["-B", "%s=%s" % (constants.BE_VCPUS, constants.VALUE_DEFAULT)],
285     ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)],
286     ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_DEFAULT)],
287
288     ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, test_kernel)],
289     ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, constants.VALUE_DEFAULT)],
290
291     # TODO: bridge tests
292     #["--bridge", "xen-br1"],
293     #["--bridge", orig_bridge],
294     ]
295
296   if default_hv == constants.HT_XEN_PVM:
297     args.extend([
298       ["-H", "%s=%s" % (constants.HV_INITRD_PATH, test_initrd)],
299       ["-H", "no_%s" % (constants.HV_INITRD_PATH, )],
300       ["-H", "%s=%s" % (constants.HV_INITRD_PATH, constants.VALUE_DEFAULT)],
301       ])
302   elif default_hv == constants.HT_XEN_HVM:
303     args.extend([
304       ["-H", "%s=acn" % constants.HV_BOOT_ORDER],
305       ["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
306       ])
307
308   for alist in args:
309     AssertCommand(["gnt-instance", "modify"] + alist + [instance["name"]])
310
311   # check no-modify
312   AssertCommand(["gnt-instance", "modify", instance["name"]], fail=True)
313
314   # Marking offline/online while instance is running must fail
315   for arg in ["--online", "--offline"]:
316     AssertCommand(["gnt-instance", "modify", arg, instance["name"]], fail=True)
317
318
319 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
320 def TestInstanceStoppedModify(instance):
321   """gnt-instance modify (stopped instance)"""
322   name = instance["name"]
323
324   # Instance was not marked offline; try marking it online once more
325   AssertCommand(["gnt-instance", "modify", "--online", name])
326
327   # Mark instance as offline
328   AssertCommand(["gnt-instance", "modify", "--offline", name])
329
330   # And online again
331   AssertCommand(["gnt-instance", "modify", "--online", name])
332
333
334 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
335 def TestInstanceConvertDisk(instance, snode):
336   """gnt-instance modify -t"""
337   name = instance["name"]
338   AssertCommand(["gnt-instance", "modify", "-t", "plain", name])
339   AssertCommand(["gnt-instance", "modify", "-t", "drbd",
340                  "-n", snode["primary"], name])
341
342
343 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
344 def TestInstanceGrowDisk(instance):
345   """gnt-instance grow-disk"""
346   name = instance["name"]
347   all_size = qa_config.get("disk")
348   all_grow = qa_config.get("disk-growth")
349   if not all_grow:
350     # missing disk sizes but instance grow disk has been enabled,
351     # let's set fixed/nomimal growth
352     all_grow = ["128M" for _ in all_size]
353   for idx, (size, grow) in enumerate(zip(all_size, all_grow)):
354     # succeed in grow by amount
355     AssertCommand(["gnt-instance", "grow-disk", name, str(idx), grow])
356     # fail in grow to the old size
357     AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
358                    size], fail=True)
359     # succeed to grow to old size + 2 * growth
360     int_size = utils.ParseUnit(size)
361     int_grow = utils.ParseUnit(grow)
362     AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
363                    str(int_size + 2 * int_grow)])
364
365
366 def TestInstanceList():
367   """gnt-instance list"""
368   qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
369
370
371 def TestInstanceListFields():
372   """gnt-instance list-fields"""
373   qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
374
375
376 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
377 def TestInstanceConsole(instance):
378   """gnt-instance console"""
379   AssertCommand(["gnt-instance", "console", "--show-cmd", instance["name"]])
380
381
382 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
383 def TestReplaceDisks(instance, pnode, snode, othernode):
384   """gnt-instance replace-disks"""
385   # pylint: disable=W0613
386   # due to unused pnode arg
387   # FIXME: should be removed from the function completely
388   def buildcmd(args):
389     cmd = ["gnt-instance", "replace-disks"]
390     cmd.extend(args)
391     cmd.append(instance["name"])
392     return cmd
393
394   for data in [
395     ["-p"],
396     ["-s"],
397     ["--new-secondary=%s" % othernode["primary"]],
398     # and restore
399     ["--new-secondary=%s" % snode["primary"]],
400     ]:
401     AssertCommand(buildcmd(data))
402
403   AssertCommand(buildcmd(["-a"]))
404   AssertCommand(["gnt-instance", "stop", instance["name"]])
405   AssertCommand(buildcmd(["-a"]), fail=True)
406   AssertCommand(["gnt-instance", "activate-disks", instance["name"]])
407   AssertCommand(buildcmd(["-a"]))
408   AssertCommand(["gnt-instance", "start", instance["name"]])
409
410
411 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
412 def TestInstanceExport(instance, node):
413   """gnt-backup export -n ..."""
414   name = instance["name"]
415   AssertCommand(["gnt-backup", "export", "-n", node["primary"], name])
416   return qa_utils.ResolveInstanceName(name)
417
418
419 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
420 def TestInstanceExportWithRemove(instance, node):
421   """gnt-backup export --remove-instance"""
422   AssertCommand(["gnt-backup", "export", "-n", node["primary"],
423                  "--remove-instance", instance["name"]])
424
425
426 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
427 def TestInstanceExportNoTarget(instance):
428   """gnt-backup export (without target node, should fail)"""
429   AssertCommand(["gnt-backup", "export", instance["name"]], fail=True)
430
431
432 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
433 def TestInstanceImport(newinst, node, expnode, name):
434   """gnt-backup import"""
435   cmd = (["gnt-backup", "import",
436           "--disk-template=plain",
437           "--no-ip-check",
438           "--src-node=%s" % expnode["primary"],
439           "--src-dir=%s/%s" % (constants.EXPORT_DIR, name),
440           "--node=%s" % node["primary"]] +
441          _GetGenericAddParameters(newinst, force_mac=constants.VALUE_GENERATE))
442   cmd.append(newinst["name"])
443   AssertCommand(cmd)
444
445
446 def TestBackupList(expnode):
447   """gnt-backup list"""
448   AssertCommand(["gnt-backup", "list", "--node=%s" % expnode["primary"]])
449
450   qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
451                             namefield=None, test_unknown=False)
452
453
454 def TestBackupListFields():
455   """gnt-backup list-fields"""
456   qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
457
458
459 def _TestInstanceDiskFailure(instance, node, node2, onmaster):
460   """Testing disk failure."""
461   master = qa_config.GetMasterNode()
462   sq = utils.ShellQuoteArgs
463
464   instance_full = qa_utils.ResolveInstanceName(instance["name"])
465   node_full = qa_utils.ResolveNodeName(node)
466   node2_full = qa_utils.ResolveNodeName(node2)
467
468   print qa_utils.FormatInfo("Getting physical disk names")
469   cmd = ["gnt-node", "volumes", "--separator=|", "--no-headers",
470          "--output=node,phys,instance",
471          node["primary"], node2["primary"]]
472   output = qa_utils.GetCommandOutput(master["primary"], sq(cmd))
473
474   # Get physical disk names
475   re_disk = re.compile(r"^/dev/([a-z]+)\d+$")
476   node2disk = {}
477   for line in output.splitlines():
478     (node_name, phys, inst) = line.split("|")
479     if inst == instance_full:
480       if node_name not in node2disk:
481         node2disk[node_name] = []
482
483       m = re_disk.match(phys)
484       if not m:
485         raise qa_error.Error("Unknown disk name format: %s" % phys)
486
487       name = m.group(1)
488       if name not in node2disk[node_name]:
489         node2disk[node_name].append(name)
490
491   if [node2_full, node_full][int(onmaster)] not in node2disk:
492     raise qa_error.Error("Couldn't find physical disks used on"
493                          " %s node" % ["secondary", "master"][int(onmaster)])
494
495   print qa_utils.FormatInfo("Checking whether nodes have ability to stop"
496                             " disks")
497   for node_name, disks in node2disk.iteritems():
498     cmds = []
499     for disk in disks:
500       cmds.append(sq(["test", "-f", _GetDiskStatePath(disk)]))
501     AssertCommand(" && ".join(cmds), node=node_name)
502
503   print qa_utils.FormatInfo("Getting device paths")
504   cmd = ["gnt-instance", "activate-disks", instance["name"]]
505   output = qa_utils.GetCommandOutput(master["primary"], sq(cmd))
506   devpath = []
507   for line in output.splitlines():
508     (_, _, tmpdevpath) = line.split(":")
509     devpath.append(tmpdevpath)
510   print devpath
511
512   print qa_utils.FormatInfo("Getting drbd device paths")
513   cmd = ["gnt-instance", "info", instance["name"]]
514   output = qa_utils.GetCommandOutput(master["primary"], sq(cmd))
515   pattern = (r"\s+-\s+sd[a-z]+,\s+type:\s+drbd8?,\s+.*$"
516              r"\s+primary:\s+(/dev/drbd\d+)\s+")
517   drbddevs = re.findall(pattern, output, re.M)
518   print drbddevs
519
520   halted_disks = []
521   try:
522     print qa_utils.FormatInfo("Deactivating disks")
523     cmds = []
524     for name in node2disk[[node2_full, node_full][int(onmaster)]]:
525       halted_disks.append(name)
526       cmds.append(sq(["echo", "offline"]) + " >%s" % _GetDiskStatePath(name))
527     AssertCommand(" && ".join(cmds), node=[node2, node][int(onmaster)])
528
529     print qa_utils.FormatInfo("Write to disks and give some time to notice"
530                               " the problem")
531     cmds = []
532     for disk in devpath:
533       cmds.append(sq(["dd", "count=1", "bs=512", "conv=notrunc",
534                       "if=%s" % disk, "of=%s" % disk]))
535     for _ in (0, 1, 2):
536       AssertCommand(" && ".join(cmds), node=node)
537       time.sleep(3)
538
539     print qa_utils.FormatInfo("Debugging info")
540     for name in drbddevs:
541       AssertCommand(["drbdsetup", name, "show"], node=node)
542
543     AssertCommand(["gnt-instance", "info", instance["name"]])
544
545   finally:
546     print qa_utils.FormatInfo("Activating disks again")
547     cmds = []
548     for name in halted_disks:
549       cmds.append(sq(["echo", "running"]) + " >%s" % _GetDiskStatePath(name))
550     AssertCommand("; ".join(cmds), node=[node2, node][int(onmaster)])
551
552   if onmaster:
553     for name in drbddevs:
554       AssertCommand(["drbdsetup", name, "detach"], node=node)
555   else:
556     for name in drbddevs:
557       AssertCommand(["drbdsetup", name, "disconnect"], node=node2)
558
559   # TODO
560   #AssertCommand(["vgs"], [node2, node][int(onmaster)])
561
562   print qa_utils.FormatInfo("Making sure disks are up again")
563   AssertCommand(["gnt-instance", "replace-disks", instance["name"]])
564
565   print qa_utils.FormatInfo("Restarting instance")
566   AssertCommand(["gnt-instance", "shutdown", instance["name"]])
567   AssertCommand(["gnt-instance", "startup", instance["name"]])
568
569   AssertCommand(["gnt-cluster", "verify"])
570
571
572 def TestInstanceMasterDiskFailure(instance, node, node2):
573   """Testing disk failure on master node."""
574   # pylint: disable=W0613
575   # due to unused args
576   print qa_utils.FormatError("Disk failure on primary node cannot be"
577                              " tested due to potential crashes.")
578   # The following can cause crashes, thus it's disabled until fixed
579   #return _TestInstanceDiskFailure(instance, node, node2, True)
580
581
582 def TestInstanceSecondaryDiskFailure(instance, node, node2):
583   """Testing disk failure on secondary node."""
584   return _TestInstanceDiskFailure(instance, node, node2, False)