Statistics
| Branch: | Tag: | Revision:

root / qa / qa_instance.py @ 318bbaa9

History | View | Annotate | Download (30.3 kB)

1
#
2
#
3

    
4
# Copyright (C) 2007, 2011, 2012, 2013 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 operator
27
import os
28
import re
29

    
30
from ganeti import utils
31
from ganeti import constants
32
from ganeti import query
33
from ganeti import pathutils
34

    
35
import qa_config
36
import qa_utils
37
import qa_error
38

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

    
42

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

    
46

    
47
def _GetGenericAddParameters(inst, disk_template, force_mac=None):
48
  params = ["-B"]
49
  params.append("%s=%s,%s=%s" % (constants.BE_MINMEM,
50
                                 qa_config.get(constants.BE_MINMEM),
51
                                 constants.BE_MAXMEM,
52
                                 qa_config.get(constants.BE_MAXMEM)))
53

    
54
  if disk_template != constants.DT_DISKLESS:
55
    for idx, size in enumerate(qa_config.get("disk")):
56
      params.extend(["--disk", "%s:size=%s" % (idx, size)])
57

    
58
  # Set static MAC address if configured
59
  if force_mac:
60
    nic0_mac = force_mac
61
  else:
62
    nic0_mac = inst.GetNicMacAddr(0, None)
63

    
64
  if nic0_mac:
65
    params.extend(["--net", "0:mac=%s" % nic0_mac])
66

    
67
  return params
68

    
69

    
70
def _DiskTest(node, disk_template, fail=False):
71
  instance = qa_config.AcquireInstance()
72
  try:
73
    cmd = (["gnt-instance", "add",
74
            "--os-type=%s" % qa_config.get("os"),
75
            "--disk-template=%s" % disk_template,
76
            "--node=%s" % node] +
77
           _GetGenericAddParameters(instance, disk_template))
78
    cmd.append(instance.name)
79

    
80
    AssertCommand(cmd, fail=fail)
81

    
82
    if not fail:
83
      _CheckSsconfInstanceList(instance.name)
84
      instance.SetDiskTemplate(disk_template)
85

    
86
      return instance
87
  except:
88
    instance.Release()
89
    raise
90

    
91
  # Handle the case where creation is expected to fail
92
  assert fail
93
  instance.Release()
94
  return None
95

    
96

    
97
def _GetInstanceInfo(instance):
98
  """Return information about the actual state of an instance.
99

100
  @type instance: string
101
  @param instance: the instance name
102
  @return: a dictionary with the following keys:
103
      - "nodes": instance nodes, a list of strings
104
      - "volumes": instance volume IDs, a list of strings
105
      - "drbd-minors": DRBD minors used by the instance, a dictionary where
106
        keys are nodes, and values are lists of integers (or an empty
107
        dictionary for non-DRBD instances)
108
      - "disk-template": instance disk template
109
      - "storage-type": storage type associated with the instance disk template
110

111
  """
112
  node_elem = r"([^,()]+)(?:\s+\([^)]+\))?"
113
  # re_nodelist matches a list of nodes returned by gnt-instance info, e.g.:
114
  #  node1.fqdn
115
  #  node2.fqdn,node3.fqdn
116
  #  node4.fqdn (group mygroup, group UUID 01234567-abcd-0123-4567-0123456789ab)
117
  # FIXME This works with no more than 2 secondaries
118
  re_nodelist = re.compile(node_elem + "(?:," + node_elem + ")?$")
119

    
120
  info = qa_utils.GetObjectInfo(["gnt-instance", "info", instance])[0]
121
  nodes = []
122
  for nodeinfo in info["Nodes"]:
123
    if "primary" in nodeinfo:
124
      nodes.append(nodeinfo["primary"])
125
    elif "secondaries" in nodeinfo:
126
      nodestr = nodeinfo["secondaries"]
127
      if nodestr:
128
        m = re_nodelist.match(nodestr)
129
        if m:
130
          nodes.extend(filter(None, m.groups()))
131
        else:
132
          nodes.append(nodestr)
133

    
134
  disk_template = info["Disk template"]
135
  if not disk_template:
136
    raise qa_error.Error("Can't get instance disk template")
137
  storage_type = constants.DISK_TEMPLATES_STORAGE_TYPE[disk_template]
138

    
139
  re_drbdnode = re.compile(r"^([^\s,]+),\s+minor=([0-9]+)$")
140
  vols = []
141
  drbd_min = {}
142
  for (count, diskinfo) in enumerate(info["Disks"]):
