Statistics
| Branch: | Tag: | Revision:

root / qa / qa_instance.py @ 0fdf247d

History | View | Annotate | Download (16 kB)

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 TestInstanceList():
283
  """gnt-instance list"""
284
  qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
285

    
286

    
287
def TestInstanceListFields():
288
  """gnt-instance list-fields"""
289
  qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
290

    
291

    
292
def TestInstanceConsole(instance):
293
  """gnt-instance console"""
294
  AssertCommand(["gnt-instance", "console", "--show-cmd", instance["name"]])
295

    
296

    
297
def TestReplaceDisks(instance, pnode, snode, othernode):
298
  """gnt-instance replace-disks"""
299
  # pylint: disable=W0613
300
  # due to unused pnode arg
301
  # FIXME: should be removed from the function completely
302
  def buildcmd(args):
303
    cmd = ["gnt-instance", "replace-disks"]
304
    cmd.extend(args)
305
    cmd.append(instance["name"])
306
    return cmd
307

    
308
  for data in [
309
    ["-p"],
310
    ["-s"],
311
    ["--new-secondary=%s" % othernode["primary"]],
312
    # and restore
313
    ["--new-secondary=%s" % snode["primary"]],
314
    ]:
315
    AssertCommand(buildcmd(data))
316

    
317
  AssertCommand(buildcmd(["-a"]))
318
  AssertCommand(["gnt-instance", "stop", instance["name"]])
319
  AssertCommand(buildcmd(["-a"]), fail=True)
320
  AssertCommand(["gnt-instance", "activate-disks", instance["name"]])
321
  AssertCommand(buildcmd(["-a"]))
322
  AssertCommand(["gnt-instance", "start", instance["name"]])
323

    
324

    
325
def TestInstanceExport(instance, node):
326
  """gnt-backup export -n ..."""
327
  name = instance["name"]
328
  AssertCommand(["gnt-backup", "export", "-n", node["primary"], name])
329
  return qa_utils.ResolveInstanceName(name)
330

    
331

    
332
def TestInstanceExportWithRemove(instance, node):
333
  """gnt-backup export --remove-instance"""
334
  AssertCommand(["gnt-backup", "export", "-n", node["primary"],
335
                 "--remove-instance", instance["name"]])
336

    
337

    
338
def TestInstanceExportNoTarget(instance):
339
  """gnt-backup export (without target node, should fail)"""
340
  AssertCommand(["gnt-backup", "export", instance["name"]], fail=True)
341

    
342

    
343
def TestInstanceImport(node, newinst, expnode, name):
344
  """gnt-backup import"""
345
  cmd = (["gnt-backup", "import",
346
          "--disk-template=plain",
347
          "--no-ip-check",
348
          "--net", "0:mac=generate",
349
          "--src-node=%s" % expnode["primary"],
350
          "--src-dir=%s/%s" % (constants.EXPORT_DIR, name),
351
          "--node=%s" % node["primary"]] +
352
         _GetGenericAddParameters())
353
  cmd.append(newinst["name"])
354
  AssertCommand(cmd)
355

    
356

    
357
def TestBackupList(expnode):
358
  """gnt-backup list"""
359
  AssertCommand(["gnt-backup", "list", "--node=%s" % expnode["primary"]])
360

    
361
  qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
362
                            namefield=None, test_unknown=False)
363

    
364

    
365
def TestBackupListFields():
366
  """gnt-backup list-fields"""
367
  qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
368

    
369

    
370
def _TestInstanceDiskFailure(instance, node, node2, onmaster):
371
  """Testing disk failure."""
372
  master = qa_config.GetMasterNode()
373
  sq = utils.ShellQuoteArgs
374

    
375
  instance_full = qa_utils.ResolveInstanceName(instance["name"])
376
  node_full = qa_utils.ResolveNodeName(node)
377
  node2_full = qa_utils.ResolveNodeName(node2)
378

    
379
  print qa_utils.FormatInfo("Getting physical disk names")
380
  cmd = ["gnt-node", "volumes", "--separator=|", "--no-headers",
381
         "--output=node,phys,instance",
382
         node["primary"], node2["primary"]]
383
  output = qa_utils.GetCommandOutput(master["primary"], sq(cmd))
384

    
385
  # Get physical disk names
386
  re_disk = re.compile(r"^/dev/([a-z]+)\d+$")
387
  node2disk = {}
388
  for line in output.splitlines():
389
    (node_name, phys, inst) = line.split("|")
390
    if inst == instance_full:
391
      if node_name not in node2disk:
392
        node2disk[node_name] = []
393

    
394
      m = re_disk.match(phys)
395
      if not m:
396
        raise qa_error.Error("Unknown disk name format: %s" % phys)
