Statistics
| Branch: | Tag: | Revision:

root / qa / qa_instance.py @ 304d9f02

History | View | Annotate | Download (23.1 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
from ganeti import pathutils
33

    
34
import qa_config
35
import qa_utils
36
import qa_error
37

    
38
from qa_utils import AssertIn, AssertCommand, AssertEqual
39
from qa_utils import InstanceCheck, INST_DOWN, INST_UP, FIRST_ARG, RETURN_VALUE
40

    
41

    
42
def _GetDiskStatePath(disk):
43
  return "/sys/block/%s/device/state" % disk
44

    
45

    
46
def _GetGenericAddParameters(inst, force_mac=None):
47
  params = ["-B"]
48
  params.append("%s=%s,%s=%s" % (constants.BE_MINMEM,
49
                                 qa_config.get(constants.BE_MINMEM),
50
                                 constants.BE_MAXMEM,
51
                                 qa_config.get(constants.BE_MAXMEM)))
52
  for idx, size in enumerate(qa_config.get("disk")):
53
    params.extend(["--disk", "%s:size=%s" % (idx, size)])
54

    
55
  # Set static MAC address if configured
56
  if force_mac:
57
    nic0_mac = force_mac
58
  else:
59
    nic0_mac = qa_config.GetInstanceNicMac(inst)
60
  if nic0_mac:
61
    params.extend(["--net", "0:mac=%s" % nic0_mac])
62

    
63
  return params
64

    
65

    
66
def _DiskTest(node, disk_template):
67
  instance = qa_config.AcquireInstance()
68
  try:
69
    cmd = (["gnt-instance", "add",
70
            "--os-type=%s" % qa_config.get("os"),
71
            "--disk-template=%s" % disk_template,
72
            "--node=%s" % node] +
73
           _GetGenericAddParameters(instance))
74
    cmd.append(instance["name"])
75

    
76
    AssertCommand(cmd)
77

    
78
    _CheckSsconfInstanceList(instance["name"])
79

    
80
    return instance
81
  except:
82
    qa_config.ReleaseInstance(instance)
83
    raise
84

    
85

    
86
def _DestroyInstanceVolumes(instance):
87
  """Remove all the LVM volumes of an instance.
88

89
  This is used to simulate HW errors (dead nodes, broken disks...); the
90
  configuration of the instance is not affected.
91

92
  """
93
  master = qa_config.GetMasterNode()
94
  infocmd = utils.ShellQuoteArgs(["gnt-instance", "info", instance["name"]])
95
  info_out = qa_utils.GetCommandOutput(master["primary"], infocmd)
96
  re_node = re.compile(r"^\s+-\s+(?:primary|secondaries):\s+(\S.+)$")
97
  node_elem = r"([^,()]+)(?:\s+\([^)]+\))?"
98
  # re_nodelist matches a list of nodes returned by gnt-instance info, e.g.:
99
  #  node1.fqdn
100
  #  node2.fqdn,node3.fqdn
101
  #  node4.fqdn (group mygroup, group UUID 01234567-abcd-0123-4567-0123456789ab)
102
  # FIXME This works with no more than 2 secondaries
103
  re_nodelist = re.compile(node_elem + "(?:," + node_elem + ")?$")
104
  re_vol = re.compile(r"^\s+logical_id:\s+(\S+)$")
105
  nodes = []
106
  vols = []
107
  for line in info_out.splitlines():
108
    m = re_node.match(line)
109
    if m:
110
      nodestr = m.group(1)
111
      m2 = re_nodelist.match(nodestr)
112
      if m2:
113
        nodes.extend(filter(None, m2.groups()))
114
      else:
115
        nodes.append(nodestr)
116
    m = re_vol.match(line)
117
    if m:
118
      vols.append(m.group(1))
119
  assert vols
120
  assert nodes
121
  for node in nodes:
122
    AssertCommand(["lvremove", "-f"] + vols, node=node)
123

    
124

    
125
def _GetBoolInstanceField(instance, field):
126
  """Get the Boolean value of a field of an instance.
127

128
  @type instance: string
129
  @param instance: Instance name
130
  @type field: string
131
  @param field: Name of the field
132

133
  """
134
  master = qa_config.GetMasterNode()
135
  infocmd = utils.ShellQuoteArgs(["gnt-instance", "list", "--no-headers",
136
                                  "-o", field, instance])
137
  info_out = qa_utils.GetCommandOutput(master["primary"], infocmd).strip()
138
  if info_out == "Y":
139
    return True
140
  elif info_out == "N":
141
    return False
142
  else:
143
    raise qa_error.Error("Field %s of instance %s has a non-Boolean value:"
144
                         " %s" % (field, instance, info_out))
145

    
146

    
147
@InstanceCheck(None, INST_UP, RETURN_VALUE)
148
def TestInstanceAddWithPlainDisk(node):
149
  """gnt-instance add -t plain"""
150
  return _DiskTest(node["primary"], "plain")
151

    
152

    
153
@InstanceCheck(None, INST_UP, RETURN_VALUE)
154
def TestInstanceAddWithDrbdDisk(node, node2):
155
  """gnt-instance add -t drbd"""
156
  return _DiskTest("%s:%s" % (node["primary"], node2["primary"]),
157
                   "drbd")
158

    
159

    
160
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
161
def TestInstanceRemove(instance):
162
  """gnt-instance remove"""
163
  AssertCommand(["gnt-instance", "remove", "-f", instance["name"]])
164

    
165
  qa_config.ReleaseInstance(instance)
166

    
167

    
168
@InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
169
def TestInstanceStartup(instance):
170
  """gnt-instance startup"""
171
  AssertCommand(["gnt-instance", "startup", instance["name"]])
172

    
173

    
174
@InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
175
def TestInstanceShutdown(instance):
176
  """gnt-instance shutdown"""
177
  AssertCommand(["gnt-instance", "shutdown", instance["name"]])
178

    
179

    
180
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
181
def TestInstanceReboot(instance):
182
  """gnt-instance reboot"""
183
  options = qa_config.get("options", {})
184
  reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
185
  name = instance["name"]
186
  for rtype in reboot_types:
187
    AssertCommand(["gnt-instance", "reboot", "--type=%s" % rtype, name])
188

    
189
  AssertCommand(["gnt-instance", "shutdown", name])
190
  qa_utils.RunInstanceCheck(instance, False)
191
  AssertCommand(["gnt-instance", "reboot", name])
192

    
193
  master = qa_config.GetMasterNode()
194
  cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", name]
195
  result_output = qa_utils.GetCommandOutput(master["primary"],
196
                                            utils.ShellQuoteArgs(cmd))
197
  AssertEqual(result_output.strip(), constants.INSTST_RUNNING)
198

    
199

    
200
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
201
def TestInstanceReinstall(instance):
202
  """gnt-instance reinstall"""
203
  AssertCommand(["gnt-instance", "reinstall", "-f", instance["name"]])
204

    
205

    
206
def _ReadSsconfInstanceList():
207
  """Reads ssconf_instance_list from the master node.
208

209
  """
210
  master = qa_config.GetMasterNode()
211

    
212
  cmd = ["cat", utils.PathJoin(pathutils.DATA_DIR,
213
                               "ssconf_%s" % constants.SS_INSTANCE_LIST)]
214

    
215
  return qa_utils.GetCommandOutput(master["primary"],
216
                                   utils.ShellQuoteArgs(cmd)).splitlines()
217

    
218

    
219
def _CheckSsconfInstanceList(instance):
220
  """Checks if a certain instance is in the ssconf instance list.
221

222
  @type instance: string
223
  @param instance: Instance name
224

225
  """
226
  AssertIn(qa_utils.ResolveInstanceName(instance),
227
           _ReadSsconfInstanceList())
228

    
229

    
230
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
231
def TestInstanceRenameAndBack(rename_source, rename_target):
232
  """gnt-instance rename
233

234
  This must leave the instance with the original name, not the target
235
  name.
236

237
  """
238
  _CheckSsconfInstanceList(rename_source)
239

    
240
  # first do a rename to a different actual name, expecting it to fail
241
  qa_utils.AddToEtcHosts(["meeeeh-not-exists", rename_target])
242
  try:
243
    AssertCommand(["gnt-instance", "rename", rename_source, rename_target],
244
                  fail=True)
245
    _CheckSsconfInstanceList(rename_source)
246
  finally:
247
    qa_utils.RemoveFromEtcHosts(["meeeeh-not-exists", rename_target])
248

    
249
  # and now rename instance to rename_target...
250
  AssertCommand(["gnt-instance", "rename", rename_source, rename_target])
251
  _CheckSsconfInstanceList(rename_target)
252
  qa_utils.RunInstanceCheck(rename_source, False)
253
  qa_utils.RunInstanceCheck(rename_target, False)
254

    
255
  # and back
256
  AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
257
  _CheckSsconfInstanceList(rename_source)
258
  qa_utils.RunInstanceCheck(rename_target, False)
259

    
260

    
261
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
262
def TestInstanceFailover(instance):
263
  """gnt-instance failover"""
264
  cmd = ["gnt-instance", "failover", "--force", instance["name"]]
265

    
266
  # failover ...
267
  AssertCommand(cmd)
268
  qa_utils.RunInstanceCheck(instance, True)
269

    
270
  # ... and back
271
  AssertCommand(cmd)
272

    
273

    
274
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
275
def TestInstanceMigrate(instance, toggle_always_failover=True):
276
  """gnt-instance migrate"""
277
  cmd = ["gnt-instance", "migrate", "--force", instance["name"]]
278
  af_par = constants.BE_ALWAYS_FAILOVER
279
  af_field = "be/" + constants.BE_ALWAYS_FAILOVER
280
  af_init_val = _GetBoolInstanceField(instance["name"], af_field)
281

    
282
  # migrate ...
283
  AssertCommand(cmd)
284
  # TODO: Verify the choice between failover and migration
285
  qa_utils.RunInstanceCheck(instance, True)
286

    
287
  # ... and back (possibly with always_failover toggled)
288
  if toggle_always_failover:
289
    AssertCommand(["gnt-instance", "modify", "-B",
290
                   ("%s=%s" % (af_par, not af_init_val)),
291
                   instance["name"]])
292
  AssertCommand(cmd)
293
  # TODO: Verify the choice between failover and migration
294
  qa_utils.RunInstanceCheck(instance, True)
295
  if toggle_always_failover:
296
    AssertCommand(["gnt-instance", "modify", "-B",
297
                   ("%s=%s" % (af_par, af_init_val)), instance["name"]])
298

    
299
  # TODO: Split into multiple tests
300
  AssertCommand(["gnt-instance", "shutdown", instance["name"]])
301
  qa_utils.RunInstanceCheck(instance, False)
302
  AssertCommand(cmd, fail=True)
303
  AssertCommand(["gnt-instance", "migrate", "--force", "--allow-failover",
304
                 instance["name"]])
305
  AssertCommand(["gnt-instance", "start", instance["name"]])
306
  AssertCommand(cmd)
307
  # @InstanceCheck enforces the check that the instance is running
308

    
309

    
310
def TestInstanceInfo(instance):
311
  """gnt-instance info"""
312
  AssertCommand(["gnt-instance", "info", instance["name"]])
313

    
314

    
315
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
316
def TestInstanceModify(instance):
317
  """gnt-instance modify"""
318
  default_hv = qa_config.GetDefaultHypervisor()
319

    
320
  # Assume /sbin/init exists on all systems
321
  test_kernel = "/sbin/init"
322
  test_initrd = test_kernel
323

    
324
  orig_maxmem = qa_config.get(constants.BE_MAXMEM)
325
  orig_minmem = qa_config.get(constants.BE_MINMEM)
326
  #orig_bridge = qa_config.get("bridge", "xen-br0")
327

    
328
  args = [
329
    ["-B", "%s=128" % constants.BE_MINMEM],
330
    ["-B", "%s=128" % constants.BE_MAXMEM],
331
    ["-B", "%s=%s,%s=%s" % (constants.BE_MINMEM, orig_minmem,
332
                            constants.BE_MAXMEM, orig_maxmem)],
333
    ["-B", "%s=2" % constants.BE_VCPUS],
334
    ["-B", "%s=1" % constants.BE_VCPUS],
335
    ["-B", "%s=%s" % (constants.BE_VCPUS, constants.VALUE_DEFAULT)],
336
    ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)],