143
    (dtype, _) = diskinfo["disk/%s" % count].split(",", 1)
144
    if dtype == constants.LD_DRBD8:
145
      for child in diskinfo["child devices"]:
146
        vols.append(child["logical_id"])
147
      for key in ["nodeA", "nodeB"]:
148
        m = re_drbdnode.match(diskinfo[key])
149
        if not m:
150
          raise qa_error.Error("Cannot parse DRBD info: %s" % diskinfo[key])
151
        node = m.group(1)
152
        minor = int(m.group(2))
153
        minorlist = drbd_min.setdefault(node, [])
154
        minorlist.append(minor)
155
    elif dtype == constants.LD_LV:
156
      vols.append(diskinfo["logical_id"])
157

    
158
  assert nodes
159
  assert len(nodes) < 2 or vols
160
  return {
161
    "nodes": nodes,
162
    "volumes": vols,
163
    "drbd-minors": drbd_min,
164
    "disk-template": disk_template,
165
    "storage-type": storage_type,
166
    }
167

    
168

    
169
def _DestroyInstanceVolumes(instance):
170
  """Remove all the LVM volumes of an instance.
171

172
  This is used to simulate HW errors (dead nodes, broken disks...); the
173
  configuration of the instance is not affected.
174
  @type instance: dictionary
175
  @param instance: the instance
176

177
  """
178
  info = _GetInstanceInfo(instance.name)
179
  vols = info["volumes"]
180
  for node in info["nodes"]:
181
    AssertCommand(["lvremove", "-f"] + vols, node=node)
182

    
183

    
184
def _GetInstanceField(instance, field):
185
  """Get the value of a field of an instance.
186

187
  @type instance: string
188
  @param instance: Instance name
189
  @type field: string
190
  @param field: Name of the field
191
  @rtype: string
192

193
  """
194
  master = qa_config.GetMasterNode()
195
  infocmd = utils.ShellQuoteArgs(["gnt-instance", "list", "--no-headers",
196
                                  "--units", "m", "-o", field, instance])
197
  return qa_utils.GetCommandOutput(master.primary, infocmd).strip()
198

    
199

    
200
def _GetBoolInstanceField(instance, field):
201
  """Get the Boolean value of a field of an instance.
202

203
  @type instance: string
204
  @param instance: Instance name
205
  @type field: string
206
  @param field: Name of the field
207
  @rtype: bool
208

209
  """
210
  info_out = _GetInstanceField(instance, field)
211
  if info_out == "Y":
212
    return True
213
  elif info_out == "N":
214
    return False
215
  else:
216
    raise qa_error.Error("Field %s of instance %s has a non-Boolean value:"
217
                         " %s" % (field, instance, info_out))
218

    
219

    
220
def _GetNumInstanceField(instance, field):
221
  """Get a numeric value of a field of an instance.
222

223
  @type instance: string
224
  @param instance: Instance name
225
  @type field: string
226
  @param field: Name of the field
227
  @rtype: int or float
228

229
  """
230
  info_out = _GetInstanceField(instance, field)
231
  try:
232
    ret = int(info_out)
233
  except ValueError:
234
    try:
235
      ret = float(info_out)
236
    except ValueError:
237
      raise qa_error.Error("Field %s of instance %s has a non-numeric value:"
238
                           " %s" % (field, instance, info_out))
239
  return ret
240

    
241

    
242
def GetInstanceSpec(instance, spec):
243
  """Return the current spec for the given parameter.
244

245
  @type instance: string
246
  @param instance: Instance name
247
  @type spec: string
248
  @param spec: one of the supported parameters: "mem-size", "cpu-count",
249
      "disk-count", "disk-size", "nic-count"
250
  @rtype: tuple
251
  @return: (minspec, maxspec); minspec and maxspec can be different only for
252
      memory and disk size
253

254
  """
255
  specmap = {
256
    "mem-size": ["be/minmem", "be/maxmem"],
257
    "cpu-count": ["vcpus"],
258
    "disk-count": ["disk.count"],
259
    "disk-size": ["disk.size/ "],
260
    "nic-count": ["nic.count"],
261
    }
262
  # For disks, first we need the number of disks
263
  if spec == "disk-size":
264
    (numdisk, _) = GetInstanceSpec(instance, "disk-count")
265
    fields = ["disk.size/%s" % k for k in range(0, numdisk)]
266
  else:
267
    assert spec in specmap, "%s not in %s" % (spec, specmap)
268
    fields = specmap[spec]
269
  values = [_GetNumInstanceField(instance, f) for f in fields]
270
  return (min(values), max(values))
