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