337
    ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_DEFAULT)],
338

    
339
    ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, test_kernel)],
340
    ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, constants.VALUE_DEFAULT)],
341

    
342
    # TODO: bridge tests
343
    #["--bridge", "xen-br1"],
344
    #["--bridge", orig_bridge],
345
    ]
346

    
347
  if default_hv == constants.HT_XEN_PVM:
348
    args.extend([
349
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, test_initrd)],
350
      ["-H", "no_%s" % (constants.HV_INITRD_PATH, )],
351
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, constants.VALUE_DEFAULT)],
352
      ])
353
  elif default_hv == constants.HT_XEN_HVM:
354
    args.extend([
355
      ["-H", "%s=acn" % constants.HV_BOOT_ORDER],
356
      ["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
357
      ])
358

    
359
  for alist in args:
360
    AssertCommand(["gnt-instance", "modify"] + alist + [instance["name"]])
361

    
362
  # check no-modify
363
  AssertCommand(["gnt-instance", "modify", instance["name"]], fail=True)
364

    
365
  # Marking offline/online while instance is running must fail
366
  for arg in ["--online", "--offline"]:
367
    AssertCommand(["gnt-instance", "modify", arg, instance["name"]], fail=True)
368

    
369

    
370
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
371
def TestInstanceStoppedModify(instance):
372
  """gnt-instance modify (stopped instance)"""
373
  name = instance["name"]
374

    
375
  # Instance was not marked offline; try marking it online once more
376
  AssertCommand(["gnt-instance", "modify", "--online", name])
377

    
378
  # Mark instance as offline
379
  AssertCommand(["gnt-instance", "modify", "--offline", name])
380

    
381
  # And online again
382
  AssertCommand(["gnt-instance", "modify", "--online", name])
383

    
384

    
385
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
386
def TestInstanceConvertDisk(instance, snode):
387
  """gnt-instance modify -t"""
388
  name = instance["name"]
389
  AssertCommand(["gnt-instance", "modify", "-t", "plain", name])
390
  AssertCommand(["gnt-instance", "modify", "-t", "drbd",
391
                 "-n", snode["primary"], name])
392

    
393

    
394
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
395
def TestInstanceGrowDisk(instance):
396
  """gnt-instance grow-disk"""
397
  name = instance["name"]
398
  all_size = qa_config.get("disk")
399
  all_grow = qa_config.get("disk-growth")
400
  if not all_grow:
401
    # missing disk sizes but instance grow disk has been enabled,
402
    # let's set fixed/nomimal growth
403
    all_grow = ["128M" for _ in all_size]
404
  for idx, (size, grow) in enumerate(zip(all_size, all_grow)):
405
    # succeed in grow by amount
406
    AssertCommand(["gnt-instance", "grow-disk", name, str(idx), grow])
407
    # fail in grow to the old size
408
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
409
                   size], fail=True)