271

    
272

    
273
def IsFailoverSupported(instance):
274
  return instance.disk_template in constants.DTS_MIRRORED
275

    
276

    
277
def IsMigrationSupported(instance):
278
  return instance.disk_template in constants.DTS_MIRRORED
279

    
280

    
281
def IsDiskReplacingSupported(instance):
282
  return instance.disk_template == constants.DT_DRBD8
283

    
284

    
285
def TestInstanceAddWithPlainDisk(nodes, fail=False):
286
  """gnt-instance add -t plain"""
287
  assert len(nodes) == 1
288
  instance = _DiskTest(nodes[0].primary, constants.DT_PLAIN, fail=fail)
289
  if not fail:
290
    qa_utils.RunInstanceCheck(instance, True)
291
  return instance
292

    
293

    
294
@InstanceCheck(None, INST_UP, RETURN_VALUE)
295
def TestInstanceAddWithDrbdDisk(nodes):
296
  """gnt-instance add -t drbd"""
297
  assert len(nodes) == 2
298
  return _DiskTest(":".join(map(operator.attrgetter("primary"), nodes)),
299
                   constants.DT_DRBD8)
300

    
301

    
302
@InstanceCheck(None, INST_UP, RETURN_VALUE)
303
def TestInstanceAddFile(nodes):
304
  """gnt-instance add -t file"""
305
  assert len(nodes) == 1
306
  return _DiskTest(nodes[0].primary, constants.DT_FILE)
307

    
308

    
309
@InstanceCheck(None, INST_UP, RETURN_VALUE)
310
def TestInstanceAddDiskless(nodes):
311
  """gnt-instance add -t diskless"""
312
  assert len(nodes) == 1
313
  return _DiskTest(nodes[0].primary, constants.DT_DISKLESS)
314

    
315

    
316
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
317
def TestInstanceRemove(instance):
318
  """gnt-instance remove"""
319
  AssertCommand(["gnt-instance", "remove", "-f", instance.name])
320

    
321

    
322
@InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
323
def TestInstanceStartup(instance):
324
  """gnt-instance startup"""
325
  AssertCommand(["gnt-instance", "startup", instance.name])
326

    
327

    
328
@InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
329
def TestInstanceShutdown(instance):
330
  """gnt-instance shutdown"""
331
  AssertCommand(["gnt-instance", "shutdown", instance.name])
332

    
333

    
334
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
335
def TestInstanceReboot(instance):
336
  """gnt-instance reboot"""
337
  options = qa_config.get("options", {})
338
  reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
339
  name = instance.name
340
  for rtype in reboot_types:
341
    AssertCommand(["gnt-instance", "reboot", "--type=%s" % rtype, name])
342

    
343
  AssertCommand(["gnt-instance", "shutdown", name])
344
  qa_utils.RunInstanceCheck(instance, False)
345
  AssertCommand(["gnt-instance", "reboot", name])
346

    
347
  master = qa_config.GetMasterNode()
348
  cmd = ["gnt-instance", "list", "--no-headers", "-o", "status", name]
349
  result_output = qa_utils.GetCommandOutput(master.primary,
350
                                            utils.ShellQuoteArgs(cmd))
351
  AssertEqual(result_output.strip(), constants.INSTST_RUNNING)
352

    
353

    
354
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
355
def TestInstanceReinstall(instance):
356
  """gnt-instance reinstall"""
357
  if instance.disk_template == constants.DT_DISKLESS:
358
    print qa_utils.FormatInfo("Test not supported for diskless instances")
359
    return
360

    
361
  AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
362

    
363
  # Test with non-existant OS definition
364
  AssertCommand(["gnt-instance", "reinstall", "-f",
365
                 "--os-type=NonExistantOsForQa",
366
                 instance.name],
367
                fail=True)
368

    
369

    
370
def _ReadSsconfInstanceList():
371
  """Reads ssconf_instance_list from the master node.
372

373
  """
374
  master = qa_config.GetMasterNode()
375

    
376
  ssconf_path = utils.PathJoin(pathutils.DATA_DIR,
377
                               "ssconf_%s" % constants.SS_INSTANCE_LIST)
378

    
379
  cmd = ["cat", qa_utils.MakeNodePath(master, ssconf_path)]
380

    
381
  return qa_utils.GetCommandOutput(master.primary,
382
                                   utils.ShellQuoteArgs(cmd)).splitlines()
383

    
384

    
385
def _CheckSsconfInstanceList(instance):
386
  """Checks if a certain instance is in the ssconf instance list.
387

388
  @type instance: string
389
  @param instance: Instance name
390

391
  """
