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