410
    # succeed to grow to old size + 2 * growth
411
    int_size = utils.ParseUnit(size)
412
    int_grow = utils.ParseUnit(grow)
413
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
414
                   str(int_size + 2 * int_grow)])
415

    
416

    
417
def TestInstanceList():
418
  """gnt-instance list"""
419
  qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
420

    
421

    
422
def TestInstanceListFields():
423
  """gnt-instance list-fields"""
424
  qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
425

    
426

    
427
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
428
def TestInstanceConsole(instance):
429
  """gnt-instance console"""
430
  AssertCommand(["gnt-instance", "console", "--show-cmd", instance["name"]])
431

    
432

    
433
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
434
def TestReplaceDisks(instance, pnode, snode, othernode):
435
  """gnt-instance replace-disks"""
436
  # pylint: disable=W0613
437
  # due to unused pnode arg
438
  # FIXME: should be removed from the function completely
439
  def buildcmd(args):
440
    cmd = ["gnt-instance", "replace-disks"]
441
    cmd.extend(args)
442
    cmd.append(instance["name"])
443
    return cmd
444

    
445
  for data in [
446
    ["-p"],
447
    ["-s"],
448
    ["--new-secondary=%s" % othernode["primary"]],
449
    # and restore
450
    ["--new-secondary=%s" % snode["primary"]],
451
    ]:
452
    AssertCommand(buildcmd(data))
453

    
454
  AssertCommand(buildcmd(["-a"]))
455
  AssertCommand(["gnt-instance", "stop", instance["name"]])
456
  AssertCommand(buildcmd(["-a"]), fail=True)
457
  AssertCommand(["gnt-instance", "activate-disks", instance["name"]])
458
  AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
459
                 instance["name"]])
460
  AssertCommand(buildcmd(["-a"]))
461
  AssertCommand(["gnt-instance", "start", instance["name"]])
462

    
463

    
464
def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
465
                         destroy=True):
466
  """Execute gnt-instance recreate-disks and check the result
467

468
  @param cmdargs: Arguments (instance name excluded)
469
  @param instance: Instance to operate on
470
  @param fail: True if the command is expected to fail
471
  @param check: If True and fail is False, check that the disks work
472
  @prama destroy: If True, destroy the old disks first
473

474
  """
475
  if destroy:
476
    _DestroyInstanceVolumes(instance)
477
  AssertCommand((["gnt-instance", "recreate-disks"] + cmdargs +
478
                 [instance["name"]]), fail)
479
  if not fail and check:
480
    # Quick check that the disks are there
481
    AssertCommand(["gnt-instance", "activate-disks", instance["name"]])
482
    AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