392
  AssertIn(qa_utils.ResolveInstanceName(instance),
393
           _ReadSsconfInstanceList())
394

    
395

    
396
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
397
def TestInstanceRenameAndBack(rename_source, rename_target):
398
  """gnt-instance rename
399

400
  This must leave the instance with the original name, not the target
401
  name.
402

403
  """
404
  _CheckSsconfInstanceList(rename_source)
405

    
406
  # first do a rename to a different actual name, expecting it to fail
407
  qa_utils.AddToEtcHosts(["meeeeh-not-exists", rename_target])
408
  try:
409
    AssertCommand(["gnt-instance", "rename", rename_source, rename_target],
410
                  fail=True)
411
    _CheckSsconfInstanceList(rename_source)
412
  finally:
413
    qa_utils.RemoveFromEtcHosts(["meeeeh-not-exists", rename_target])
414

    
415
  info = _GetInstanceInfo(rename_source)
416

    
417
  # Check instance volume tags correctly updated. Note that this check is lvm
418
  # specific, so we skip it for non-lvm-based instances.
419
  # FIXME: This will need updating when instances will be able to have
420
  # different disks living on storage pools with etherogeneous storage types.
421
  # FIXME: This check should be put inside the disk/storage class themselves,
422
  # rather than explicitly called here.
423
  if info["storage-type"] == constants.ST_LVM_VG:
424
    # In the lvm world we can check for tags on the logical volume
425
    tags_cmd = ("lvs -o tags --noheadings %s | grep " %
426
                (" ".join(info["volumes"]), ))
427
  else:
428
    # Other storage types don't have tags, so we use an always failing command,
429
    # to make sure it never gets executed
430
    tags_cmd = "false"
431

    
432
  # and now rename instance to rename_target...
433
  AssertCommand(["gnt-instance", "rename", rename_source, rename_target])
434
  _CheckSsconfInstanceList(rename_target)
435
  qa_utils.RunInstanceCheck(rename_source, False)
436
  qa_utils.RunInstanceCheck(rename_target, False)
437

    
438
  # NOTE: tags might not be the exactly as the instance name, due to
439
  # charset restrictions; hence the test might be flaky
440
  if (rename_source != rename_target and
441
      info["storage-type"] == constants.ST_LVM_VG):
442
    for node in info["nodes"]:
443
      AssertCommand(tags_cmd + rename_source, node=node, fail=True)
444
      AssertCommand(tags_cmd + rename_target, node=node, fail=False)
445

    
446
  # and back
447
  AssertCommand(["gnt-instance", "rename", rename_target, rename_source])
448
  _CheckSsconfInstanceList(rename_source)
449
  qa_utils.RunInstanceCheck(rename_target, False)
450

    
451
  if (rename_source != rename_target and
452
      info["storage-type"] == constants.ST_LVM_VG):
453
    for node in info["nodes"]:
454
      AssertCommand(tags_cmd + rename_source, node=node, fail=False)
455
      AssertCommand(tags_cmd + rename_target, node=node, fail=True)
456

    
457

    
458
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
459
def TestInstanceFailover(instance):
460
  """gnt-instance failover"""
461
  if not IsFailoverSupported(instance):
462
    print qa_utils.FormatInfo("Instance doesn't support failover, skipping"
463
                              " test")
464
    return
465

    
466
  cmd = ["gnt-instance", "failover", "--force", instance.name]
467

    
468
  # failover ...
469
  AssertCommand(cmd)
470
  qa_utils.RunInstanceCheck(instance, True)
471

    
472
  # ... and back
473
  AssertCommand(cmd)
474

    
475

    
476
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
477
def TestInstanceMigrate(instance, toggle_always_failover=True):
478
  """gnt-instance migrate"""
479
  if not IsMigrationSupported(instance):
480
    print qa_utils.FormatInfo("Instance doesn't support migration, skipping"
481
                              " test")
482
    return
483

    
484
  cmd = ["gnt-instance", "migrate", "--force", instance.name]
485
  af_par = constants.BE_ALWAYS_FAILOVER
486
  af_field = "be/" + constants.BE_ALWAYS_FAILOVER
487
  af_init_val = _GetBoolInstanceField(instance.name, af_field)
488

    
489
  # migrate ...
490
  AssertCommand(cmd)
491
  # TODO: Verify the choice between failover and migration
492
  qa_utils.RunInstanceCheck(instance, True)
493

    
494
  # ... and back (possibly with always_failover toggled)
495
  if toggle_always_failover:
496
    AssertCommand(["gnt-instance", "modify", "-B",
497
                   ("%s=%s" % (af_par, not af_init_val)),
498
                   instance.name])
499
  AssertCommand(cmd)
500
  # TODO: Verify the choice between failover and migration
501
  qa_utils.RunInstanceCheck(instance, True)
502
  if toggle_always_failover:
503
    AssertCommand(["gnt-instance", "modify", "-B",
504
                   ("%s=%s" % (af_par, af_init_val)), instance.name])
505

    
506
  # TODO: Split into multiple tests
507
  AssertCommand(["gnt-instance", "shutdown", instance.name])
508
  qa_utils.RunInstanceCheck(instance, False)
509
  AssertCommand(cmd, fail=True)
510
  AssertCommand(["gnt-instance", "migrate", "--force", "--allow-failover",
511
                 instance.name])
512
  AssertCommand(["gnt-instance", "start", instance.name])
513
  AssertCommand(cmd)
514
  # @InstanceCheck enforces the check that the instance is running
515
  qa_utils.RunInstanceCheck(instance, True)
516

    
517
  AssertCommand(["gnt-instance", "modify", "-B",
518
                 ("%s=%s" %
519
                  (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)),
520
                 instance.name])
521

    
522
  AssertCommand(cmd)
523
  qa_utils.RunInstanceCheck(instance, True)
524
  # TODO: Verify that a failover has been done instead of a migration
525

    
526
  # TODO: Verify whether the default value is restored here (not hardcoded)
527
  AssertCommand(["gnt-instance", "modify", "-B",
528
                 ("%s=%s" %
529
                  (constants.BE_ALWAYS_FAILOVER, constants.VALUE_FALSE)),
530
                 instance.name])
531

    
532
  AssertCommand(cmd)
533
  qa_utils.RunInstanceCheck(instance, True)
534

    
535

    
536
def TestInstanceInfo(instance):
537
  """gnt-instance info"""
538
  AssertCommand(["gnt-instance", "info", instance.name])
539

    
540

    
541
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
542
def TestInstanceModify(instance):
543
  """gnt-instance modify"""
544
  default_hv = qa_config.GetDefaultHypervisor()
545

    
546
  # Assume /sbin/init exists on all systems
547
  test_kernel = "/sbin/init"
548
  test_initrd = test_kernel
549

    
550
  orig_maxmem = qa_config.get(constants.BE_MAXMEM)
551
  orig_minmem = qa_config.get(constants.BE_MINMEM)
552
  #orig_bridge = qa_config.get("bridge", "xen-br0")
553

    
554
  args = [
555
    ["-B", "%s=128" % constants.BE_MINMEM],
556
    ["-B", "%s=128" % constants.BE_MAXMEM],
557
    ["-B", "%s=%s,%s=%s" % (constants.BE_MINMEM, orig_minmem,
558
                            constants.BE_MAXMEM, orig_maxmem)],
559
    ["-B", "%s=2" % constants.BE_VCPUS],
560
    ["-B", "%s=1" % constants.BE_VCPUS],
561
    ["-B", "%s=%s" % (constants.BE_VCPUS, constants.VALUE_DEFAULT)],
562
    ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_TRUE)],
563
    ["-B", "%s=%s" % (constants.BE_ALWAYS_FAILOVER, constants.VALUE_DEFAULT)],
564

    
565
    ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, test_kernel)],
566
    ["-H", "%s=%s" % (constants.HV_KERNEL_PATH, constants.VALUE_DEFAULT)],
567

    
568
    # TODO: bridge tests
569
    #["--bridge", "xen-br1"],
570
    #["--bridge", orig_bridge],
571
    ]
572

    
573
  if default_hv == constants.HT_XEN_PVM:
574
    args.extend([
575
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, test_initrd)],
576
      ["-H", "no_%s" % (constants.HV_INITRD_PATH, )],
577
      ["-H", "%s=%s" % (constants.HV_INITRD_PATH, constants.VALUE_DEFAULT)],
578
      ])
579
  elif default_hv == constants.HT_XEN_HVM:
580
    args.extend([
581
      ["-H", "%s=acn" % constants.HV_BOOT_ORDER],
582
      ["-H", "%s=%s" % (constants.HV_BOOT_ORDER, constants.VALUE_DEFAULT)],
583
      ])
584

    
585
  for alist in args:
586
    AssertCommand(["gnt-instance", "modify"] + alist + [instance.name])
587

    
588
  # check no-modify
589
  AssertCommand(["gnt-instance", "modify", instance.name], fail=True)
