Statistics
| Branch: | Tag: | Revision:

root / qa / qa_instance.py @ 51131cad

History | View | Annotate | Download (18.4 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
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)