483
                   instance["name"]])
484
    AssertCommand(["gnt-instance", "deactivate-disks", instance["name"]])
485

    
486

    
487
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
488
def TestRecreateDisks(instance, pnode, snode, othernodes):
489
  """gnt-instance recreate-disks
490

491
  @param instance: Instance to work on
492
  @param pnode: Primary node
493
  @param snode: Secondary node, or None for sigle-homed instances
494
  @param othernodes: list/tuple of nodes where to temporarily recreate disks
495

496
  """
497
  other_seq = ":".join([n["primary"] for n in othernodes])
498
  orig_seq = pnode["primary"]
499
  if snode:
500
    orig_seq = orig_seq + ":" + snode["primary"]
501
  # These fail because the instance is running
502
  _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
503
  _AssertRecreateDisks(["-I", "hail"], instance, fail=True, destroy=False)
504
  AssertCommand(["gnt-instance", "stop", instance["name"]])
505
  # Disks exist: this should fail
506
  _AssertRecreateDisks([], instance, fail=True, destroy=False)
507
  # Recreate disks in place
508
  _AssertRecreateDisks([], instance)
509
  # Move disks away
510
  _AssertRecreateDisks(["-I", "hail"], instance)
511
  # Move disks back
512
  _AssertRecreateDisks(["-n", orig_seq], instance, check=False)
