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