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