513
  # This and InstanceCheck decoration check that the disks are working
514
  AssertCommand(["gnt-instance", "reinstall", "-f", instance["name"]])
515
  AssertCommand(["gnt-instance", "start", instance["name"]])
516

    
517

    
518
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
519
def TestInstanceExport(instance, node):
520
  """gnt-backup export -n ..."""
521
  name = instance["name"]
522
  AssertCommand(["gnt-backup", "export", "-n", node["primary"], name])
523
  return qa_utils.ResolveInstanceName(name)
524

    
525

    
526
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
527
def TestInstanceExportWithRemove(instance, node):
528
  """gnt-backup export --remove-instance"""
529
  AssertCommand(["gnt-backup", "export", "-n", node["primary"],
530
                 "--remove-instance", instance["name"]])
531

    
532

    
533
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
534
def TestInstanceExportNoTarget(instance):
535
  """gnt-backup export (without target node, should fail)"""
536
  AssertCommand(["gnt-backup", "export", instance["name"]], fail=True)
537

    
538

    
539
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
540
def TestInstanceImport(newinst, node, expnode, name):
541
  """gnt-backup import"""
542
  cmd = (["gnt-backup", "import",
543
          "--disk-template=plain",
544
          "--no-ip-check",
545
          "--src-node=%s" % expnode["primary"],
546
          "--src-dir=%s/%s" % (pathutils.EXPORT_DIR, name),
547
          "--node=%s" % node["primary"]] +
548
         _GetGenericAddParameters(newinst, force_mac=constants.VALUE_GENERATE))
549
  cmd.append(newinst["name"])
550
  AssertCommand(cmd)
551

    
552

    
553
def TestBackupList(expnode):
554
  """gnt-backup list"""
555
  AssertCommand(["gnt-backup", "list", "--node=%s" % expnode["primary"]])
556

    
557
  qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
558
                            namefield=None, test_unknown=False)
559

    
560

    
561
def TestBackupListFields():
562
  """gnt-backup list-fields"""
563
  qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
564

    
565

    
566
def _TestInstanceDiskFailure(instance, node, node2, onmaster):
567
  """Testing disk failure."""
568
  master = qa_config.GetMasterNode()
569
  sq = utils.ShellQuoteArgs
570

    
571
  instance_full = qa_utils.ResolveInstanceName(instance["name"])
572
  node_full = qa_utils.ResolveNodeName(node)
573
  node2_full = qa_utils.ResolveNodeName(node2)
574

    
575
  print qa_utils.FormatInfo("Getting physical disk names")
576
  cmd = ["gnt-node", "volumes", "--separator=|", "--no-headers",
577
         "--output=node,phys,instance",
578
         node["primary"], node2["primary"]]
579
  output = qa_utils.GetCommandOutput(master["primary"], sq(cmd))
580

    
581
  # Get physical disk names
582
  re_disk = re.compile(r"^/dev/([a-z]+)\d+$")
583
  node2disk = {}
584
  for line in output.splitlines():
585
    (node_name, phys, inst) = line.split("|")
586
    if inst == instance_full:
587
      if node_name not in node2disk:
588
        node2disk[node_name] = []
589

    
590
      m = re_disk.match(phys)
591
      if not m:
592
        raise qa_error.Error("Unknown disk name format: %s" % phys)
593

    
594
      name = m.group(1)
595
      if name not in node2disk[node_name]:
596
        node2disk[node_name].append(name)
597

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

    
602
  print qa_utils.FormatInfo("Checking whether nodes have ability to stop"
603
                            " disks")
604
  for node_name, disks in node2disk.iteritems():
605
    cmds = []
606
    for disk in disks:
607
      cmds.append(sq(["test", "-f", _GetDiskStatePath(disk)]))
608
    AssertCommand(" && ".join(cmds), node=node_name)
609

    
610
  print qa_utils.FormatInfo("Getting device paths")