397

    
398
      name = m.group(1)
399
      if name not in node2disk[node_name]:
400
        node2disk[node_name].append(name)
401

    
402
  if [node2_full, node_full][int(onmaster)] not in node2disk:
403
    raise qa_error.Error("Couldn't find physical disks used on"
404
                         " %s node" % ["secondary", "master"][int(onmaster)])
405

    
406
  print qa_utils.FormatInfo("Checking whether nodes have ability to stop"
407
                            " disks")
408
  for node_name, disks in node2disk.iteritems():
409
    cmds = []
410
    for disk in disks:
411
      cmds.append(sq(["test", "-f", _GetDiskStatePath(disk)]))
412
    AssertCommand(" && ".join(cmds), node=node_name)
413

    
414
  print qa_utils.FormatInfo("Getting device paths")
415
  cmd = ["gnt-instance", "activate-disks", instance["name"]]
416
  output = qa_utils.GetCommandOutput(master["primary"], sq(cmd))
417
  devpath = []
418
  for line in output.splitlines():
419
    (_, _, tmpdevpath) = line.split(":")
420
    devpath.append(tmpdevpath)
421
  print devpath
422

    
423
  print qa_utils.FormatInfo("Getting drbd device paths")
424
  cmd = ["gnt-instance", "info", instance["name"]]
425
  output = qa_utils.GetCommandOutput(master["primary"], sq(cmd))
426
  pattern = (r"\s+-\s+sd[a-z]+,\s+type:\s+drbd8?,\s+.*$"
427
             r"\s+primary:\s+(/dev/drbd\d+)\s+")
428
  drbddevs = re.findall(pattern, output, re.M)
429
  print drbddevs
430

    
431
  halted_disks = []
432
  try:
433
    print qa_utils.FormatInfo("Deactivating disks")
434
    cmds = []
435
    for name in node2disk[[node2_full, node_full][int(onmaster)]]:
436
      halted_disks.append(name)
437
      cmds.append(sq(["echo", "offline"]) + " >%s" % _GetDiskStatePath(name))
438
    AssertCommand(" && ".join(cmds), node=[node2, node][int(onmaster)])
439

    
440
    print qa_utils.FormatInfo("Write to disks and give some time to notice"
441
                              " to notice the problem")
442
    cmds = []
443
    for disk in devpath:
444
      cmds.append(sq(["dd", "count=1", "bs=512", "conv=notrunc",
445
                      "if=%s" % disk, "of=%s" % disk]))
446
    for _ in (0, 1, 2):
447
      AssertCommand(" && ".join(cmds), node=node)
448
      time.sleep(3)
449

    
450
    print qa_utils.FormatInfo("Debugging info")
451
    for name in drbddevs:
452
      AssertCommand(["drbdsetup", name, "show"], node=node)
453

    
454
    AssertCommand(["gnt-instance", "info", instance["name"]])
455

    
456
  finally:
457
    print qa_utils.FormatInfo("Activating disks again")
458
    cmds = []
459
    for name in halted_disks:
460
      cmds.append(sq(["echo", "running"]) + " >%s" % _GetDiskStatePath(name))
461
    AssertCommand("; ".join(cmds), node=[node2, node][int(onmaster)])
462

    
463
  if onmaster:
464
    for name in drbddevs:
465
      AssertCommand(["drbdsetup", name, "detach"], node=node)
466
  else:
467
    for name in drbddevs:
468
      AssertCommand(["drbdsetup", name, "disconnect"], node=node2)
469

    
470
  # TODO
471
  #AssertCommand(["vgs"], [node2, node][int(onmaster)])
472

    
473
  print qa_utils.FormatInfo("Making sure disks are up again")
474
  AssertCommand(["gnt-instance", "replace-disks", instance["name"]])
475

    
476
  print qa_utils.FormatInfo("Restarting instance")
477
  AssertCommand(["gnt-instance", "shutdown", instance["name"]])
478
  AssertCommand(["gnt-instance", "startup", instance["name"]])
479

    
480
  AssertCommand(["gnt-cluster", "verify"])
481

    
482

    
483
def TestInstanceMasterDiskFailure(instance, node, node2):
484
  """Testing disk failure on master node."""
485
  # pylint: disable=W0613
486
  # due to unused args
487
  print qa_utils.FormatError("Disk failure on primary node cannot be"
488
                             " tested due to potential crashes.")
489
  # The following can cause crashes, thus it's disabled until fixed
490
  #return _TestInstanceDiskFailure(instance, node, node2, True)
491

    
492

    
493
def TestInstanceSecondaryDiskFailure(instance, node, node2):
494
  """Testing disk failure on secondary node."""
495
  return _TestInstanceDiskFailure(instance, node, node2, False)