590

    
591
  # Marking offline while instance is running must fail...
592
  AssertCommand(["gnt-instance", "modify", "--offline", instance.name],
593
                 fail=True)
594

    
595
  # ...while making it online is ok, and should work
596
  AssertCommand(["gnt-instance", "modify", "--online", instance.name])
597

    
598

    
599
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
600
def TestInstanceModifyPrimaryAndBack(instance, currentnode, othernode):
601
  """gnt-instance modify --new-primary
602

603
  This will leave the instance on its original primary node, not other node.
604

605
  """
606
  if instance.disk_template != constants.DT_FILE:
607
    print qa_utils.FormatInfo("Test only supported for the file disk template")
608
    return
609

    
610
  cluster_name = qa_config.get("name")
611

    
612
  name = instance.name
613
  current = currentnode.primary
614
  other = othernode.primary
615

    
616
  # FIXME: the qa doesn't have a customizable file storage dir parameter. As
617
  # such for now we use the default.
618
  filestorage = pathutils.DEFAULT_FILE_STORAGE_DIR
619
  disk = os.path.join(filestorage, name)
620

    
621
  AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name],
622
                fail=True)
623
  AssertCommand(["gnt-instance", "shutdown", name])
624
  AssertCommand(["scp", "-oGlobalKnownHostsFile=%s" %
625
                 pathutils.SSH_KNOWN_HOSTS_FILE,
626
                 "-oCheckHostIp=no", "-oStrictHostKeyChecking=yes",
627
                 "-oHashKnownHosts=no", "-oHostKeyAlias=%s" % cluster_name,
628
                 "-r", disk, "%s:%s" % (other, filestorage)])
629
  AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % other, name])
630
  AssertCommand(["gnt-instance", "startup", name])
631

    
632
  # and back
633
  AssertCommand(["gnt-instance", "shutdown", name])
634
  AssertCommand(["rm", "-rf", disk], node=other)
635
  AssertCommand(["gnt-instance", "modify", "--new-primary=%s" % current, name])
636
  AssertCommand(["gnt-instance", "startup", name])
637

    
638

    
639
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
640
def TestInstanceStoppedModify(instance):
641
  """gnt-instance modify (stopped instance)"""
642
  name = instance.name
643

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

    
647
  # Mark instance as offline
648
  AssertCommand(["gnt-instance", "modify", "--offline", name])
649

    
650
  # When the instance is offline shutdown should only work with --force,
651
  # while start should never work
652
  AssertCommand(["gnt-instance", "shutdown", name], fail=True)
653
  AssertCommand(["gnt-instance", "shutdown", "--force", name])
654
  AssertCommand(["gnt-instance", "start", name], fail=True)
655
  AssertCommand(["gnt-instance", "start", "--force", name], fail=True)
656

    
657
  # Also do offline to offline
658
  AssertCommand(["gnt-instance", "modify", "--offline", name])
659

    
660
  # And online again
661
  AssertCommand(["gnt-instance", "modify", "--online", name])
662

    
663

    
664
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
665
def TestInstanceConvertDiskToPlain(instance, inodes):
666
  """gnt-instance modify -t"""
667
  name = instance.name
668

    
669
  template = instance.disk_template
670
  if template != constants.DT_DRBD8:
671
    print qa_utils.FormatInfo("Unsupported template %s, skipping conversion"
672
                              " test" % template)
673
    return
674

    
675
  assert len(inodes) == 2
676
  AssertCommand(["gnt-instance", "modify", "-t", constants.DT_PLAIN, name])
677
  AssertCommand(["gnt-instance", "modify", "-t", constants.DT_DRBD8,
678
                 "-n", inodes[1].primary, name])
679

    
680

    
681
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
682
def TestInstanceGrowDisk(instance):
683
  """gnt-instance grow-disk"""
684
  if qa_config.GetExclusiveStorage():
685
    print qa_utils.FormatInfo("Test not supported with exclusive_storage")
686
    return
687

    
688
  if instance.disk_template == constants.DT_DISKLESS:
689
    print qa_utils.FormatInfo("Test not supported for diskless instances")
690
    return
691

    
692
  name = instance.name
693
  all_size = qa_config.get("disk")
694
  all_grow = qa_config.get("disk-growth")
695

    
696
  if not all_grow:
697
    # missing disk sizes but instance grow disk has been enabled,
698
    # let's set fixed/nomimal growth
699
    all_grow = ["128M" for _ in all_size]
700

    
701
  for idx, (size, grow) in enumerate(zip(all_size, all_grow)):