611
  cmd = ["gnt-instance", "activate-disks", instance["name"]]
612
  output = qa_utils.GetCommandOutput(master["primary"], sq(cmd))
613
  devpath = []
614
  for line in output.splitlines():
615
    (_, _, tmpdevpath) = line.split(":")
616
    devpath.append(tmpdevpath)
617
  print devpath
618

    
619
  print qa_utils.FormatInfo("Getting drbd device paths")
620
  cmd = ["gnt-instance", "info", instance["name"]]
621
  output = qa_utils.GetCommandOutput(master["primary"], sq(cmd))
622
  pattern = (r"\s+-\s+sd[a-z]+,\s+type:\s+drbd8?,\s+.*$"
623
             r"\s+primary:\s+(/dev/drbd\d+)\s+")
624
  drbddevs = re.findall(pattern, output, re.M)
625
  print drbddevs
626

    
627
  halted_disks = []
628
  try:
629
    print qa_utils.FormatInfo("Deactivating disks")
630
    cmds = []
631
    for name in node2disk[[node2_full, node_full][int(onmaster)]]:
632
      halted_disks.append(name)
633
      cmds.append(sq(["echo", "offline"]) + " >%s" % _GetDiskStatePath(name))
634
    AssertCommand(" && ".join(cmds), node=[node2, node][int(onmaster)])
635

    
636
    print qa_utils.FormatInfo("Write to disks and give some time to notice"
637
                              " the problem")
638
    cmds = []
639
    for disk in devpath:
640
      cmds.append(sq(["dd", "count=1", "bs=512", "conv=notrunc",
641
                      "if=%s" % disk, "of=%s" % disk]))
642
    for _ in (0, 1, 2):
643
      AssertCommand(" && ".join(cmds), node=node)
644
      time.sleep(3)
645

    
646
    print qa_utils.FormatInfo("Debugging info")
647
    for name in drbddevs:
648
      AssertCommand(["drbdsetup", name, "show"], node=node)
649

    
650
    AssertCommand(["gnt-instance", "info", instance["name"]])
651

    
652
  finally:
653
    print qa_utils.FormatInfo("Activating disks again")
654
    cmds = []
655
    for name in halted_disks:
656
      cmds.append(sq(["echo", "running"]) + " >%s" % _GetDiskStatePath(name))
657
    AssertCommand("; ".join(cmds), node=[node2, node][int(onmaster)])
658

    
659
  if onmaster:
660
    for name in drbddevs:
661
      AssertCommand(["drbdsetup", name, "detach"], node=node)
662
  else:
663
    for name in drbddevs:
664
      AssertCommand(["drbdsetup", name, "disconnect"], node=node2)
665

    
666
  # TODO
667
  #AssertCommand(["vgs"], [node2, node][int(onmaster)])
668

    
669
  print qa_utils.FormatInfo("Making sure disks are up again")
670
  AssertCommand(["gnt-instance", "replace-disks", instance["name"]])
671

    
672
  print qa_utils.FormatInfo("Restarting instance")
673
  AssertCommand(["gnt-instance", "shutdown", instance["name"]])
674
  AssertCommand(["gnt-instance", "startup", instance["name"]])
675

    
676
  AssertCommand(["gnt-cluster", "verify"])
677

    
678

    
679
def TestInstanceMasterDiskFailure(instance, node, node2):
680
  """Testing disk failure on master node."""
681
  # pylint: disable=W0613
682
  # due to unused args
683
  print qa_utils.FormatError("Disk failure on primary node cannot be"
684
                             " tested due to potential crashes.")
685
  # The following can cause crashes, thus it's disabled until fixed
686
  #return _TestInstanceDiskFailure(instance, node, node2, True)
687

    
688

    
689
def TestInstanceSecondaryDiskFailure(instance, node, node2):
690
  """Testing disk failure on secondary node."""
691
  return _TestInstanceDiskFailure(instance, node, node2, False)