702
    # succeed in grow by amount
703
    AssertCommand(["gnt-instance", "grow-disk", name, str(idx), grow])
704
    # fail in grow to the old size
705
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
706
                   size], fail=True)
707
    # succeed to grow to old size + 2 * growth
708
    int_size = utils.ParseUnit(size)
709
    int_grow = utils.ParseUnit(grow)
710
    AssertCommand(["gnt-instance", "grow-disk", "--absolute", name, str(idx),
711
                   str(int_size + 2 * int_grow)])
712

    
713

    
714
def TestInstanceList():
715
  """gnt-instance list"""
716
  qa_utils.GenericQueryTest("gnt-instance", query.INSTANCE_FIELDS.keys())
717

    
718

    
719
def TestInstanceListFields():
720
  """gnt-instance list-fields"""
721
  qa_utils.GenericQueryFieldsTest("gnt-instance", query.INSTANCE_FIELDS.keys())
722

    
723

    
724
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
725
def TestInstanceConsole(instance):
726
  """gnt-instance console"""
727
  AssertCommand(["gnt-instance", "console", "--show-cmd", instance.name])
728

    
729

    
730
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
731
def TestReplaceDisks(instance, curr_nodes, other_nodes):
732
  """gnt-instance replace-disks"""
733
  def buildcmd(args):
734
    cmd = ["gnt-instance", "replace-disks"]
735
    cmd.extend(args)
736
    cmd.append(instance.name)
737
    return cmd
738

    
739
  if not IsDiskReplacingSupported(instance):
740
    print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
741
                              " skipping test")
742
    return
743

    
744
  # Currently all supported templates have one primary and one secondary node
745
  assert len(curr_nodes) == 2
746
  snode = curr_nodes[1]
747
  assert len(other_nodes) == 1
748
  othernode = other_nodes[0]
749

    
750
  options = qa_config.get("options", {})
751
  use_ialloc = options.get("use-iallocators", True)
752
  for data in [
753
    ["-p"],
754
    ["-s"],
755
    # A placeholder; the actual command choice depends on use_ialloc
756
    None,
757
    # Restore the original secondary
758
    ["--new-secondary=%s" % snode.primary],
759
    ]:
760
    if data is None:
761
      if use_ialloc:
762
        data = ["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT]
763
      else:
764
        data = ["--new-secondary=%s" % othernode.primary]
765
    AssertCommand(buildcmd(data))
766

    
767
  AssertCommand(buildcmd(["-a"]))
768
  AssertCommand(["gnt-instance", "stop", instance.name])
769
  AssertCommand(buildcmd(["-a"]), fail=True)
770
  AssertCommand(["gnt-instance", "activate-disks", instance.name])
771
  AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
772
                 instance.name])
773
  AssertCommand(buildcmd(["-a"]))
774
  AssertCommand(["gnt-instance", "start", instance.name])
775

    
776

    
777
def _AssertRecreateDisks(cmdargs, instance, fail=False, check=True,
778
                         destroy=True):
779
  """Execute gnt-instance recreate-disks and check the result
780

781
  @param cmdargs: Arguments (instance name excluded)
782
  @param instance: Instance to operate on
783
  @param fail: True if the command is expected to fail
784
  @param check: If True and fail is False, check that the disks work
785
  @prama destroy: If True, destroy the old disks first
786

787
  """
788
  if destroy:
789
    _DestroyInstanceVolumes(instance)
790
  AssertCommand((["gnt-instance", "recreate-disks"] + cmdargs +
791
                 [instance.name]), fail)
792
  if not fail and check:
793
    # Quick check that the disks are there
794
    AssertCommand(["gnt-instance", "activate-disks", instance.name])
795
    AssertCommand(["gnt-instance", "activate-disks", "--wait-for-sync",
796
                   instance.name])
797
    AssertCommand(["gnt-instance", "deactivate-disks", instance.name])
798

    
799

    
800
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
801
def TestRecreateDisks(instance, inodes, othernodes):
802
  """gnt-instance recreate-disks
803

804
  @param instance: Instance to work on
805
  @param inodes: List of the current nodes of the instance
806
  @param othernodes: list/tuple of nodes where to temporarily recreate disks
807

808
  """
809
  options = qa_config.get("options", {})
810
  use_ialloc = options.get("use-iallocators", True)
811
  other_seq = ":".join([n.primary for n in othernodes])
812
  orig_seq = ":".join([n.primary for n in inodes])
813
  # These fail because the instance is running
814
  _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
815
  if use_ialloc:
816
    _AssertRecreateDisks(["-I", "hail"], instance, fail=True, destroy=False)
817
  else:
818
    _AssertRecreateDisks(["-n", other_seq], instance, fail=True, destroy=False)
819
  AssertCommand(["gnt-instance", "stop", instance.name])
820
  # Disks exist: this should fail
821
  _AssertRecreateDisks([], instance, fail=True, destroy=False)
822
  # Recreate disks in place
823
  _AssertRecreateDisks([], instance)
824
  # Move disks away
825
  if use_ialloc:
826
    _AssertRecreateDisks(["-I", "hail"], instance)
827
    # Move disks somewhere else
828
    _AssertRecreateDisks(["-I", constants.DEFAULT_IALLOCATOR_SHORTCUT],
829
                         instance)
830
  else:
831
    _AssertRecreateDisks(["-n", other_seq], instance)
832
  # Move disks back
833
  _AssertRecreateDisks(["-n", orig_seq], instance, check=False)
834
  # This and InstanceCheck decoration check that the disks are working
835
  AssertCommand(["gnt-instance", "reinstall", "-f", instance.name])
836
  AssertCommand(["gnt-instance", "start", instance.name])
837

    
838

    
839
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
840
def TestInstanceExport(instance, node):
841
  """gnt-backup export -n ..."""
842
  name = instance.name
843
  AssertCommand(["gnt-backup", "export", "-n", node.primary, name])
844
  return qa_utils.ResolveInstanceName(name)
845

    
846

    
847
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
848
def TestInstanceExportWithRemove(instance, node):
849
  """gnt-backup export --remove-instance"""
850
  AssertCommand(["gnt-backup", "export", "-n", node.primary,
851
                 "--remove-instance", instance.name])
852

    
853

    
854
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
855
def TestInstanceExportNoTarget(instance):
856
  """gnt-backup export (without target node, should fail)"""
857
  AssertCommand(["gnt-backup", "export", instance.name], fail=True)
858

    
859

    
860
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
861
def TestInstanceImport(newinst, node, expnode, name):
862
  """gnt-backup import"""
863
  templ = constants.DT_PLAIN
864
  cmd = (["gnt-backup", "import",
865
          "--disk-template=%s" % templ,
866
          "--no-ip-check",
867
          "--src-node=%s" % expnode.primary,
868
          "--src-dir=%s/%s" % (pathutils.EXPORT_DIR, name),
869
          "--node=%s" % node.primary] +
870
         _GetGenericAddParameters(newinst, templ,
871
                                  force_mac=constants.VALUE_GENERATE))
872
  cmd.append(newinst.name)
873
  AssertCommand(cmd)
874
  newinst.SetDiskTemplate(templ)
875

    
876

    
877
def TestBackupList(expnode):
878
  """gnt-backup list"""
879
  AssertCommand(["gnt-backup", "list", "--node=%s" % expnode.primary])
880

    
881
  qa_utils.GenericQueryTest("gnt-backup", query.EXPORT_FIELDS.keys(),
882
                            namefield=None, test_unknown=False)
883

    
884

    
885
def TestBackupListFields():
886
  """gnt-backup list-fields"""
887
  qa_utils.GenericQueryFieldsTest("gnt-backup", query.EXPORT_FIELDS.keys())
888

    
889

    
890
def TestRemoveInstanceOfflineNode(instance, snode, set_offline, set_online):
891
  """gnt-instance remove with an off-line node
892

893
  @param instance: instance
894
  @param snode: secondary node, to be set offline
895
  @param set_offline: function to call to set the node off-line
896
  @param set_online: function to call to set the node on-line
897

898
  """
899
  info = _GetInstanceInfo(instance.name)
900
  set_offline(snode)
901
  try:
902
    TestInstanceRemove(instance)
903
  finally:
904
    set_online(snode)
905

    
906
  # Clean up the disks on the offline node, if necessary
907
  if instance.disk_template not in constants.DTS_EXT_MIRROR:
908
    # FIXME: abstract the cleanup inside the disks
909
    if info["storage-type"] == constants.ST_LVM_VG:
910
      for minor in info["drbd-minors"][snode.primary]:
911
        AssertCommand(["drbdsetup", str(minor), "down"], node=snode)
912
      AssertCommand(["lvremove", "-f"] + info["volumes"], node=snode)
913
    elif info["storage-type"] == constants.ST_FILE:
914
      filestorage = pathutils.DEFAULT_FILE_STORAGE_DIR
915
      disk = os.path.join(filestorage, instance.name)
916
      AssertCommand(["rm", "-rf", disk], node=snode)